Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
82d1b5f
deps: raise CTBase to 0.29, CTModels to 0.18, ExaModels to 0.12
ocots Aug 25, 2026
c5d425d
feat(exa)!: emit the ExaModels 0.12 functional builder API
ocots Aug 25, 2026
f8a0fda
fix(exa): carry ExaModels' unregistered linear-algebra extension
ocots Aug 25, 2026
6139789
ci: replace the retired kkt runner with occidata, rename labels to "r…
ocots Aug 25, 2026
cb2d573
fix(exceptions)!: throw CTException subtypes instead of bare String
ocots Aug 25, 2026
ad4b988
test: qualify the ExaModels import to drop the constraint name clash
ocots Aug 25, 2026
e185d6e
chore(release): 0.9.0, with CHANGELOG and BREAKING
ocots Aug 25, 2026
f02aedc
test: load CUDSS, which MadNLPGPU 0.10 no longer pulls in
ocots Aug 25, 2026
dcf4995
test: qualify PreconditionError for consistency with IncorrectArgument
ocots Aug 25, 2026
28562bb
beta version
ocots Aug 26, 2026
ef0a290
test: loosen use case 8's atol on GPU, tightened past the solver's ow…
ocots Aug 26, 2026
6b2a5dc
fix: avoid misleading parser lines for structured errors
ocots Aug 27, 2026
edb51ec
chore(release): prepare 0.9.1-beta
ocots Aug 27, 2026
94351c5
test: detect the kkt/occidata GPU runners and skip visibly
ocots Aug 29, 2026
faa3f20
chore(release): prepare 0.9.2-beta
ocots Aug 29, 2026
985e6ea
chore(release): prepare 0.9.3-beta
ocots Aug 30, 2026
adb0477
chore(release): prepare 0.9.4-beta with CTBase 0.30 support
ocots Aug 30, 2026
47765d4
fix(parser): reject constraint bounds that depend on v/x/u/t (#343)
ocots Aug 31, 2026
aa6684a
fix(parser): print the trace once in @def trace mode (#344)
ocots Aug 31, 2026
5628d78
Merge pull request #347 from control-toolbox/fix/def-trace-double-pri…
ocots Aug 31, 2026
1bddecd
Merge branch 'chore/deps-examodels-0.12' into fix/constraint-bound-ef…
ocots Aug 31, 2026
805e79d
Merge pull request #345 from control-toolbox/fix/constraint-bound-eff…
ocots Aug 31, 2026
5db77ee
chore(release): prepare 0.9.5-beta
ocots Aug 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,16 @@ on:

jobs:
# Job for the GitHub hosted runners (ubuntu, macos, windows)
test-github-runner:
test-cpu-github:
# A 'labeled' event fires once per label added, and re-evaluates against the PR's
# *current* full label set — so adding several labels in a row would otherwise
# re-run this job on every subsequent label add. Only react to 'labeled' when it's
# this specific label; for other trigger types (synchronize/reopened), fall back to
# checking the current label set as before.
if: >
github.event_name != 'pull_request' ||
(github.event.action == 'labeled' && github.event.label.name == 'github-runner') ||
(github.event.action != 'labeled' && contains(github.event.pull_request.labels.*.name, 'github-runner'))
(github.event.action == 'labeled' && github.event.label.name == 'run ci github-runner') ||
(github.event.action != 'labeled' && contains(github.event.pull_request.labels.*.name, 'run ci github-runner'))
uses: control-toolbox/CTActions/.github/workflows/ci.yml@main
with:
runs_on: '["ubuntu-latest", "macos-latest", "windows-latest"]'
Expand All @@ -28,17 +28,17 @@ jobs:
secrets:
SSH_KEY: ${{ secrets.SSH_KEY }}

# Job for the self-hosted runner kkt (GPU/CUDA)
test-kkt-runner:
# See the comment on test-github-runner above.
# Job for the self-hosted runner occidata (GPU/CUDA)
test-gpu-occidata:
# See the comment on test-cpu-github above.
if: >
github.event_name != 'pull_request' ||
(github.event.action == 'labeled' && github.event.label.name == 'kkt-runner') ||
(github.event.action != 'labeled' && contains(github.event.pull_request.labels.*.name, 'kkt-runner'))
(github.event.action == 'labeled' && github.event.label.name == 'run ci occidata-runner') ||
(github.event.action != 'labeled' && contains(github.event.pull_request.labels.*.name, 'run ci occidata-runner'))
uses: control-toolbox/CTActions/.github/workflows/ci.yml@main
with:
versions: '["1"]'
runs_on: '[["kkt"]]'
runs_on: '[["occidata"]]'
runner_type: 'self-hosted'
use_ct_registry: true
secrets:
Expand Down
11 changes: 10 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,23 @@ Quick-reference for any agent working on this repository.

## Repository Layout

CTParser is a single flat module — no submodule split, no `ext/`.
CTParser is a single flat module — no submodule split.

```text
src/ # onepass.jl (the `@def` parser), defaults.jl, initial_guess.jl, utils.jl
ext/ # CTParserExaModels.jl only — see below
test/ # Test suite: flat files (not test/suite/)
docs/ # Documentation site (DocumenterVitepress)
```

`ext/` holds exactly one extension, and it is **temporary**. ExaModels ships the
linear-algebra glue its expression nodes need (`dot`, `*` on node vectors/matrices,
`det`, `norm`, `Null` zero elimination) but never declares that extension in its
`[extensions]`, so Julia never loads it. Until upstream wires it up, CTParser carries
a port of it, triggered by `ExaModels` + `LinearAlgebra` weak dependencies. Delete
the file and the two weakdeps when upstream fixes it — see
[#325](https://github.com/control-toolbox/CTParser.jl/issues/325).

---

## Developer Resources
Expand Down
153 changes: 153 additions & 0 deletions BREAKING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
<!-- markdownlint-disable MD024 -->
# Breaking Changes

Breaking changes in CTParser releases, and how to migrate. Tracked from the 0.8.15
baseline onward; see [CHANGELOG.md](CHANGELOG.md) for the full record.

## [0.9.5-beta] - 2026-08-31

No breaking changes in this release.

- Constraint bounds that depend on `v`, the state, the control or the time
([#343](https://github.com/control-toolbox/CTParser.jl/issues/343)) are now rejected
with a `CTBase.ParsingError` naming the cause. Such definitions already failed to
build — previously with an internal `UndefVarError` leaked from generated code — so no
working `@def` changes. Rewrite the relation as a functional constraint, e.g.
`x₂(0) - v == 0` instead of `x₂(0) == v`.
- Trace mode (`@def name … end true`) prints the parsed model once instead of twice
([#344](https://github.com/control-toolbox/CTParser.jl/issues/344)). Trace output only;
the returned model and the public API are unchanged.

## [0.9.3-beta] - 2026-08-30

No breaking changes in this release.

- Documentation-only fix ([#341](https://github.com/control-toolbox/CTParser.jl/issues/341)):
examples in docstrings now use static `julia` fences so they render correctly when
transcluded into consumer documentation. Runtime behavior and the public API are unchanged.

## [0.9.2-beta] - 2026-08-28

No breaking changes in this release.

- Test-suite and release metadata only ([#339](https://github.com/control-toolbox/CTParser.jl/issues/339)):
GPU runner capability detection now recognises both the `kkt` and `occidata`
self-hosted runners, the GPU test tiers skip visibly with `Test.@test_skip` on
CPU runners, and a new `test/test_environment_contract.jl` enforces both. `src/`,
`ext/` and `.github/workflows/CI.yml` are unchanged.

## [0.9.1-beta] - 2026-08-26

No breaking changes in this release.

- Structured CTBase errors no longer print an unrelated `Line n: ...` message on
`stdout`; their existing structured diagnostics and exception types are unchanged.

## [0.9.0] - 2026-08-25

### The `:exa` emission requires ExaModels ≥ 0.12

ExaModels 0.12 deleted `src/deprecated.jl` and with it the mutable builder API —
`variable`, `parameter`, `subexpr`, `constraint!`, `LegacyExaCore`. The code `@def`
generates for `backend = :exa` now uses the functional API instead, where `add_var` /
`add_con` / `add_obj` return `(new_core, result)` and the core is threaded through the
build.

**Who is affected**: anyone pinning ExaModels below 0.12 while using the `:exa` backend.
Nothing else — `@def` problem definitions, the `:fun` backend, and the shape of the
`(model, getter)` pair `build_examodel` returns are all unchanged.

There is no compatibility shim, and adding one is not practical: CTParser does not
depend on ExaModels (the module is reached through `prefix_exa()`), so it cannot detect
the version at macro-expansion time and pick an emission.

**Migration**: raise your ExaModels bound.

```toml
# Before
ExaModels = "0.9"

# After
ExaModels = "0.12"
```

For reference, what changed in the generated code:

```julia
# Before — emitted against ExaModels 0.9–0.11
c = ExaModels.ExaCore(base_type; backend, minimize)
x = ExaModels.variable(c, n, 0:grid_size; lvar, uvar, start)
ExaModels.constraint(c, expr for j in 0:grid_size-1; lcon, ucon)
ExaModels.objective(c, expr for j in 0:grid_size-1)

# After — emitted against ExaModels 0.12
c = ExaModels.ExaCore(base_type; backend, minimize) # unchanged
c, x = ExaModels.add_var(c, n, 0:grid_size; lvar, uvar, start)
c, _ = ExaModels.add_con(c, expr for j in 0:grid_size-1; lcon, ucon)
c, _ = ExaModels.add_obj(c, expr for j in 0:grid_size-1)
```

### Untyped `String` errors are now `CTException` subtypes

Seventeen sites in `src/onepass.jl` threw a bare `String`. Callers could not dispatch on
them, and `showerror` fell back to `show`, rendering the message wrapped in quotes rather
than the formatted block every other error in the ecosystem produces.

**Who is affected**: code that catches these errors and compares the caught value to a
string, or that matches on `String`.

| what | type now |
| --- | --- |
| unknown numerical scheme | `CTBase.IncorrectArgument` |
| lower/upper bound length mismatch | `CTBase.PreconditionError` |
| bound lengths vs. the constrained range | `CTBase.PreconditionError` |
| unknown value for the getter's `val` keyword | `CTBase.IncorrectArgument` |
| unknown parsing backend | `CTBase.IncorrectArgument` |
| `:fun` cannot be activated or deactivated | `CTBase.PreconditionError` |

The split follows the Handbook's rule: a single argument's value out of domain is
`IncorrectArgument`; a relational, state or timing contract is `PreconditionError`. A
bound-length mismatch relates two things — the bounds to each other, or to the
constrained range — hence `PreconditionError`. `:fun` is a perfectly valid backend name;
what is forbidden is toggling it, which is a state contract, not a bad value.

**Migration**:

```julia
# Before
try
solve(ocp, :exa; scheme=:gauss_legendre_2)
catch e
e == "unknown numerical scheme: gauss_legendre_2 (possible choices are ...)"
end

# After
try
solve(ocp, :exa; scheme=:gauss_legendre_2)
catch e
e isa CTBase.IncorrectArgument
end
```

`@test_throws String` in a downstream test suite becomes `@test_throws
CTBase.IncorrectArgument` or `@test_throws CTBase.PreconditionError`, per the table.

## Non-breaking note (0.9.0)

- **New `CTParserExaModels` extension.** Additive, and triggered only when both
`ExaModels` and `LinearAlgebra` are loaded — the main module gains no dependency. It
restores the linear algebra on ExaModels expression nodes that a dynamics like
`∂(x)(t) == A * x(t) + B * u(t)` needs, which ExaModels 0.12 ships but never registers
in its own `[extensions]`. **No migration required.** It is temporary: when upstream
wires its extension up ([madsuite-org/ExaModels.jl#323](https://github.com/madsuite-org/ExaModels.jl/issues/323)),
the file and the two weak dependencies go away, with no user-visible change either way.

- **Compat bounds** raised for CTBase (`0.29`), CTModels (`0.18`), OrderedCollections
(`1, 2`) and Parameters (`0.13`), and widened for CUDA (`5, 6`), MadNLP (`0.9, 0.10`)
and MadNLPGPU (`0.8, 0.10`). **No breaking change**: none of these required a source
change, and the `:fun` test groups stayed green at 1054/1054 across the bump.

## [0.8.15] - 2026-04-21 — baseline

Reference version. Breaking changes are tracked from here onward; use `git log` for
earlier history.
Loading
Loading