Migrating from v2.0
OptimalControl.jl v2.1.0-beta reorganised the ecosystem so that every name has exactly one owner. Most of the changes are mechanical: a Lie becomes an ad, a success becomes a successful, and the old spelling fails with a message naming its replacement. A few changes are semantic and do not announce themselves; they get their own warnings below.
The messages need 2.2.0-beta or newer
The old spellings stopped working in v2.1.0-beta. The messages that name their replacements (What you get instead of an error) came in 2.2.0-beta. With exactly v2.1.0-beta, the same mistakes give a bare UndefVarError or MethodError.
The full record of the breaking changes, with the reasons, is BREAKING.md in the repository; this page says what to change in a script.
Start here: Flow needs an integrator
This is the most common first failure. The ODE integrators are no longer a dependency of the package: load one before building a flow.
using OptimalControl
using OrdinaryDiffEqTsit5 # required before any Flow(...)
f = Flow(ocp, (x, p) -> p[2])Without it, Flow fails with an ExtensionError that names the package to load. The direct method, solve, does not need it. See Installation and Flows overview.
What was renamed
| v2.0 | Now | Notes |
|---|---|---|
OptimalControl.VectorField, OptimalControl.Hamiltonian, … | VectorField, Hamiltonian, … | exported; the qualified form still works |
Lie(X, f) | ad(X, f) | Lie throws, naming ad |
X ⋅ f | ad(X, f) | no operator; X ⋅ f throws, naming ad, when X is a VectorField |
HamiltonianLift | Lift(f) to build; OptimalControl.LiftedHamiltonianFunction to name the type | renamed and re-parented, see What changed meaning silently |
autonomous=, variable=, inplace= (constructor keywords) | is_autonomous=, is_variable=, is_inplace= | on VectorField, Hamiltonian, @Lie, and the other constructors |
Flow(f) with f::Function | Flow(VectorField(f)), Flow(Hamiltonian(f)), Flow(HamiltonianVectorField(f)) | a function alone does not say which flow to build |
Flow(ocp, u, g, μ) | Flow(ocp, u; constraint=g, multiplier=μ) | two keywords, given together |
f(t0, x0, p0, tf, λ), f(t0, x0, tf, λ) | f(t0, x0, p0, tf; variable=λ), f(t0, x0, tf; variable=λ) | variable= is a keyword, required on a problem with a variable |
augment=true | variable_costate=true | returns (xf, pf, pvf) |
CTSolvers.Modelers.ADNLP(), CTDirect.Collocation() | OptimalControl.ADNLP(), OptimalControl.Collocation() | the modules CTSolvers and CTDirect are not exported |
time(ocp), time(sol) | times(ocp), time_grid(sol) | time is Base.time |
success(sol) | successful(sol) | success is Base.success |
What changed shape
The flow call. The variable is passed by keyword, never by position:
f(t0, x0, p0, tf, λ)
f(t0, x0, p0, tf; augment=true)
f(t0, x0, p0, tf; variable=λ)
f(t0, x0, p0, tf; variable=λ, variable_costate=true) On a problem with a variable, variable= is required: without it, the call throws, saying what to pass. The call also accepts unsafe=true, which does not check the return code of the integrator: inside a shooting function, a failed integration then shows in the residual instead of stopping the solver (see Shooting). The integrator options (saveat=, abstol=, reltol=, alg=, …) are no longer accepted by the call. Pass them when the flow is built:
f = Flow(ocp, (x, p) -> p[2]; abstol=1e-8)
f(t0, x0, p0, tf)Constrained flows. The constraint and the multiplier are keywords, given together:
fb = Flow(ocp, u, g, μ)
fb = Flow(ocp, u; constraint=g, multiplier=μ) One without the other is an IncorrectArgument. constraint= accepts a function, a typed constraint, or a Symbol naming a path constraint of the problem, constraint=:vmax. The Symbol form uses the constraint function stored in the problem, without its bound: see the caveat on Constrained arcs.
Constructor keywords take an is_ prefix, on VectorField, Hamiltonian, @Lie, and the other constructors:
VectorField(f; autonomous=false, variable=true)
@Lie [X, Y] autonomous=false
VectorField(f; is_autonomous=false, is_variable=true)
@Lie [X, Y] is_autonomous=falseThe old keyword on @Lie raises an IncorrectArgument that lists the accepted keywords. On VectorField and Hamiltonian, it raises a MethodError, "got unsupported keyword argument".
What changed meaning silently
Three changes do not raise an error.
Lift(f::Function) is no longer an AbstractHamiltonian
In v2.0, lifting a plain function and lifting a VectorField gave the same kind of object. Now, the lift of a function is a function, OptimalControl.LiftedHamiltonianFunction:
H = Lift(f) # f::Function
H isa AbstractHamiltonian # false now, true in v2.0A test isa AbstractHamiltonian on it is now false. The lift of a VectorField is still a Hamiltonian. See Lift.
OpenLoop checks the arity of the law when the flow runs
An open-loop control is a function of time, u(t), or u(t, v) with a variable. A function without argument is accepted when the law is built, and fails only when the flow runs, with a MethodError far from the mistake (CTBase#570):
bad_law = OpenLoop(() -> 1.0) # accepted
Flow(ControlledVectorField(fc), bad_law)(t0, x0, tf) # MethodError hereSee Simulation.
The old state-flow call runs on a flow with a costate
In v2.0, a flow of the state was called f(t0, x0, tf, λ), with the variable f(t0, x0, p0, tf), also with four arguments. Called the old way, it runs, with the arguments shifted: tf is taken as p0, and λ as tf.
f(t0, x0, tf, λ) # runs as f(t0, x0, p0=tf, tf=λ)It fails only when the shapes do not match. Check these calls by hand.
What you get instead of an error
The removed spellings throw a PreconditionError naming their replacement. With a problem ocp with a variable, its solution sol, a vector field X and a function f:
julia> Lie(X, f)
PreconditionError → Lie, deprecated.jl:52
│
│ `Lie(X, f) / Lie(X, Y)` is deprecated
│
│ Reason this spelling was removed in v2.1.0-beta
│
│ Hint use ad(X, f) or ad(X, Y)
└─julia> X ⋅ f
PreconditionError → dot, deprecated.jl:62
│
│ `X \cdot f` is deprecated
│
│ Reason this spelling was removed in v2.1.0-beta
│
│ Hint use ad(X, f)
└─julia> HamiltonianLift(f)
PreconditionError → HamiltonianLift, deprecated.jl:72
│
│ `HamiltonianLift` is deprecated
│
│ Reason this spelling was removed in v2.1.0-beta
│
│ Hint use Lift(f) for a plain function, or CTLie.LiftedHamiltonianFunction
└─julia> time(ocp)
PreconditionError → time, deprecated.jl:82
│
│ `time(ocp)` is deprecated
│
│ Reason this spelling was removed in v2.1.0-beta
│
│ Hint use times(ocp)
└─julia> time(sol)
PreconditionError → time, deprecated.jl:84
│
│ `time(sol)` is deprecated
│
│ Reason this spelling was removed in v2.1.0-beta
│
│ Hint use time_grid(sol)
└─julia> success(sol)
PreconditionError → success, deprecated.jl:86
│
│ `success(sol)` is deprecated
│
│ Reason this spelling was removed in v2.1.0-beta
│
│ Hint use successful(sol)
└─julia> Flow(f)
PreconditionError → Flow, deprecated.jl:91
│
│ `Flow(f::Function)` is deprecated
│
│ Reason this spelling was removed in v2.1.0-beta
│
│ Hint use Flow(VectorField(f)), Flow(Hamiltonian(f)), or Flow(HamiltonianVectorField(f))
└─julia> Flow(ocp, (x, p, v) -> 0, g, μ)
PreconditionError → top-level scope, REPL[1]:2
│
│ Flow(ocp, …) with extra positional arguments is not supported
│
│ Reason passing a control law, state constraint or multiplier as a positional argument is not handled by the OCP flow constructor
│
│ Context Flow(ocp::CTModels.Models.Model) — positional-argument guard
│ Hint Flow(ocp, u) takes the control law positionally; pass constraint/multiplier as keywords: Flow(ocp, u; constraint=g, multiplier=μ)
└─On the flow fh of the problem, and on the flow fs of a state with a variable:
julia> fh(t0, x0, p0, tf, λ)
PreconditionError → AbstractFlow, deprecated.jl:102
│
│ `f(t0, x0, p0, tf, lambda)` is deprecated
│
│ Reason this spelling was removed in v2.1.0-beta
│
│ Hint use f(t0, x0, p0, tf; variable=lambda)
└─julia> fs(t0, 1.0, tf, λ)
PreconditionError → AbstractFlow, deprecated.jl:108
│
│ `f(t0, x0, tf, lambda)` is deprecated
│
│ Reason this spelling was removed in v2.1.0-beta
│
│ Hint use f(t0, x0, tf; variable=lambda)
└─julia> fh(t0, x0, p0, tf; variable=λ, augment=true)
PreconditionError → top-level scope, REPL[1]:2
│
│ augment=true is not supported (removed in v2.1.0-beta)
│
│ Reason the `augment` keyword was renamed to `variable_costate`, with the same behaviour
│
│ Context OptimalControlFlow point evaluation — deprecated `augment` keyword guard
│ Hint use variable_costate=true instead of augment=true
└─X ⋅ f is caught when X is a VectorField. With a plain function X, as in v2.0, it raises a MethodError about iterate.
What raises another error
| Spelling | What happens | Write instead |
|---|---|---|
f(t0, x0, p0, tf; saveat=…), and the other integrator options on a call | MethodError, "unsupported keyword argument" | Flow(…; saveat=…), when the flow is built |
CTSolvers.Modelers.ADNLP(), CTDirect.Collocation() | UndefVarError on CTSolvers or CTDirect | OptimalControl.ADNLP(), OptimalControl.Collocation() |
@Lie [X, Y] autonomous=false | IncorrectArgument, with the accepted keywords | @Lie [X, Y] is_autonomous=false |
VectorField(f; autonomous=false), Hamiltonian(h; variable=true) | MethodError, "unsupported keyword argument" | is_autonomous=, is_variable= |
Initial guesses as named tuples
@init is the recommended way to write an initial guess. The named tuple of v2.0 still works, with the fields state, control and variable (see Initial guess), and also with the labels of the components:
sol = solve(ocp; init=(state=[-0.2, 0.1], control=-0.2))
sol = solve(ocp; init=(q=-1.0, v=0.0, u=0.1, tf=2.0))@init checks the labels against the problem. In both forms, the costate and the multipliers cannot be given a guess.
From v1.x to v2.0
For the older migration from v1.x to v2.0 (the direct-mode API, option routing, the methods), see the second section of BREAKING.md.