Skip to content

fix(parser): reject constraint bounds that depend on v/x/u/t (#343) - #345

Merged
ocots merged 2 commits into
chore/deps-examodels-0.12from
fix/constraint-bound-effective-343
Aug 31, 2026
Merged

fix(parser): reject constraint bounds that depend on v/x/u/t (#343)#345
ocots merged 2 commits into
chore/deps-examodels-0.12from
fix/constraint-bound-effective-343

Conversation

@ocots

@ocots ocots commented Aug 31, 2026

Copy link
Copy Markdown
Member

Closes #343.

Stacked on #326 — this PR targets chore/deps-examodels-0.12 so it can be reviewed and adjusted independently.

Problem

A boundary constraint whose bound references the optimization variable, e.g. x₂(0) == v, was correctly rejected but with an internal gensym leaking into the message:

Line 7: x₂(0) == v
UndefVarError: `v##286` not defined in `Main`

Root cause: in parse! aliases are substituted first, so v becomes v##NNNN[1]. x₂(0) == v calls p_constraint!(p, p_ocp, e2, e1, e2) — the bounds e1/e3 are v##NNNN[1] — and p_constraint_fun! emits constraint!(ocp, :boundary; f=fun, lb=(v##NNNN[1]), ub=(v##NNNN[1]), …). lb/ub are evaluated once at build time, but the gensym is only a function-argument name (for the :fun backend it is never bound at all), hence the UndefVarError. p_constraint! never validated the bounds.

This matches the documented rule "constraint bounds must be effective, that is must not depend on a variable".

Change

p_constraint! now checks both bounds and, if either depends on v, the state, the control or the time, returns a CTBase.ParsingError naming the real cause:

the lower bound of a constraint must not depend on the variable; write a functional
constraint instead by moving the term to the constrained side (e.g. `x₂(0) - v == 0`
rather than `x₂(0) == v`)

The check is backend-agnostic (before the :fun / :exa dispatch), so v == x₂(0) and x₁(0) ≤ x₂(0) are caught too. The constrained side may still reference the variable (e.g. 0 ≤ r(0) - z ≤ 1) — only the bounds are restricted.

Public API

No change. An input that used to throw UndefVarError now throws CTBase.ParsingError; nothing that used to build changes.

Tests

  • test/test_onepass_fun.jl: two @test_throws ParsingError cases plus a positive check that the documented work-around x₂(0) - v == 0 still builds.
  • test/test_onepass_exa.jl: one matching @test_throws ParsingError for the :exa path.
  • Full suite green locally: 2599 pass, 9 broken (pre-existing), 0 fail/error.

🤖 Generated with Claude Code

A boundary/path constraint whose bound referenced the optimization
variable (e.g. `x₂(0) == v`) failed with a leaked internal gensym
`UndefVarError: v##NNNN` instead of a clear error, because `lb`/`ub`
are evaluated once at build time and cannot see a function-argument
name.

`p_constraint!` now checks both bounds and returns a `ParsingError`
pointing to the fix (`x₂(0) - v == 0`). Backend-agnostic: covers both
`:fun` and `:exa`.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@ocots
ocots merged commit 805e79d into chore/deps-examodels-0.12 Aug 31, 2026
5 checks passed
@ocots
ocots deleted the fix/constraint-bound-effective-343 branch August 31, 2026 18:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants