Choosing a method
A method is a quadruplet (discretizer, modeler, solver, parameter). This page maps out what can be combined with what, how a partial description gets completed, and how to inspect any one piece before you commit to it.
The four families
Discretizer — how the continuous problem is transcribed into a finite-dimensional one. Only
:collocationexists today.Modeler — how the resulting NLP is built:
:adnlp(automatic differentiation via ADNLPModels) or:exa(SIMD-friendly, GPU-capable, via ExaModels — only for problems whose dynamics are written coordinatewise, see Abstract syntax).Solver — which NLP solver runs:
:ipopt,:madnlp,:uno,:madncl,:knitro.Parameter — execution backend:
:cpuor:gpu.
What is available
using OptimalControl
methods()(:collocation, :adnlp, :ipopt, :cpu)
(:collocation, :adnlp, :madnlp, :cpu)
(:collocation, :adnlp, :uno, :cpu)
(:collocation, :adnlp, :madncl, :cpu)
(:collocation, :adnlp, :knitro, :cpu)
(:collocation, :exa, :ipopt, :cpu)
(:collocation, :exa, :madnlp, :cpu)
(:collocation, :exa, :uno, :cpu)
(:collocation, :exa, :madncl, :cpu)
(:collocation, :exa, :knitro, :cpu)
(:collocation, :exa, :madnlp, :gpu)
(:collocation, :exa, :madncl, :gpu)There are 12 methods: every {adnlp, exa} × {ipopt, madnlp, uno, madncl, knitro} pair on :cpu (10), plus the two GPU-capable combinations :exa × {:madnlp, :madncl} on :gpu (2). This list is not fixed prose to memorize — it is exactly what methods() returns, so it's printed live rather than quoted as a number anywhere on this page.
A problem to try them on
Every solve call on this page uses the same problem — the double integrator, with its dynamics written coordinatewise so that both the :adnlp and the :exa modeler accept it:
ocp = @def begin
t ∈ [0, 1], time
x = (q, v) ∈ R², state
u ∈ R, control
x(0) == [-1, 0]
x(1) == [0, 0]
∂(q)(t) == v(t)
∂(v)(t) == u(t)
∫(0.5u(t)^2) → min
endPartial descriptions
solve(ocp, :madnlp) doesn't need the other three tokens — they're completed for you. Completion walks methods() from top to bottom and returns the first entry containing every token you gave:
solve(ocp, :madnlp) # → (:collocation, :adnlp, :madnlp, :cpu) — first entry with :madnlp
solve(ocp, :exa) # → (:collocation, :exa, :ipopt, :cpu) — first entry with :exa
solve(ocp, :gpu) # → (:collocation, :exa, :madnlp, :gpu) — first GPU entryThis first-match-top-to-bottom rule is also why the plain solve(ocp) default is (:collocation, :adnlp, :ipopt, :cpu): it's simply methods()[1]. All of these are equivalent:
solve(ocp) # empty description → methods()[1]
solve(ocp, :collocation)
solve(ocp, :adnlp)
solve(ocp, :ipopt)
solve(ocp, :cpu)
solve(ocp, :collocation, :adnlp)
solve(ocp, :collocation, :adnlp, :ipopt, :cpu) # the complete descriptionAmbiguity
Two tokens from the same family never both fit one method — :adnlp and :exa can't both be true of one quadruplet — so this raises AmbiguousDescription rather than silently picking one:
julia> solve(ocp, :adnlp, :exa; display=false)
AmbiguousDescription → _complete_description, strategy_builders.jl:260
│
│ cannot find matching description
│
│ Diagnostic No complete match — no description contains all symbols
│ Requested (:adnlp, :exa)
│ Available (:collocation, :adnlp, :ipopt, :cpu)
│ (:collocation, :adnlp, :madnlp, :cpu)
│ (:collocation, :adnlp, :uno, :cpu)
│ (:collocation, :adnlp, :madncl, :cpu)
│ (:collocation, :adnlp, :knitro, :cpu)
│ (:collocation, :exa, :ipopt, :cpu)
│ (:collocation, :exa, :madnlp, :cpu)
│ (:collocation, :exa, :uno, :cpu)
│ (:collocation, :exa, :madncl, :cpu)
│ (:collocation, :exa, :knitro, :cpu)
│
│ Context description completion
│ Hint Try one of the closest matches:
└─The exception lists every candidate whose tokens are a superset of what matched, so you can see what's close.
What each solver needs installed
| Solver | Load |
|---|---|
:ipopt | using NLPModelsIpopt |
:madnlp | using MadNLP (CPU) or using MadNLPGPU (GPU) |
:uno | using UnoSolver |
:madncl | using MadNCL and using MadNLP (both) |
:knitro | using NLPModelsKnitro (commercial licence required) |
Solving without the matching package loaded raises an ExtensionError naming exactly which using statement to add.
Inspecting a strategy
describe works on any strategy id, and covers more than the direct-solve side: it also describes the indirect-method families (:di, :sciml) and the two parameters themselves.
describe(:collocation)Collocation (strategy)
├─ id: :collocation
├─ hierarchy: Collocation → AbstractDiscretizer → AbstractStrategy
├─ family: AbstractDiscretizer
│
└─ options (3 options):
│
├─ grid_size::Int64 (default: 250)
│ description: Number of time steps for the collocation grid
│
├─ scheme (disc_method)::Symbol (default: midpoint)
│ description: Time integration scheme (:trapeze, :midpoint, :euler (or :euler_explicit, :euler_forward), :euler_implicit (or :euler_backward), :gauss_legendre_2, :gauss_legendre_3, :variable)
│
└─ time_grid::Any (default: nothing)
description: Explicit time grid (possibly non uniform) for the collocationdescribe(:adnlp)ADNLP (strategy)
├─ id: :adnlp
├─ hierarchy: ADNLP → AbstractNLPModeler → AbstractStrategy
├─ description: NLP modeler using ADNLPModels with automatic differentiation.
│ See: https://jso.dev/ADNLPModels.jl
├─ family: AbstractNLPModeler
├─ default parameter: CPU
├─ parameters: CPU
│
└─ options (11 options):
│
├─ show_time::Bool (default: NotProvided)
│ description: Whether to show timing information while building the ADNLP model
│
├─ backend (adnlp_backend)::Symbol (default: optimized)
│ description: Automatic differentiation backend used by ADNLPModels.
│ Available: zygote, default, generic, enzyme, optimized and manual.
│
├─ matrix_free::Bool (default: NotProvided)
│ description: Enable matrix-free mode (avoids explicit Hessian/Jacobian matrices)
│
├─ name::String (default: NotProvided)
│ description: Name of the optimization model for identification
│
├─ gradient_backend::Union{Nothing, ADNLPModels.ADBackend, Type{<:ADNLPModels.ADBackend}} (default: NotProvided)
│ description: Override backend for gradient computation (advanced users only)
│
├─ hprod_backend::Union{Nothing, ADNLPModels.ADBackend, Type{<:ADNLPModels.ADBackend}} (default: NotProvided)
│ description: Override backend for Hessian-vector product (advanced users only)
│
├─ jprod_backend::Union{Nothing, ADNLPModels.ADBackend, Type{<:ADNLPModels.ADBackend}} (default: NotProvided)
│ description: Override backend for Jacobian-vector product (advanced users only)
│
├─ jtprod_backend::Union{Nothing, ADNLPModels.ADBackend, Type{<:ADNLPModels.ADBackend}} (default: NotProvided)
│ description: Override backend for transpose Jacobian-vector product (advanced users only)
│
├─ jacobian_backend::Union{Nothing, ADNLPModels.ADBackend, Type{<:ADNLPModels.ADBackend}} (default: NotProvided)
│ description: Override backend for Jacobian matrix computation (advanced users only)
│
├─ hessian_backend::Union{Nothing, ADNLPModels.ADBackend, Type{<:ADNLPModels.ADBackend}} (default: NotProvided)
│ description: Override backend for Hessian matrix computation (advanced users only)
│
└─ ghjvprod_backend::Union{Nothing, ADNLPModels.ADBackend, Type{<:ADNLPModels.ADBackend}} (default: NotProvided)
description: Override backend for g^T ∇²c(x)v computation (advanced users only)using NLPModelsIpopt
describe(:ipopt)Ipopt (strategy)
├─ id: :ipopt
├─ hierarchy: Ipopt → AbstractNLPSolver → AbstractStrategy
├─ description: Interior-point NLP solver (COIN-OR Ipopt).
│ See: https://coin-or.github.io/Ipopt/OPTIONS.html
├─ family: AbstractNLPSolver
├─ default parameter: CPU
├─ parameters: CPU
│
└─ options (29 options):
│
├─ tol::Real (default: 1.0e-8)
│ description: Desired convergence tolerance (relative). Determines the convergence tolerance for the algorithm. The algorithm terminates successfully, if the (scaled) NLP error becomes smaller than this value, and if the (absolute) criteria according to dual_inf_tol, constr_viol_tol, and compl_inf_tol are met.
│
├─ max_iter (maxiter, max_iterations, maxit)::Integer (default: 1000)
│ description: Maximum number of iterations. The algorithm terminates with a message if the number of iterations exceeded this number.
│
├─ max_wall_time (maxtime, max_time, time_limit)::Real (default: NotProvided)
│ description: Maximum number of walltime clock seconds. A limit on walltime clock seconds that Ipopt can use to solve one problem.
│
├─ max_cpu_time::Real (default: NotProvided)
│ description: Maximum number of CPU seconds. A limit on CPU seconds that Ipopt can use to solve one problem.
│
├─ dual_inf_tol::Real (default: NotProvided)
│ description: Desired threshold for the dual infeasibility. Absolute tolerance on the dual infeasibility. Successful termination requires that the max-norm of the (unscaled) dual infeasibility is less than this threshold.
│
├─ constr_viol_tol::Real (default: NotProvided)
│ description: Desired threshold for the constraint and variable bound violation. Absolute tolerance on the constraint and variable bound violation.
│
├─ acceptable_tol (acc_tol)::Real (default: NotProvided)
│ description: Acceptable convergence tolerance (relative). Determines which (scaled) optimality error is considered close enough.
│
├─ acceptable_iter::Integer (default: NotProvided)
│ description: Number of "acceptable" iterations required to trigger termination. If the algorithm encounters this many consecutive iterations that are acceptable, it terminates.
│
├─ diverging_iterates_tol::Real (default: NotProvided)
│ description: Threshold for maximal value of primal iterates. If any component of the primal iterates exceeds this value (in absolute terms), the optimization is aborted.
│
├─ derivative_test::String (default: NotProvided)
│ description: Enable derivative check. If enabled, performs a finite difference check of the derivatives.
│
├─ derivative_test_tol::Real (default: NotProvided)
│ description: Threshold for identifying incorrect derivatives. If the relative error of the finite difference approximation exceeds this value, an error is reported.
│
├─ derivative_test_print_all::String (default: NotProvided)
│ description: Indicates whether information for all estimated derivatives should be printed.
│
├─ hessian_approximation::String (default: NotProvided)
│ description: Indicates what Hessian information regarding the Lagrangian function is to be used.
│
├─ limited_memory_update_type::String (default: NotProvided)
│ description: Quasi-Newton update method for the limited memory approximation.
│
├─ warm_start_init_point::String (default: NotProvided)
│ description: Indicates whether specific warm start values should be used for the primal and dual variables.
│
├─ warm_start_bound_push::Real (default: NotProvided)
│ description: Indicates how much the primal variables should be pushed inside the bounds for the warm start.
│
├─ warm_start_mult_bound_push::Real (default: NotProvided)
│ description: Indicates how much the dual variables should be pushed inside the bounds for the warm start.
│
├─ mu_strategy::String (default: adaptive)
│ description: Barrier parameter update strategy
│
├─ mu_init::Real (default: NotProvided)
│ description: Initial value for the barrier parameter.
│
├─ mu_max_fact::Real (default: NotProvided)
│ description: Factor for maximal barrier parameter. This factor determines the upper bound on the barrier parameter.
│
├─ mu_max::Real (default: NotProvided)
│ description: Maximal value for barrier parameter. This option overrides the factor setting.
│
├─ mu_min::Real (default: NotProvided)
│ description: Minimal value for barrier parameter.
│
├─ timing_statistics::String (default: NotProvided)
│ description: Indicates whether to measure time spent in components of Ipopt and NLP evaluation. The overall algorithm time is unaffected by this option.
│
├─ linear_solver::String (default: mumps)
│ description: Linear solver used for step computations. Determines which linear algebra package is to be used for the solution of the augmented linear system (for obtaining the search directions).
│
├─ print_level::Integer (default: 5)
│ description: Ipopt output verbosity (0-12)
│
├─ print_timing_statistics::String (default: NotProvided)
│ description: Switch to print timing statistics. If selected, the program will print the time spent for selected tasks. This implies timing_statistics=yes.
│
├─ print_frequency_iter::Integer (default: NotProvided)
│ description: Determines at which iteration frequency the summarizing iteration output line should be printed. Summarizing iteration output is printed every print_frequency_iter iterations, if at least print_frequency_time seconds have passed since last output.
│
├─ print_frequency_time::Real (default: NotProvided)
│ description: Determines at which time frequency the summarizing iteration output line should be printed. Summarizing iteration output is printed if at least print_frequency_time seconds have passed since last output and the iteration number is a multiple of print_frequency_iter.
│
└─ sb::String (default: yes)
description: Suppress Ipopt banner (yes/no)describe(:cpu)CPU (parameter)
├─ id: :cpu
├─ hierarchy: CPU → AbstractStrategyParameter
├─ description: CPU-based computation
│
└─ used by strategies (9):
├─ :adnlp (AbstractNLPModeler) → ADNLP{CPU}
├─ :di (AbstractADBackend) → DifferentiationInterface{O}
├─ :exa (AbstractNLPModeler) → Exa{CPU}
├─ :ipopt (AbstractNLPSolver) → Ipopt{CPU}
├─ :knitro (AbstractNLPSolver) → Knitro{CPU}
├─ :madncl (AbstractNLPSolver) → MadNCL{CPU}
├─ :madnlp (AbstractNLPSolver) → MadNLP{CPU}
├─ :sciml (AbstractIntegrator) → SciML{O, OP, OT}
└─ :uno (AbstractNLPSolver) → Uno{CPU}Discretization schemes
:collocation accepts a scheme option (alias disc_method):
| Value | Notes | :adnlp | :exa |
|---|---|---|---|
:trapeze | second-order | ✅ | ✅ |
:midpoint | second-order, default | ✅ | ✅ |
:euler | first-order, explicit | ✅ | ✅ |
:euler_implicit | first-order, implicit | ✅ | ✅ |
:euler_explicit, :euler_forward | aliases of :euler | ✅ | ✗ |
:euler_backward | alias of :euler_implicit | ✅ | ✗ |
:gauss_legendre_2 | fourth-order | ✅ | ✗ |
:gauss_legendre_3 | sixth-order | ✅ | ✗ |
:variable | variable-step, ODE-based | ✗ | ✗ |
plus grid_size (default 250) or an explicit, possibly non-uniform, time_grid.
Every cell above was checked by solving with that scheme. Two of the results need spelling out.
Under :exa, only the four canonical names work
:exa takes :trapeze, :midpoint, :euler and :euler_implicit, and nothing else. Both Gauss-Legendre schemes are out — and so are the aliases, which is the surprising part: :euler_forward is rejected where :euler is accepted, though under :adnlp the two name the same scheme. Confirmed live:
using Logging
# ExaModels warns about a deprecated `ExaCore()` call on every model it
# builds; silenced here so the scheme error is the only output.
try
with_logger(NullLogger()) do
solve(ocp, :exa; scheme=:gauss_legendre_2, display=false)
end
catch e
println(e)
endLine 6: (∂(q))(t) == v(t)
unknown numerical scheme: gauss_legendre_2 (possible choices are :euler, :euler_implicit, :midpoint, :trapeze):variable is advertised but does not run
The describe(:collocation) output above lists :variable, and the registry accepts it, but no modeler can use it: the scheme dispatches to CTDirect.VariableStepODE, whose implementation is not compiled into CTDirect, so a solve raises UndefVarError(:VariableStepODE, …, CTDirect) rather than any typed error. It is kept in the table only because the registry listing above advertises it.
Advanced: the strategy registry
strategy_ids, type_from_id, and available_parameters (and the create_registry used to build one) all operate on a populated StrategyRegistry. The one that already knows about every built-in strategy is internal (OptimalControl.get_strategy_registry(), not re-exported) — these functions are orchestration/extension-authoring tools, not something a typical solve caller reaches for. For everyday inspection, methods() and describe (above) cover the same ground and need nothing extra.
See also
Options and routing — how keyword arguments reach the right strategy.
Solving on GPU — the
:gpuparameter in full.API reference: Options and strategies — every symbol on this page, with full signatures.