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.1.0-beta, solving with: collocationadnlpipopt (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.174

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