Skip to content

Control-free problems ​

What this is for ​

Control-free problems are optimal control problems without a control. They are used to optimise constant parameters of a dynamical system, for instance to

  • identify unknown parameters from observed data (parameter estimation),

  • find the parameters that are best for a given performance criterion.

This page shows how to declare and solve such problems. For the full story of the two examples below, solved by both the direct and the indirect method, see Parameter estimation without a control.

How to declare it ​

There is no dedicated syntax: simply never declare a control. Declare a variable (the parameters), a time, a state, the dynamics and a cost, and omit the control line with @def, or never call control! with the functional API. The functional API page builds the parameter-estimation problem below both ways, side by side.

control!(pre, 0) is an error

With the functional API, a control-free problem is obtained by omission only. control!(pre, 0) throws an IncorrectArgument: a dimension must be positive.

julia
using OptimalControl
using NLPModelsIpopt
using Plots

Example: parameter estimation ​

A system with exponential growth,   ,  , where is an unknown growth rate. We have noisy observed data and estimate by minimising the squared error:

The data come from the model with  , plus a perturbation. There is no control line, only a variable:

julia
# observed data (analytical solution with λ = 0.5, plus a perturbation)
λ_true = 0.5
data(t) = 2 * exp(λ_true * t) + 2e-1 * sin(4π * t)

t0 = 0; tf = 2; x0 = 2
ocp = @def begin
    λ ∈ R, variable              # growth rate to estimate
    t ∈ [t0, tf], time
    x ∈ R, state

    x(t0) == x0
    ẋ(t) == λ * x(t)

    ∫((x(t) - data(t))^2) → min  # fit to observed data
end

The model knows it has no control:

julia
is_control_free(ocp)
true

It solves like any other problem. The estimated parameter is the optimal value of the variable:

julia
sol = solve(ocp; grid_size=20, display=false)
println("estimated λ = ", variable(sol), "   (true value: ", λ_true, ")")
estimated λ = 0.4960778956661449   (true value: 0.5)

The fitted state follows the data, up to the perturbation:

julia
plt = plot(sol, :state; size=(800, 400), label="Direct")
tg = time_grid(sol)
plot!(
    plt, tg, data.(tg);
    subplot=1, line=:dot, lw=2, label="Data", color=:black,
)

Example: harmonic oscillator ​

The same shape with a Mayer cost: minimise the pulsation of    under  ,   and  . The solution is  , and the smallest pulsation such that   is  :

julia
ocp = @def begin
    ω ∈ R, variable              # pulsation to minimise
    t ∈ [0, 1], time
    x = (q, v) ∈ R², state

    q(0) == 1.0
    v(0) == 0.0
    q(1) == 0.0

    ẋ(t) == [v(t), -ω^2 * q(t)]

    ω^2 → min
end

Solving it recovers :

julia
sol = solve(ocp; display=false)
println("ω = ", variable(sol), "   (π/2 = ", π / 2, ")")
ω = 1.570801494616592   (π/2 = 1.5707963267948966)

See also ​