Align with CTBase 0.29 / CTModels 0.18 / ExaModels 0.12 - #326
Conversation
Align CTParser's compat bounds with the released ecosystem: CTBase 0.29.3,
CTModels 0.18.0, CTSolvers 0.5.3 and CTFlows 0.17.2.
Verified in the real suite: the :fun groups (test_onepass_fun, _bis, utils,
initial_guess) stay green at 1054/1054 against CTBase 0.29.3 + CTModels 0.18.0 +
OrderedCollections 2.0.1, so these three bumps need no source change. CTBase 0.29
dropped its top-level exports, but its submodules are `using`-ed inside CTBase, so
the two symbols src/ uses -- ctindices and ctupperscripts -- still resolve, as does
ParsingError, which generated code reaches through e_prefix.
The ExaModels bump does NOT stand on its own: 0.12 deleted the mutable builder API
that def_exa emits, so every :exa group now fails with
UndefVarError: `variable` not defined in `ExaModels`
That is deliberate at this commit -- it reproduces the breakage inside the real
suite, and the next commit migrates the emission to the functional builder API.
CUDA, MadNLP and MadNLPGPU keep their lower bound, mirroring CTSolvers 0.5.3, so
the GitHub-hosted runners are not forced onto a CUDA 6 resolve.
Refs #325
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ExaModels 0.12 deleted the mutable builder API that def_exa emitted: variable,
parameter, subexpr, constraint! and LegacyExaCore are gone with src/deprecated.jl.
The replacement is functional -- add_var/add_con/add_obj each return
(new_core, result) -- so the generated code now threads and rebinds the core.
23 emission sites in src/onepass.jl:
p_variable_exa! / p_state_exa! / p_control_exa! variable -> add_var 3
p_constraint_exa! constraint -> add_con 6
p_dynamics_exa! constraint -> add_con 4
p_dynamics_coord_exa! constraint -> add_con 4
p_lagrange_exa! objective -> add_obj 5
p_mayer_exa! objective -> add_obj 1
The five constraint! calls in p_constraint_fun! are CTModels', not ExaModels', and
do not move.
Three emission shapes needed care, each validated against ExaModels 0.12 before
being written:
- value used: the binding stays outside __wrap's try, as the existing comment
requires, while the core is rebound inside it -- ($p_ocp, $x) = try ... end.
Reassigning an existing outer local from inside try/catch is visible outside;
only new declarations are not.
- per-scheme dynamics: the `if` is kept and each branch returns add_con's pair,
so ($p_ocp, $(p.dyn_con)[$i]) = if scheme == ... end destructures both at once,
rebinding the core on every loop iteration.
- per-scheme Lagrange: the rebinding moves *inside* each branch, because the
trapeze branch adds two objectives and the `if`'s own value is unused.
ExaCore(base_type; backend, minimize) is left exactly as it was. Under 0.12 it no
longer warns -- the deprecation shim is gone -- so no `concrete` keyword is passed.
That is deliberate: with the 0.12 default (Vector{Any} block storage) typeof(core)
is invariant across every add_*, so rebinding is free, whereas concrete = Val(true)
changes the core's type on each add_* and would recompile the builder per block.
test/test_exa_linalg.jl builds an ExaCore directly and moves the same way.
Results: test_control_zero 37/37, test_onepass_exa_bis 176/176, test_onepass_exa
564 pass / 12 errors, test_dynamics_exa 96 pass / 4 errors, test_exa_linalg
136 pass / 20 fail / 103 errors.
Every one of those remaining failures has a single cause, unrelated to this commit:
ExaModels ships ext/ExaModelsOptimalControl.jl -- the successor to 0.9's
ExaModelsLinearAlgebra -- but never registers it in [extensions], so Julia never
loads it and the node linear-algebra glue (dot, convert, zero, scalar x vector) is
absent. Verified that the shipped file still works verbatim against 0.12 once
loaded by hand. Handled next.
Refs #325
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ExaModels ships ext/ExaModelsOptimalControl.jl -- the successor to 0.9's ExaModelsLinearAlgebra -- but never declares it in [extensions], so Julia never loads it. Everything it provides is unreachable: dot, * on node vectors and matrices, det, norm, tr, diag, convert/promote_rule/zero/one for AbstractNode, and the Null zero/one elimination. LinearAlgebra is still in ExaModels' [weakdeps] with nothing referencing it, and OptimalControl -- the trigger the rename implies -- is not in [weakdeps] at all. True of main as well as of the 0.12.0 release. That is not cosmetic here. It breaks a real @def feature: a dynamics written as ∂(x)(t) == A * x(t) + B * u(t), or an objective using dot(q, x(t)), applies those operators to arrays of ExaModels nodes while the model is being built. The shipped file still works verbatim against 0.12 once loaded by hand, so this carries a port of it as ext/CTParserExaModels.jl, triggered by ExaModels + LinearAlgebra weak dependencies. CTParser is the package that *emits* the ExaModels code, so it is the natural owner of that contract until upstream wires its own extension up. Three things the port had to get right: - ExaModels 0.12's core defines node arithmetic only generically, on AbstractNode. There is no Null-specific method anywhere in it -- `hasmethod` says otherwise only because Null <: AbstractNode. So every Null overload here is strictly more specific and overwrites nothing, and skipping them is not an option: without the zero/one elimination test_exa_linalg sits at 381/485. - Upstream's Section F (ExaModels.add_con(core, ::AbstractVector)) is not ported. It is broken as written -- it starts from c1 = nothing and calls the removed ExaModels.constraint on it -- and p_constraint_exa! never emits the vector form anyway, it loops over components. - Folding structural zeros to Null lets both operands of a second-order adjoint pass be SecondAdjointNull at once, which reaches a genuine ambiguity in ExaModels' own src/simdfunction.jl:142-143: _hdrpass_val has methods for (<:SecondAdjointNull, ::Type) and (::Type, <:SecondAdjointNull) but none for the intersection. Both return Val(0), so the missing value is forced. Defined here, with the reasoning in a comment; it hits second derivatives of a dot-written dynamics under the trapeze scheme. Aqua needs no exemption: Aqua.test_all(CTParser) inspects the package module, not its extensions, so piracies=true stays on unchanged and still passes 11/11. CLAUDE.md and AGENTS.md said "no ext/"; both now describe the one extension and the condition for deleting it. All :exa groups green: test_onepass_exa 596/596, test_onepass_exa_bis 176/176, test_control_zero 37/37, test_dynamics_exa 100/100, test_exa_linalg 491/491. Upstream question (intended direction, not a patch): madsuite-org/ExaModels.jl#323. Refs #325 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…un ci ..." The self-hosted kkt runner no longer exists, so its job could never be scheduled. Replaced by occidata, following CTFlows.jl, which already runs both. Labels renamed to the "run ci <target>" form used across the ecosystem, so the CI triggers group together in the label list instead of scattering among the topic labels: github-runner -> run ci github-runner kkt-runner -> run ci occidata-runner Jobs renamed to match CTFlows too (test-cpu-github, test-gpu-occidata), which says what runs where rather than only which runner it lands on. The label-gating logic is unchanged, including the `github.event.label.name` guard on the 'labeled' branch that keeps an unrelated label from re-triggering CI. Labels created and the obsolete pair deleted on the repository. Refs #325 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Seventeen sites threw a bare String, so typeof(e) === String: callers could not dispatch on them, and showerror fell back to show, rendering the message wrapped in quotes instead of the Reason / Context / Hint block every other error in the ecosystem produces. Typed by the Handbook's choice rule -- a single argument's value out of domain is IncorrectArgument, a relational/state/timing contract is PreconditionError: unknown numerical scheme 3 sites IncorrectArgument lower/upper bound length mismatch 2 sites PreconditionError bound lengths vs the constrained range 5 sites PreconditionError unknown value for the getter's val kwarg 1 site IncorrectArgument unknown parsing backend 4 sites IncorrectArgument :fun cannot be activated or deactivated 2 sites PreconditionError Two of those took an actual call. A bound-length mismatch relates two things -- the bounds to each other, or to the constrained range -- which is the Handbook's own heuristic for PreconditionError, not IncorrectArgument. And ':fun' is a perfectly valid backend name: what is forbidden is toggling it, a state contract rather than a bad value, so it is PreconditionError while an unrecognised backend name is IncorrectArgument. Each throw carries got/expected (or reason) and a suggestion; the bound errors build their reason at run time from the actual lengths, which the old single-line message never reported. Generated code goes through the existing e_prefix (:CTBase), so the four emitters that needed it now bind e_pref alongside pref. The plain runtime functions (activate_backend, deactivate_backend, is_active_backend, parsing) call CTBase directly. The comment claiming __throw had to be avoided here was right about __throw -- it builds a macro-expansion-time expression -- but said nothing about the thrown object's type: __wrap rethrows whatever it caught, so the type is preserved either way. Reworded. The 19 @test_throws String assertions now assert the concrete type. Fixes #322. Refs #325 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
test/runtests.jl did a bare `using ExaModels` while `constraint` was already imported
from CTModels, so ExaModels' exported `constraint` landed in Main on top of it and every
run opened with
WARNING: using ExaModels.constraint in module Main conflicts with an existing
identifier.
Still true under ExaModels 0.12, where `constraint` remains exported for the oracle
form. Now a qualified `using ExaModels: ExaModels`, which is also Handbook tenet 2. No
call site needed changing: the test files already write ExaModels.x throughout.
Full suite: 2580/2580, and the warning is gone from the log.
Fixes #230. Refs #325
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Leaves the beta series behind and aligns CTParser with the released ecosystem: CTBase 0.29.3, CTModels 0.18.0, CTSolvers 0.5.3, CTFlows 0.17.2. 0.9.0 rather than 0.8.18-beta because two public API changes land here: the :exa emission now requires ExaModels >= 0.12 (no shim is possible -- CTParser does not depend on ExaModels, so it cannot detect the version at macro-expansion time), and errors that used to be bare Strings are now CTException subtypes. The repository had neither CHANGELOG.md nor BREAKING.md, which the Handbook requires of every package. Created with its retroactive bootstrap: a baseline entry for v0.8.15 (2026-04-21, the last non-beta tag) pointing at git log for earlier history, then the full 0.9.0 entry. Both breaking changes appear in both files with # Before / # After migration blocks, and BREAKING.md carries non-breaking notes for the new extension and the compat bumps. Full suite green at 2580/2580 across all 13 files, no errors, no failures, and no name-clash warning. Docs build clean; the remaining warnings are pre-existing undocumented internals. Refs #325 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The occidata GPU job failed with 48 errors, all the same:
MadNLPGPU: cannot build a GPU sparse KKT system because the GPU backend extension
is not loaded. For CUDA, the extension activates only once both the CUDA backend
and CUDSS are loaded -- add `using CUDSS` ... before solving a model with GPU
arrays.
Nothing to do with the ExaModels migration. MadNLPGPU moved CUDSS from [deps] to
[weakdeps] between 0.8 and 0.10: its CUDA extension now triggers on
["CUDACore", "CUDSS", "cuBLAS", "cuSOLVER", "cuSPARSE"], so CUDSS stopped arriving
transitively and the consumer has to load it. Added to test/Project.toml and to the
runner, with the compat range CTSolvers 0.5.3 already uses.
There was a second effect worth recording. With CUDSS absent, nothing constrained
GPUToolbox, so it resolved to 3.0.0 and dragged CUDA to 6.3.0. CUDSS 0.6+ declares
GPUToolbox = ["0.3", "1"], so simply adding CUDSS to a manifest already pinned that way
is unsatisfiable. Resolving the test environment from scratch settles on CUDA 6.2.0 +
CUDSS 0.8.0 + GPUToolbox 1.1.1, which is consistent -- so the CUDA = "5, 6" bound stays
as it is; it was never the problem.
Invisible locally: CUDA.functional() is false on a CPU-only machine, so every GPU path
is skipped and the CPU suite passed 2580/2580 without ever touching this. `using CUDSS`
itself loads fine without a GPU, so the GitHub-hosted runners are unaffected.
Refs #325
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Phase C commit typed 11 assertions across these four files: some as CTBase.IncorrectArgument (never imported bare, so qualification was mandatory) and some as a bare PreconditionError, which happens to resolve because runtests.jl still does `import CTBase: CTBase, ParsingError, PreconditionError`. Two names for the same kind of thing read inconsistently side by side. Qualified every @test_throws PreconditionError in the four files to CTBase.PreconditionError, including three sites the Phase C commit did not touch (the pre-existing @def-detects-a-precondition-violation assertions in test_onepass_fun.jl) -- purely cosmetic there, since bare PreconditionError already meant CTBase.PreconditionError via the same import; qualifying it changes nothing at run time. Re-ran all four groups: 1452/1452. Refs #325 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
@jbcaillau please review. |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #326 +/- ##
==========================================
- Coverage 98.22% 95.71% -2.52%
==========================================
Files 4 5 +1
Lines 1073 1307 +234
==========================================
+ Hits 1054 1251 +197
- Misses 19 56 +37 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
…n tolerance The v0.9.0-beta tag push (triggered by main's on.push.tags CI trigger) exercised the occidata GPU job on a commit one line away from a fully green run (dcf4995 -> 28562bb, only Project.toml's version string changed), and turned up one failure: use case no. 8: vectorised dynamics (GPU, midpoint): Test Failed Expression: ≈(obj1 - obj2, 0, atol = __atol) Evaluated: 5.722384364137412e-7 ≈ 0 (atol=1.0e-9) Not a regression from this branch. The test dates from 2025-12-21 (f5ab71f, git blame), long before this PR, and it compares the objective of two INDEPENDENTLY converged MadNLP solves -- a vectorised formulation built from A[i,:]' * x(t) dot products, and a hand-unrolled scalar one -- at atol=1e-9, which is already tighter than the solver's own convergence tolerance (`tol=tolerance`, 1e-8 by default). Two separate solves are only mathematically guaranteed to agree to the solver's own tolerance, not machine precision; CPU happened to satisfy 1e-9 anyway because its floating-point summation order is deterministic and matches between runs, while GPU's parallel reduction order does not. There is already a precedent for this exact adjustment in the same file (line ~1900, `__atol = 1e-3 # otherwise would just work for midpoint`) for the same class of comparison. occidata had never reached this test before: the job was first blocked by the ExaModels 0.12 API break (Phase B), then by MadNLPGPU no longer pulling in CUDSS (f02aedc). This is the first CI run in which it ever ran to completion. __atol is now backend-dependent: unchanged at 1e-9 on CPU (verified: onepass_exa 596/596, identical to before this commit, since the ternary evaluates to the same branch), loosened to 1e-5 on GPU -- about 17x the observed 5.7e-7, comfortable margin without hiding an actual regression. Refs #325 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
The suite had no notion of which runner it was executing on. The two :exa GPU tiers were gated behind a bare short-circuit on CUDA.functional(), so a correctly-skipped run on a developer machine and a silently-broken one on a GPU runner produced the same output: a green run with the GPU tier absent. test/runtests.jl now holds the single capability module the Handbook asks for (philosophy/testing.md, "Capability-gated tests"), mirroring CTSolvers: CUDA_FUNCTIONAL, ON_GPU_RUNNER and GPU_SOLVER_ARMED. ON_GPU_RUNNER matches the kkt / occidata substring of RUNNER_NAME -- the self-hosted runners are registered as kkt-runner / occidata-runner, whereas the CI.yml runs_on label is the bare kkt / occidata -- so a missing device fails loudly on either. RUNNER_NAME is set by the GitHub Actions runner agent itself, so no CI.yml or CTActions change is needed. The two GPU tiers now branch to Test.@test_skip, showing as Broken in the summary, and test/test_environment_contract.jl enforces the contract: the MadNLPGPU/CUDSS extension must be armed on every runner, a device must be present on the GPU runners, and the silent-guard anti-pattern must not reappear under test/. GPU_SOLVER_ARMED uses isdefined rather than CTSolvers' `CUDSSSolver isa Type`: the symbol only exists once MadNLPGPUCUDAExt loads, and an UndefVarError at module load would abort the run instead of failing one assertion. Closes #339. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Test-suite and metadata only; src/ and ext/ are untouched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
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>
`@def name … end true` printed the parsed model twice whenever the `:exa` backend was active: `def_fun` re-parses the definition to build the ExaModels artifact and that second pass inherited the `log` flag, re-emitting the whole trace. The `:exa` sub-parse now runs with `log=false`; the `:fun` pass already produced the trace. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ective-343 fix(parser): reject constraint bounds that depend on v/x/u/t (#343)
Bundles the two parser fixes merged into this branch: - #343 — constraint bounds depending on v/x/u/t are rejected with a clear ParsingError instead of a leaked internal gensym - #344 — @def trace mode prints the parsed model once, not twice No breaking changes: #343 only affects inputs that already errored, #344 is trace-only output. CHANGELOG and BREAKING updated. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Added since the initial review — phases I–MBeyond the original ExaModels 0.12 / typed-errors / CI scope, this branch now also carries: I — 0.9.3-beta prep (
|
Closes #325. Closes #323. Closes #322. Closes #339. Closes #230. Closes #341. Closes #342. Closes #343. Closes #344.
Problem
CTSolvers 0.5.3 already declares
ExaModels = "0.12", but its extension only reads backend metadata. The package that actually emitsExaCore/variable/constraint/objectivecalls is CTParser, fromdef_exainsrc/onepass.jl— and ExaModels 0.12 deleted that mutable builder API (src/deprecated.jlis gone, along withvariable,parameter,subexpr,constraint!andLegacyExaCore).So CTSolvers' declared 0.12 support was nominal until this: any
:exasolve failed at run time, inside generated code, withUndefVarError: variable not defined in ExaModels. CTParser is the one package in the stack whose source had to change.Public API changes
1. The
:exaemission requires ExaModels ≥ 0.12Supporting both is not practical: CTParser does not depend on ExaModels (the module is reached through
prefix_exa()), so it cannot branch on the version at macro-expansion time.ExaCoreis called exactly as before. Under 0.12 it no longer warns — the deprecation shim is gone — so noconcretekeyword is passed. That is deliberate: with the 0.12 default (Vector{Any}block storage)typeof(core)is invariant across everyadd_*, so rebinding the core costs nothing, whereasconcrete = Val(true)changes the core's type on eachadd_*and would recompile the builder once per block.2. Untyped
Stringerrors becomeCTExceptionsubtypesSeventeen sites typed by the Handbook's choice rule — a single argument's value out of domain is
IncorrectArgument, a relational/state/timing contract isPreconditionError.Two of those took an actual call. A bound-length mismatch relates two things — the bounds to each other, or to the constrained range — which is the Handbook's own heuristic for
PreconditionError. And:funis a perfectly valid backend name: what is forbidden is toggling it, a state contract rather than a bad value, so that isPreconditionErrorwhile an unrecognised backend name isIncorrectArgument.An unplanned blocker, upstream
ExaModels ships
ext/ExaModelsOptimalControl.jl— the successor to 0.9'sExaModelsLinearAlgebra— but never declares it in[extensions], so Julia never loads it. Everything it provides is unreachable:dot,*on node vectors and matrices,det,norm,tr,diag,convert/promote_rule/zero/oneforAbstractNode, and theNullzero/one elimination.LinearAlgebrais still in ExaModels'[weakdeps]with nothing referencing it, andOptimalControl— the trigger the rename implies — is not in[weakdeps]at all. True ofmainas well as of the 0.12.0 release.That is not cosmetic: it breaks a real
@deffeature. A dynamics written as∂(x)(t) == A * x(t) + B * u(t), or an objective usingdot(q, x(t)), applies those operators to arrays of ExaModels nodes while the model is being built.The shipped file still works verbatim against 0.12 once loaded by hand, so this PR carries a port of it as
ext/CTParserExaModels.jl, triggered byExaModels+LinearAlgebraweak dependencies. CTParser is the package that emits the ExaModels code, so it is the natural owner of that contract until upstream wires its own extension up. Upstream question — asked as "what is the intended direction?" rather than as a patch: madsuite-org/ExaModels.jl#323.Two details from the port worth knowing about: upstream's Section F (
ExaModels.add_con(core, ::AbstractVector)) is broken as written and is not ported, and enabling theNullzero elimination surfaces a genuine ambiguity in ExaModels' ownsrc/simdfunction.jl:142-143that is worked around here, with the reasoning in a comment.GPU runner capability detection (#339)
The CI phase above swapped the retired
kktrunner foroccidata, but the suite still had no notion of which runner it was executing on. Both:exaGPU tiers were gated behind a bare short-circuit onCUDA.functional(), so a correctly-skipped run on a CPU laptop and a silently-broken one onoccidata— device present but not functional — produced the identical output: a green run with the GPU tier simply absent. That is the anti-pattern the Handbook'sphilosophy/testing.md§"Capability-gated tests" forbids.test/runtests.jlnow carries the single capability module the Handbook asks for, aligned with CTSolvers (its #189 / #217):The substring match is deliberate:
RUNNER_NAMEis set by the GitHub Actions runner agent itself — noCI.ymlor CTActions change needed — to the runner's registered name, and ours are registered askkt-runner/occidata-runner, whereas theCI.ymlruns_onlabel is the barekkt/occidata. Detection covers both runners per the issue, even though onlyoccidatais a live target today.GPU_SOLVER_ARMEDdiverges from CTSolvers'MadNLPGPU.CUDSSSolver isa Typeon purpose: the symbol only exists onceMadNLPGPUCUDAExtloads, so that form would throwUndefVarErrorat module load and abort the whole run instead of failing one assertion. It asserts the same thing.The two GPU tiers now branch to
Test.@test_skip, and a newtest/test_environment_contract.jlenforces the contract centrally: the MadNLPGPU/CUDSS extension must be armed on every runner (this is what catches the CUDSS wiring regression), a device must be present onkkt/occidata, and the silent-guard anti-pattern must not reappear anywhere undertest/. CTSolvers' companionisdefined(Main, ...)audit is not ported — it exists because every CTSolvers suite file is wrapped in its own module, whereas CTParser's tests are a mix of module-wrapped and flat files included straight intoMain, where the idiom is legitimate.Verified by faking the runner on a CPU box, which is the only way to exercise the loud-failure path locally:
The audit proved itself the same way, unprompted: its first run failed on the explanatory comments I had just written into the two test files, which spelled out the literal pattern. Reworded, green.
Phases
:fungroups green at 1054/1054;:exagroups red on purpose at that commit, reproducing the breakage inside the real suite.ExaCoreuse intest_exa_linalg.jl.throw(String)sites typed, and the 19@test_throws Stringassertions with them.test/runtests.jl([Dev] Check warning when using ExaModels (constraint) #230).CHANGELOG.md+BREAKING.mdcreated with the Handbook's retroactive bootstrap (baseline v0.8.15), version bumped to 0.9.0.kktself-hosted runner replaced byoccidata, trigger labels renamed to the ecosystem'srun ci <target>form.kktandoccidata, visibleTest.@test_skipon CPU runners, and thetest_environment_contract.jlmeta-test (Add consistent GPU runner capability detection for kkt and occidata #339).CHANGELOG.md+BREAKING.mdentries (no breaking changes: test-suite and metadata only).@def/@initdocstring examples moved from@exampleto staticjuliafences so they render when transcluded into consumer docs (@def/@init docstrings use ```@example fences, which Documenter never executes when transcluded into a consumer's @docs block #341).[compat]widened toCTBase = "0.29, 0.30"(Widen [compat] to admit CTBase 0.30 (needed for CTFlows 0.18's Makie backend, one level down from CTDirect#629) #342). No source change.v, the state, the control or the time is rejected with aCTBase.ParsingErrornaming the cause, instead of a leaked internal gensymUndefVarErrorfrom generated code. Backend-agnostic (:funand:exa). (x(0) == vwithvthe variable reportsUndefVarError:v##NNNNnot defined inMain`` instead of a clear message #343, merged as fix(parser): reject constraint bounds that depend on v/x/u/t (#343) #345)@def name … end truetrace mode prints the parsed model once: the:exare-parse insidedef_funno longer inherits thelogflag. (@def name … end true(trace mode) prints the parsed model twice #344, merged as fix(parser): print the trace once in @def trace mode (#344) #347)CHANGELOG.md+BREAKING.mdentries forx(0) == vwithvthe variable reportsUndefVarError:v##NNNNnot defined inMain`` instead of a clear message #343 /@def name … end true(trace mode) prints the parsed model twice #344 (no breaking changes:x(0) == vwithvthe variable reportsUndefVarError:v##NNNNnot defined inMain`` instead of a clear message #343 only affects inputs that already errored,@def name … end true(trace mode) prints the parsed model twice #344 is trace-only output).Test results
test_aqua.jlpiracies=trueunchanged)test_control_zero.jltest_dynamics_exa.jltest_environment_contract.jltest_exa_linalg.jltest_initial_guess.jltest_onepass_exa.jltest_onepass_exa_bis.jltest_onepass_fun.jltest_onepass_fun_bis.jltest_prefix.jltest_prefix_bis.jltest_utils.jltest_utils_bis.jlNo errors, no failures, and no
WARNING: … conflicts with an existing identifierin the log. CPU only locally — theoccidataGPU job runs on this PR via its label.The 9 skips are the point of #339: they are the 4 GPU scheme tiers per
:exatest file, plus the "device only required onkkt/occidata" skip, all now visible asBrokenin the summary instead of vanishing. Onoccidatathe first 8 become real runs and the ninth becomes an asserted pass. The total moved 2580 → 2592 for reasons mostly unrelated to #339: +2 from the new file, and +5 in each oftest_onepass_exa.jlandtest_onepass_fun_bis.jlfrom the #338 fix, which landed after the earlier table was written.Follow-up
ExaModels = "0.11"and consumes this generated code — it needs a matching bump.x(0) == vwithvthe variable reportsUndefVarError:v##NNNNnot defined inMain`` instead of a clear message #343 /@def name … end true(trace mode) prints the parsed model twice #344 parser fixes;Project.tomlis at0.9.5-beta.🤖 Generated with Claude Code