Skip to content

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 printed, and the two ways to steer it away from its defaults.

Quick start ​

julia
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                               = 5.550

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 exact using statement that fixes it.

Reading the display ​

By default solve prints a configuration table before running: which discretizer, modeler, and solver were selected, and every option that ends up applied to each — tagged by where the value came from:

  • :user — you passed it explicitly,

  • :default — the strategy's own default,

  • :computed — derived from the problem (e.g. a grid size picked from the time span).

This is the fastest way to answer "what did solve actually do with the call I just wrote?" without reading source.

Turning the display off ​

julia
sol = solve(ocp; display=false)

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:

julia
solve(ocp, :collocation, :adnlp, :ipopt, :cpu)

This particular quadruplet isn't special-cased — it's simply 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 component instances, e.g. solve(ocp; solver=OptimalControl.MadNLP()) (see Explicit mode).

The one thing worth knowing before either of those pages: which mode you're in is decided by the type of a keyword's value, never by the keyword's name. Any keyword argument whose value isa AbstractDiscretizer, AbstractNLPModeler, or AbstractNLPSolver switches solve into explicit mode, no matter what that keyword is called. Mixing a typed component with a non-empty symbolic description is rejected outright:

julia
julia> using MadNLP

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 doesn't converge still returns a Solution — inspect it rather than assuming success:

julia
println(successful(sol))   # true/false — did the solver report success?
println(status(sol))       # a Symbol, e.g. :first_order, :max_iter
println(message(sol))      # the solver's own message
println(constraints_violation(sol))
true
first_order
Ipopt/generic
2.220446049250313e-16

See also ​