Solve overview
solve is the entry point for the direct methods: transcribe the problem, hand it to an NLP solver, get a Solution back. This page shows the quickest way to call it, how to read what it prints, and the two ways to steer it away from its defaults.
Quick start
using OptimalControl
using NLPModelsIpopt
t0 = 0
tf = 1
x0 = [-1, 0]
ocp = @def begin
t ∈ [t0, tf], time
x = (q, v) ∈ R², state
u ∈ R, control
x(t0) == x0
x(tf) == [0, 0]
ẋ(t) == [v(t), u(t)]
0.5∫(u(t)^2) → min
end
sol = solve(ocp)▫ This is OptimalControl 2.2.1-beta, solving with: collocation → adnlp → ipopt (cpu)
📦 Configuration:
├─ Discretizer: collocation
├─ Modeler: adnlp
└─ Solver: ipopt
▫ This is Ipopt version 3.14.19, running with linear solver MUMPS 5.9.0.
Number of nonzeros in equality constraint Jacobian...: 1754
Number of nonzeros in inequality constraint Jacobian.: 0
Number of nonzeros in Lagrangian Hessian.............: 250
Total number of variables............................: 752
variables with only lower bounds: 0
variables with lower and upper bounds: 0
variables with only upper bounds: 0
Total number of equality constraints.................: 504
Total number of inequality constraints...............: 0
inequality constraints with only lower bounds: 0
inequality constraints with lower and upper bounds: 0
inequality constraints with only upper bounds: 0
iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls
0 5.0000000e-03 1.10e+00 2.24e-14 0.0 0.00e+00 - 0.00e+00 0.00e+00 0
1 6.0000960e+00 2.22e-16 1.78e-15 -11.0 6.08e+00 - 1.00e+00 1.00e+00h 1
Number of Iterations....: 1
(scaled) (unscaled)
Objective...............: 6.0000960015360381e+00 6.0000960015360381e+00
Dual infeasibility......: 1.7763568394002505e-15 1.7763568394002505e-15
Constraint violation....: 2.2204460492503131e-16 2.2204460492503131e-16
Variable bound violation: 0.0000000000000000e+00 0.0000000000000000e+00
Complementarity.........: 0.0000000000000000e+00 0.0000000000000000e+00
Overall NLP error.......: 1.7763568394002505e-15 1.7763568394002505e-15
Number of objective function evaluations = 2
Number of objective gradient evaluations = 2
Number of equality constraint evaluations = 2
Number of inequality constraint evaluations = 0
Number of equality constraint Jacobian evaluations = 2
Number of inequality constraint Jacobian evaluations = 0
Number of Lagrangian Hessian evaluations = 1
Total seconds in IPOPT = 4.672
EXIT: Optimal Solution Found.A solver package must be loaded before calling solve: here using NLPModelsIpopt provides the default :ipopt. Without it, solve raises an ExtensionError naming the missing package and the using statement that fixes it (see Installation).
Reading the display
Before running, solve prints the method it is about to use, then a Configuration box with one line per strategy: the discretizer, the modeler and the solver. Each line lists the options that differ from the strategy's defaults. Above, no option was passed, so the box only names the strategies. Pass some options, and they appear on the line of the strategy that receives them:
sol = solve(ocp; grid_size=50, scheme=:trapeze, max_iter=100, print_level=0)▫ This is OptimalControl 2.2.1-beta, solving with: collocation → adnlp → ipopt (cpu)
📦 Configuration:
├─ Discretizer: collocation (grid_size = 50, scheme = trapeze)
├─ Modeler: adnlp
└─ Solver: ipopt (max_iter = 100, print_level = 0)The box now shows:
Discretizer:
collocation, withgrid_size = 50andscheme = trapeze;Modeler:
adnlp, with no option of its own;Solver:
ipopt, withmax_iter = 100andprint_level = 0.
Each option was sent to the strategy that declares it: this is the automatic routing described in Options. Some defaults depend on the :cpu/:gpu parameter; they are shown too, tagged [cpu-dependent] (or [gpu-dependent]). For instance, MadNLP's linear solver:
using MadNLP
sol = solve(ocp, :madnlp; print_level=MadNLP.ERROR)▫ This is OptimalControl 2.2.1-beta, solving with: collocation → adnlp → madnlp (cpu)
📦 Configuration:
├─ Discretizer: collocation
├─ Modeler: adnlp
└─ Solver: madnlp (linear_solver = MumpsSolver [cpu-dependent], print_level = ERROR)Default values are not shown. To list every option of a strategy with its default, use describe, for instance describe(:ipopt) (see Options).
Turning the display off
sol = solve(ocp; display=false)It silences both the configuration box and the solver's own log. Useful once you trust a configuration and are solving in a loop, a test, or a script.
The defaults
Calling solve(ocp) with no strategy tokens is equivalent to:
solve(ocp, :collocation, :adnlp, :ipopt, :cpu)This quadruplet is the first entry of methods(), and completion always takes the first match, top to bottom (see Choosing a method for the full list and how partial descriptions are completed).
Two ways to steer it
solve can be pointed at a different strategy in two styles:
descriptive: symbolic tokens, e.g.
solve(ocp, :madnlp)(see Choosing a method),explicit: typed components, e.g.
solve(ocp; solver=OptimalControl.MadNLP())(see Explicit mode).
Use one style or the other in a call, not both: a typed component together with symbolic tokens is rejected.
julia> solve(ocp, :collocation; solver=OptimalControl.MadNLP())
IncorrectArgument → _explicit_or_descriptive, mode_detection.jl:60
│
│ Cannot mix explicit components with symbolic description
│
│ Got explicit components + symbolic description (:collocation,)
│ Expected either explicit components OR symbolic description
│
│ Context solve function call
│ Hint Use either solve(ocp; discretizer=..., modeler=..., solver=...) OR solve(ocp, :collocation, :adnlp, :ipopt)
└─When it fails
A solve that does not converge still returns a Solution. Check it rather than assuming success. Here we stop Ipopt before its first iteration:
sol = solve(ocp; max_iter=0, display=false, print_level=0)
println("successful: ", successful(sol)) # did the solver report success?
println("status: ", status(sol)) # a Symbol, e.g. :first_order, :max_iter
println("violation: ", constraints_violation(sol))successful: false
status: max_iter
violation: 1.1The solution holds the initial guess, which violates the boundary conditions. A converged solve reports successful(sol) == true, the status :first_order and a negligible violation:
sol = solve(ocp; display=false, print_level=0)
println("successful: ", successful(sol))
println("status: ", status(sol))
println("violation: ", constraints_violation(sol))successful: true
status: first_order
violation: 2.220446049250313e-16See also
Choosing a method — the full list of strategies and how partial descriptions are completed.
Options — routing, inspection with
describe, disambiguation.Explicit mode — build and pass typed components directly.
Initial guess — every way to hand
solvea starting point.