jax-ml/jax · error · ValueError

Invalid equality constraint: {e1} == {e2}. The left-hand-sid

Error message

Invalid equality constraint: {e1} == {e2}. The left-hand-side must be of the form `term * coefficient`.

What it means

For equality constraints used as normalization rules (solving one variable in terms of others), the left-hand side must be a single term times a coefficient (e.g. 'a * 2 == b'). If the LHS is not a symbolic expression at all (a bare constant), the constraint cannot define a normalization and is rejected.

Source

Thrown at jax/_src/export/shape_poly.py:1076

    e2, = _Parser(e2_str, None, repr(e2_str), self).parse()
    if cmp == Comparator.GEQ and not is_geq:
      e1, e2 = e2, e1

    # Compute e1 - e2 before we add to normalization rules
    constr = _SymbolicConstraint(debug_str=c_str, cmp=cmp, e1=e1, e2=e2,
                                 diff=e1 - e2)
    self._process_explicit_constraint(constr)

  def _process_explicit_constraint(self, constr: _SymbolicConstraint):
    if (diff_const := _DimExpr._to_constant(constr.diff)) is not None:
      if ((constr.cmp == Comparator.EQ and diff_const != 0) or
          (constr.cmp == Comparator.GEQ and diff_const < 0)):
        raise ValueError(f"Unsatisfiable explicit constraint: {constr.debug_str}")
      return

    if constr.cmp == Comparator.EQ:
      if not isinstance(constr.e1, _DimExpr):
        raise ValueError("Invalid equality constraint: {e1} == {e2}. "
                         "The left-hand-side must be of the form `term * coefficient`.")
      (before, before_k), *rest = constr.e1._sorted_terms
      if rest:
        raise ValueError("Invalid equality constraint: {e1} == {e2}. "
                         "The left-hand-side must be of the form `term * coefficient`.")

      after = _ensure_poly(constr.e2, "parse_constraint", constr.e1.scope)
      if before in self._normalization_rules:
        raise NotImplementedError(
            f"Found multiple equality constraints with the same left-hand-side: {before}")
      self._normalization_rules[before] = (after, before_k)
      # Look for constraints of the form mod(before_e1, before_k2) * 1 == 0
      if (before_k == 1 and
          isinstance(constr.e2, int) and constr.e2 == 0 and
          (before_f := before.to_factor()) and
          before_f.operation == _DimFactor.MOD and
          (before_k2 := _DimExpr._to_constant(before_f.operands[1])) is not None):
        # Add before_k2*floordiv(before_e1, before_k2) == before_e1

View on GitHub (pinned to 1e1c6a8fc0)

Solutions

  1. Put the symbolic term on the left-hand side: 'n * 2 == 8' instead of '8 == n * 2'
  2. If the constraint is a bound, use '>=' or '<=' instead of '=='
  3. Ensure at least one dim variable appears on the LHS

Example fix

# before
constraints = ('8 == n * 2',)
# after
constraints = ('n * 2 == 8',)
Defensive patterns

Strategy: validation

Validate before calling

for c in eq_constraints:
    lhs = c.split('==')[0]
    assert has_dim_var(lhs), f'LHS must contain a variable: {c}'

Type guard

def lhs_is_symbolic(c: str) -> bool:
    return any(ch.isalpha() for ch in c.split('==')[0])

Prevention

When it happens

Trigger: Constraints like '4 == b', '8 == n*2', or '2 == a' where the LHS contains no dimension variable.

Common situations: Writing the constant on the left by habit; constraints intended as range checks mistakenly written with '==' and constant LHS.

Related errors


AI-assisted analysis of jax-ml/jax@1e1c6a8fc0 (2026-08-27). Data as JSON: /api/errors/47f1ad1bca369ea5. Report an issue: GitHub.