jax-ml/jax · error · ValueError

The symbolic constraints should be a sequence of strings. Go

Error message

The symbolic constraints should be a sequence of strings. Got {repr(constraints_str)}

What it means

SymbolicScope (created implicitly by polymorphic_shapes with constraints) expects a sequence of constraint strings like ('n >= 4',). Passing a single string would be iterated character-by-character, so JAX rejects it explicitly with this ValueError.

Source

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

  All symbolic expressions that interact (e.g., appear in the argument shapes
  for one JAX function invocation, or are involved in arithmetic operations)
  must be from the same scope and must share the same SymbolicScope object.

  Holds the constraints on symbolic expressions.

  See [the README](https://docs.jax.dev/en/latest/export/shape_poly.html#user-specified-symbolic-constraints)
  for more details.

  Args:
    constraints_str: A sequence of constraints on symbolic dimension expressions,
      of the form `e1 >= e2` or `e1 <= e2` or `e1 == e2`.
  """

  def __init__(self,
               constraints_str: Sequence[str] = ()):
    if isinstance(constraints_str, str):
      raise ValueError(
          "The symbolic constraints should be a sequence of strings. "
          f"Got {repr(constraints_str)}")
    self._initialized = False
    self._location_frame = source_info_util.user_frame(
        source_info_util.current().traceback)
    # Keep the explicit constraints in the order in which they were added
    self._explicit_constraints: list[_SymbolicConstraint] = []

    # We cache the _DimExpr.bounds calls. The result depends only on the
    # explicit and implicit constraints, so it is safe to keep it in the
    # scope. Set the cache before we parse constraints. We also keep the
    # bounds precision with which we computed the cached result.
    self._bounds_cache: dict[_DimExpr,
                             tuple[float, float, BoundsPrecision]] = {}

    # We store here a decision procedure state initialized with all the
    # _explicit_constraints.
    self._decision_initial_state: Any | None = None

View on GitHub (pinned to 1e1c6a8fc0)

Solutions

  1. Wrap the constraint(s) in a list or tuple: constraints=('n >= 4',) or ['a == b*2']
  2. Check for accidental string concatenation producing a single str
  3. Verify the parameter type at the call site before passing

Example fix

# before
scope = jax.export.shape_poly.SymbolicScope('n >= 4')
# after
scope = jax.export.shape_poly.SymbolicScope(('n >= 4',))
Defensive patterns

Strategy: validation

Validate before calling

assert not isinstance(constraints, str), 'pass a sequence of strings, e.g. ("n >= 4",)'

Type guard

def valid_constraints(c) -> bool:
    return not isinstance(c, str) and all(isinstance(s, str) for s in c)

Try / catch

try:
    scope = SymbolicScope(constraints)
except ValueError as e:
    if 'sequence of strings' in str(e): scope = SymbolicScope((constraints,))

Prevention

When it happens

Trigger: Passing constraints='n>=4' (a bare string) instead of a tuple/list ['n>=4'] anywhere constraints are accepted (SymbolicScope(...), shapes with constraints, jax.export APIs).

Common situations: Copy-paste from docs where a tuple lost its parentheses; refactoring that turned a list into an optional string.

Related errors


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