Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
b6b45f9
feat(imports)!: migrate to the restructured CTx stack
ocots Jul 29, 2026
ea8a83d
test(reexport): assert ownership, not mere definedness
ocots Jul 29, 2026
f1991f0
test(helpers,builders): re-point paths, consolidate CUDA checks
ocots Jul 29, 2026
32da9d1
test(solve): re-point paths, complete the strategy contract in mocks
ocots Jul 29, 2026
1d7ff7a
test(indirect)!: migrate to the new flow calling convention
ocots Jul 29, 2026
1353a02
test(problems): dual-form library, and declare direct/indirect capabi…
ocots Jul 29, 2026
8b08673
test: form equivalence, hamiltonian_type, flow API, shape contract
ocots Jul 29, 2026
b8aae58
test(gpu): CPU-runnable routing; docs: BREAKING.md and CHANGELOG for …
ocots Jul 29, 2026
00a0c99
add mis-
ocots Jul 29, 2026
5478639
test(gpu): require a functional device on the kkt runner
ocots Jul 29, 2026
b2e1550
feat(describe): merge solve + flow registries, describe() covers :di …
ocots Jul 29, 2026
02eb159
refactor(print): constrain _strategy_parameter to AbstractStrategy
ocots Jul 29, 2026
73ce076
feat(print): warn once per strategy type on missing parameter() override
ocots Jul 29, 2026
25f62f1
test(indirect): shooting derivations live with their problems
ocots Jul 30, 2026
4247ee5
feat(strategies): consume CTBase 0.28.8-beta — merge, describe, param…
ocots Jul 30, 2026
5a74a75
fix(flows)!: OpenLoop is unconditionally non-autonomous (CTBase#515)
ocots Jul 30, 2026
cc01e0d
ci: use ct-registry, add windows to the CPU matrix
ocots Jul 30, 2026
ba98381
test: guard @inferred kwarg-extraction checks behind Julia >=1.11
ocots Jul 30, 2026
bac33d1
ci: point at CTActions#67 to test the Windows Pkg/SSH fix
ocots Jul 30, 2026
a2c6ad1
ci: point back at CTActions main now that #67 is merged
ocots Jul 30, 2026
0e60467
test: guard print-performance alloc checks behind Julia >=1.11
ocots Jul 30, 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
8 changes: 3 additions & 5 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,9 @@ jobs:
(github.event.action != 'labeled' && contains(github.event.pull_request.labels.*.name, 'run ci cpu'))
uses: control-toolbox/CTActions/.github/workflows/ci.yml@main
with:
versions: '["1.12"]'
runs_on: '["ubuntu-latest", "macos-latest"]'
runs_on: '["ubuntu-latest", "macos-latest", "windows-latest"]'
runner_type: 'github'
use_ct_registry: false
use_ct_registry: true
secrets:
SSH_KEY: ${{ secrets.SSH_KEY }}

Expand All @@ -37,9 +36,8 @@ jobs:
(github.event.action != 'labeled' && contains(github.event.pull_request.labels.*.name, 'run ci gpu'))
uses: control-toolbox/CTActions/.github/workflows/ci.yml@main
with:
versions: '["1"]'
runs_on: '[["kkt"]]'
runner_type: 'self-hosted'
use_ct_registry: false
use_ct_registry: true
secrets:
SSH_KEY: ${{ secrets.SSH_KEY }}
137 changes: 137 additions & 0 deletions BREAKING.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,140 @@
# Breaking Changes: v2.0 → v2.1.0-beta

This section describes the breaking changes when migrating from **OptimalControl.jl v2.0.5-beta** to **v2.1.0-beta**. For the v1.x → v2.0 migration, see [the section below](#breaking-changes-v1x--v20).

Three symbol families physically changed package in this release, as the ecosystem was reorganised so that each name has exactly one owner. Most of the fallout is mechanical, but three items are semantic and will not announce themselves as import errors.

## Start here: `Flow` needs an integrator

Every example's preamble changes. SciML is no longer a hard dependency of the stack — the user chooses and loads an integrator:

```julia
using OptimalControl
using OrdinaryDiffEqTsit5 # ← new, and required before any Flow(...)

f = Flow(ocp, (x, p) -> p[2])
```

Without it, `Flow` fails with a bare `MethodError`. This is by design: it keeps the install cost of the direct path off users who never write a flow.

## Differential geometry moved to CTLie

`Lift`, `Poisson`, `∂ₜ` and `@Lie` are re-exported from the same names as before, so most code is unaffected. Two changes are not cosmetic:

| v2.0 | v2.1.0-beta | Notes |
| --- | --- | --- |
| `Lie(X, f)` | `ad(X, f)` | renamed |
| `X ⋅ f` | `ad(X, f)` | **removed**, no alias |
| `HamiltonianLift` | `CTLie.LiftedHamiltonianFunction` | renamed **and** re-parented |

`LiftedHamiltonianFunction` is `<: Function`, no longer `<: AbstractHamiltonian`. Any `isa` or `<:` test against the old hierarchy is now wrong:

```julia
H = Lift(F) # F::Function
H isa AbstractHamiltonian # false in v2.1.0-beta, true in v2.0
```

Note that `Lift` is overloaded on its input: `Lift(X::AbstractVectorField)` still returns a `Hamiltonian`. Only the plain-`Function` overload changed.

## Flow call convention

The signature changed, not just the spelling.

```julia
# before
f(t0, x0, p0, tf, λ)
f(t0, x0, p0, tf; augment=true)

# after
f(t0, x0, p0, tf; variable=λ)
f(t0, x0, p0, tf; variable=λ, variable_costate=true)
```

1. **There is no positional slot for the variable any more**, and `variable=` is **mandatory** on a `NonFixed` problem. Omitting it raises a `PreconditionError` whose suggestion is literally *"Pass `variable=v` when calling the flow"* — it does not silently default.
2. **`augment=true` → `variable_costate=true`.** It integrates the augmented adjoint `ṗᵥ = -∂H/∂v` and returns `(xf, pf, pvf)` instead of `(xf, pf)`.
3. **New `unsafe=false`.** With `unsafe=true` the ODE retcode is not checked and failures do not throw — useful inside a shooting loop, where an intermediate failure should surface through the residual.

## Constrained flows: keywords replace positional arguments

```julia
# before
fb = Flow(ocp, u, g, μ) # 3 positional

# after
fb = Flow(ocp, u; constraint=g, multiplier=μ) # paired keywords
```

The two are a pair: one without the other is an `IncorrectArgument`.

`constraint` now accepts three spellings, which is a capability gain rather than a rename — a plain `Function`, a `Data.PathConstraint`, **or a `Symbol` naming a `:path` constraint already declared in the OCP**:

```julia
fb = Flow(ocp, u; constraint=:vmax, multiplier=μ) # reuse the model's own constraint
```

## Constructor keywords take an `is_` prefix

```julia
# before
VectorField(f; autonomous=false, variable=true)
@Lie [X, Y] autonomous=false

# after
VectorField(f; is_autonomous=false, is_variable=true)
@Lie [X, Y] is_autonomous=false
```

The old spelling on `@Lie` raises an `IncorrectArgument` at macro-expansion time rather than being ignored.

## Two names are no longer re-exported

| Name | Why |
| --- | --- |
| `time` | It is `Base.time`, extended but not exported by `CTModels.Components`. Get it from `Base`. |
| `success` | `CTModels.Solutions` exports the name but defines no method for it, so `success(sol)` was always a `MethodError`. **Use `successful(sol)`**, which is the real accessor and is unchanged. |

## Newly re-exported

The full `CTBase.Data` type vocabulary is now available without reaching into the package by hand — `Flow` dispatches on these, so building a flow explicitly needed them:

`VectorField`, `Hamiltonian`, `HamiltonianVectorField`, `ComposedHamiltonian`, `PseudoHamiltonian`, `ControlLaw`, `OpenLoop`, `ClosedLoop`, `DynClosedLoop`, `PathConstraint`, `StateConstraint`, `ControlConstraint`, `MixedConstraint`, `Multiplier`, and their abstract supertypes.

⚠️ `OpenLoop`, `ClosedLoop`, `DynClosedLoop` and the constraint kinds are **factory functions, not types**. They all build a `ControlLaw{F,Kind,…}` / `PathConstraint{F,Kind,…}`; the kind is a trait parameter, so `OpenLoop <: AbstractControlLaw` is a `TypeError`. Dispatch on the trait.

Also new: `CTLie.dg_ad_backend` / `dg_ad_backend!` (global AD-backend control), `CTFlows.MultiPhase` (`n_phases`, `get_flow`, `get_switching_time`, …), and `CTSolvers.Integrators` (`SciML`, `final_state`, `evaluate_at`).

## `OpenLoop` is unconditionally non-autonomous

An open-loop control depends only on time — `u(t)` (or `u(t, v)`) — never on the state or costate, and autonomy is a property of the OCP, not of the control law itself. `OpenLoop` therefore does not offer `is_autonomous` as a real choice, unlike `ClosedLoop`/`DynClosedLoop`:

```julia
OpenLoop(t -> 1.0) # the only spelling — always u(t), or u(t, v) with is_variable=true
OpenLoop(() -> 1.0) # wrong: constructs silently, MethodError once the flow is run
```

`is_autonomous` is kept as a misuse-detector keyword only: passing it (`true` or `false`) emits a `@warn` explaining that it has no effect, rather than silently doing nothing or being treated as a real choice. `ClosedLoop` and `DynClosedLoop` are unaffected — `is_autonomous` still governs their arity exactly as before.

See [control-toolbox/CTBase.jl#515](https://github.com/control-toolbox/CTBase.jl/issues/515).

## For package authors: the strategy contract

If you define your own `AbstractStrategy`, you must now implement `parameter`:

```julia
CTBase.Strategies.parameter(::Type{<:MyStrategy}) = nothing # non-parameterized
CTBase.Strategies.parameter(::Type{MyStrategy{P}}) where {P} = P # parameterized
```

This is **not** a rename of `CTSolvers.Strategies.get_parameter_type`, which returned `nothing` by default. The CTBase generic throws `NotImplemented` instead, and option routing calls it — so a strategy that omits it fails at `solve` time rather than being treated as non-parameterized.

A caller that cannot guarantee a third-party strategy implements the contract should reach for the non-throwing `CTBase.Strategies.parameter(strategy_type, default)` — the `get(dict, key, default)`-style 2-arg accessor (CTBase ≥ 0.28.8-beta) — rather than writing its own `try`/`catch` around `NotImplemented`. OptimalControl's own display code does exactly this, and warns once per strategy type when the fallback is taken.

## `describe` now covers every strategy

`describe(:id)` previously only knew the *solve* registry (discretizer, NLP modeler, NLP solver). The AD backend and the ODE integrator are strategies in the same sense — `describe(:di)` and `describe(:sciml)` now work from the same single entry point, which merges the solve registry with CTFlows' flow registry (`Base.merge(::CTBase.Strategies.StrategyRegistry...)`, CTBase ≥ 0.28.8-beta).

---

# Breaking Changes: v1.x → v2.0

This document describes the breaking changes when migrating from **OptimalControl.jl v1.1.6** (last stable release) to **v2.0.0**.
Expand Down
52 changes: 52 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,58 @@ Versions follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

---

## [2.1.0-beta] — unreleased

Dependency upgrade onto the restructured control-toolbox stack, CTLie integration, and a substantial test rework. See [BREAKING.md](BREAKING.md) for the migration guide.

### Breaking

- **`Flow` now requires an integrator to be loaded.** SciML is no longer a hard dependency; add `using OrdinaryDiffEqTsit5` (or another integrator) before building a flow. This changes every example's preamble.

- **Differential geometry moved to CTLie**: `Lie(X, f)` → `ad(X, f)`; `X ⋅ f` **removed** with no alias; `HamiltonianLift` → `CTLie.LiftedHamiltonianFunction`, which is `<: Function` and **no longer** `<: AbstractHamiltonian`

- **Flow call convention**: the variable has no positional slot any more and `variable=` is mandatory on `NonFixed` problems; `augment=` → `variable_costate=`; new `unsafe=` to suppress the ODE retcode check

- **Constrained flows** take the paired keywords `constraint=` / `multiplier=` instead of three positional arguments. `constraint` now also accepts a `Symbol` naming a `:path` constraint already declared in the OCP

- **Constructor keywords** take an `is_` prefix: `autonomous=` → `is_autonomous=`, `variable=` → `is_variable=`, on the `Data` constructors and on `@Lie`

- **`time` and `success` are no longer re-exported.** Both resolve to bare `Base` functions — `CTModels.Components` extends `Base.time` without exporting it, and `CTModels.Solutions` exports the name `success` while defining no method for it, so `success(sol)` was always a `MethodError`. Use `successful(sol)`

- **For package authors**: a custom `AbstractStrategy` must now implement `CTBase.Strategies.parameter`. This is not a rename of `get_parameter_type`, which defaulted to `nothing`; the CTBase generic throws `NotImplemented`, and option routing calls it

- **`OpenLoop` is unconditionally non-autonomous.** An open-loop control depends only on time, `u(t)` (or `u(t, v)`) — autonomy is a property of the OCP, not of the control, so `is_autonomous` is not a real choice for `OpenLoop` the way it is for `ClosedLoop`/`DynClosedLoop`. `is_autonomous` is kept as a misuse-detector keyword that warns rather than doing nothing. See [CTBase.jl#515](https://github.com/control-toolbox/CTBase.jl/issues/515)

### Added

- **CTLie** as a dependency: `ad`, `Lift`, `Poisson`, `∂ₜ`, `@Lie`, and `dg_ad_backend` / `dg_ad_backend!` for global AD-backend control

- **The full `CTBase.Data` type vocabulary** is re-exported — `Flow` dispatches on these, so building a flow explicitly previously meant reaching into the package by hand. Note that `OpenLoop`, `ClosedLoop`, `DynClosedLoop` and the constraint kinds are factory functions, not types: the kind is a trait parameter

- `CTFlows.MultiPhase` (`n_phases`, `get_flow`, `get_switching_time`, …) and `CTSolvers.Integrators` (`SciML`, `final_state`, `evaluate_at`)

- **`describe` now covers the full strategy surface.** `describe(:di)` and `describe(:sciml)` work from the same entry point as `describe(:ipopt)`, merging the solve registry with CTFlows' flow registry via `Base.merge` (CTBase ≥ 0.28.8-beta)

- **Test problems are available in two front-end forms**, `:abstract` (the `@def` DSL) and `:functional` (the `CTModels.Building` API), and declare which solution methods they are fixtures for

- **Indirect test fixtures carry their own shooting derivation** (`TestProblem.shoot_builder`, next to the problem itself), consumed generically by a single shooting sweep instead of being re-derived per problem and per test file

- New test groups: extension arming, front-end equivalence, `hamiltonian_type`, the `Flow` API surface, the 1-D = scalar contract across both the direct and indirect paths, `describe` over the full strategy surface, and CPU/GPU routing — the device tier now *requires* a functional GPU on the self-hosted `kkt` runner rather than skipping unconditionally, so a degraded runner fails loudly instead of reporting green having run nothing

### Changed

- **Dependencies**: CTBase `0.28`, CTModels `0.15`, CTSolvers `0.4`, CTFlows `0.16`, CTDirect `1`, CTParser `0.8`; CTLie `0.1` added. SciML moved to `[extras]`. Direct dependencies 21 → 17

- **Imports point at owning submodules** throughout, per the Handbook `modules.md` rule

- **Extensions are explicitly armed.** A `[deps]` entry fires no extension — Julia loads one when its trigger package is loaded. ADNLPModels and DifferentiationInterface are now imported by `src/imports/`, without which the ADNLP modeler and the whole differential-geometry API were dead capabilities we still paid to install

### Fixed

- `_extract_strategy_parameters` no longer crashes on a strategy that has not implemented the optional parameter contract — display code should not be what fails on a third-party strategy. It now warns once per strategy type instead of staying silent, forwarding to CTBase's non-throwing `parameter(T, default)` accessor (CTBase ≥ 0.28.8-beta) rather than rolling its own `try`/`catch`

---

## [2.0.5-beta] — 2026-07-24

### Added
Expand Down
42 changes: 24 additions & 18 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
name = "OptimalControl"
uuid = "5f98b655-cc9a-415a-b60e-744165666948"
version = "2.0.5-beta"
version = "2.1.0-beta"
authors = ["Olivier Cots <olivier.cots@toulouse-inp.fr>"]

[deps]
ADNLPModels = "54578032-b7ea-4c30-94aa-7cbd1cce6c9a"
CTBase = "54762871-cc72-4466-b8e8-f6c8b58076cd"
CTDirect = "790bbbee-bee9-49ee-8912-a9de031322d5"
CTFlows = "1c39547c-7794-42f7-af83-d98194f657c2"
CTLie = "6880e05b-3a7d-4cac-887c-30cb52c5fdde"
CTModels = "34c4fa32-2049-4079-8329-de33c2a22e2d"
CTParser = "32681960-a1b1-40db-9bff-a1ca817385d1"
CTSolvers = "d3e8d392-8e4b-4d9b-8e92-d7d4e3650ef6"
CommonSolve = "38540f10-b2f7-11e9-35d8-d573e4eb0ff2"
DifferentiationInterface = "a0c0ee7d-e4b9-4e03-894e-1c5f64a51d63"
DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
ExaModels = "1037b233-b668-4ce9-9b63-f9f681f55dd2"
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
NLPModels = "a4795742-8479-5a88-8948-cc11e1c8c1a6"
RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"
Expand All @@ -24,32 +26,35 @@ SolverCore = "ff4d7338-4cf1-434d-91df-b86cb86fb843"
[compat]
ADNLPModels = "0.8"
BenchmarkTools = "1"
CTBase = "=0.18.8"
CTBase = "0.28"
CTDirect = "1"
CTFlows = "0.8"
CTModels = "=0.10.1"
CTFlows = "0.16"
CTLie = "0.1"
CTModels = "0.15"
CTParser = "0.8"
CTSolvers = "0.4"
CUDA = "5"
CUDA = "5, 6"
CUDSS = "0.6, 0.7, 0.8"
CommonSolve = "0.2"
DiffEqBase = "6, 7"
DifferentiationInterface = "0.7"
DocStringExtensions = "0.9"
Documenter = "1.17.0"
ExaModels = "0.9"
ForwardDiff = "0.10, 1.0"
ExaModels = "0.11"
ForwardDiff = "0.10, 1"
LinearAlgebra = "1"
Literate = "2"
MadNCL = "0.2"
MadNLP = "0.9"
MadNLPGPU = "0.8"
MadNLP = "0.9, 0.10"
MadNLPGPU = "0.8, 0.10"
NLPModels = "0.21"
NLPModelsIpopt = "0.11"
NonlinearSolve = "4"
OrdinaryDiffEq = "6"
OrdinaryDiffEq = "6, 7"
OrdinaryDiffEqTsit5 = "2"
Printf = "1"
RecipesBase = "1"
Reexport = "1"
SolverCore = "0.3.9"
SciMLBase = "3"
SolverCore = "0.3"
SplitApplyCombine = "1"
Test = "1"
UnoSolver = "0.3"
Expand All @@ -58,19 +63,20 @@ julia = "1.10"
[extras]
BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba"
DifferentiationInterface = "a0c0ee7d-e4b9-4e03-894e-1c5f64a51d63"
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
Literate = "98b081ad-f1c9-55d3-8b20-4c87d4299306"
CUDSS = "45b445bb-4962-46a0-9369-b4df9d0f772e"
DiffEqBase = "2b5f629d-d688-5b77-993f-72d75c75574e"
MadNCL = "434a0bcb-5a7c-42b2-a9d3-9e3f760e7af0"
MadNLP = "2621e9c9-9eb4-46b1-8089-e8c72242dfb6"
MadNLPGPU = "d72a61cc-809d-412f-99be-fd81f4b8a598"
NLPModelsIpopt = "f4238b75-b362-5c4c-b852-0801c9a21d71"
NonlinearSolve = "8913a72c-1f9b-4ce2-8d82-65094dcecaec"
OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed"
OrdinaryDiffEqTsit5 = "b1df2697-797e-41e3-8120-5422d3b24e4a"
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
SciMLBase = "0bca4576-84f4-4d90-8ffe-ffa030f20462"
SplitApplyCombine = "03a91e81-4c3e-53e1-a0a4-9c0c8f19dd66"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
UnoSolver = "1baa60ac-02f7-4b39-a7a8-2f4f58486b05"

[targets]
test = ["BenchmarkTools", "CUDA", "DifferentiationInterface", "ForwardDiff", "MadNCL", "MadNLP", "MadNLPGPU", "NLPModelsIpopt", "NonlinearSolve", "OrdinaryDiffEq", "Printf", "SplitApplyCombine", "Test", "UnoSolver"]
test = ["BenchmarkTools", "CUDA", "CUDSS", "DiffEqBase", "MadNCL", "MadNLP", "MadNLPGPU", "NLPModelsIpopt", "NonlinearSolve", "OrdinaryDiffEq", "OrdinaryDiffEqTsit5", "Printf", "SciMLBase", "SplitApplyCombine", "Test", "UnoSolver"]
1 change: 1 addition & 0 deletions _typos.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
locale = "en"
extend-ignore-re = [
"ded",
"mis-",
]

[files]
Expand Down
20 changes: 15 additions & 5 deletions src/OptimalControl.jl
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ sol = solve(ocp, :collocation, :adnlp, :ipopt)
# Or solve using explicit mode (typed components)
sol = solve(ocp;
discretizer=CTDirect.Collocation(),
modeler=CTSolvers.ADNLP(),
solver=CTSolvers.Ipopt()
modeler=CTSolvers.Modelers.ADNLP(),
solver=CTSolvers.Solvers.Ipopt()
)
```

Expand All @@ -57,17 +57,27 @@ using CommonSolve: CommonSolve
@reexport import CommonSolve: solve
using CTBase: CTBase
using CTModels: CTModels
using CTLie: CTLie
using CTDirect: CTDirect
using CTSolvers: CTSolvers
using CTFlows: CTFlows

# Imports
#
# Order follows the dependency graph: CTBase, then CTModels / CTLie, then
# CTSolvers, then CTFlows, then CTDirect / CTParser.
include(joinpath(@__DIR__, "imports", "ctbase.jl"))
include(joinpath(@__DIR__, "imports", "ctdirect.jl"))
include(joinpath(@__DIR__, "imports", "ctflows.jl"))
include(joinpath(@__DIR__, "imports", "ctmodels.jl"))
include(joinpath(@__DIR__, "imports", "ctparser.jl"))
include(joinpath(@__DIR__, "imports", "ctlie.jl"))
include(joinpath(@__DIR__, "imports", "ctsolvers.jl"))
include(joinpath(@__DIR__, "imports", "ctflows.jl"))
include(joinpath(@__DIR__, "imports", "ctdirect.jl"))
include(joinpath(@__DIR__, "imports", "ctparser.jl"))

# Extension triggers — a `[deps]` entry arms nothing, the load is what counts.
include(joinpath(@__DIR__, "imports", "adnlpmodels.jl"))
include(joinpath(@__DIR__, "imports", "examodels.jl"))
include(joinpath(@__DIR__, "imports", "ad.jl"))
# include(joinpath(@__DIR__, "imports", "redefine.jl"))

# helpers
Expand Down
Loading