Skip to content

Multi-phase flows

Bang-bang switchings, jumps at a boundary arc's entry/exit, phase changes of any kind: a sequence of flows, each active on its own sub-interval, concatenated into one callable object — itself a flow.

julia
using OptimalControl
using OrdinaryDiffEqTsit5

Concatenating two flows

* glues two flows together at a switching time — the result is itself a flow:

julia
f1 = Flow(VectorField(x -> 0.0))    # constant
f2 = Flow(VectorField(x -> 1.0))    # unit slope

f = f1 * (1.0, f2)                  # f1 until t=1, then f2
typeof(f)
MultiPhaseStateFlow{Autonomous, Fixed, Tuple{StateFlow{Autonomous, Fixed, VectorFieldSystem{Autonomous, Fixed, OutOfPlace, VectorField{var"#2#3", Autonomous, Fixed, OutOfPlace}}, SciML{CPU, StrategyOptions{@NamedTuple{internalnorm::OptionValue{typeof(real_norm)}, alg::OptionValue{Tsit5{typeof(trivial_limiter!), typeof(trivial_limiter!), Serial}}, reltol::OptionValue{Float64}, save_everystep::OptionValue{Symbol}, abstol::OptionValue{Float64}, save_start::OptionValue{Symbol}, dense::OptionValue{Symbol}}}, Dict{Symbol, Any}, Dict{Symbol, Any}}}, StateFlow{Autonomous, Fixed, VectorFieldSystem{Autonomous, Fixed, OutOfPlace, VectorField{var"#5#6", Autonomous, Fixed, OutOfPlace}}, SciML{CPU, StrategyOptions{@NamedTuple{internalnorm::OptionValue{typeof(real_norm)}, alg::OptionValue{Tsit5{typeof(trivial_limiter!), typeof(trivial_limiter!), Serial}}, reltol::OptionValue{Float64}, save_everystep::OptionValue{Symbol}, abstol::OptionValue{Float64}, save_start::OptionValue{Symbol}, dense::OptionValue{Symbol}}}, Dict{Symbol, Any}, Dict{Symbol, Any}}}}, Vector{Real}, Vector{Any}} (alias for MultiPhaseFlow{CTBase.Traits.Autonomous, CTBase.Traits.Fixed, CTBase.Traits.StateDynamics, Tuple{Flow{CTBase.Traits.Autonomous, CTBase.Traits.Fixed, CTBase.Traits.StateDynamics, CTFlows.Systems.VectorFieldSystem{CTBase.Traits.Autonomous, CTBase.Traits.Fixed, CTBase.Traits.OutOfPlace, VectorField{Main.var"#2#3", CTBase.Traits.Autonomous, CTBase.Traits.Fixed, CTBase.Traits.OutOfPlace}}, SciML{CPU, CTBase.Strategies.StrategyOptions{@NamedTuple{internalnorm::CTBase.Options.OptionValue{typeof(CTSolvers.Integrators.real_norm)}, alg::CTBase.Options.OptionValue{Tsit5{typeof(OrdinaryDiffEqCore.trivial_limiter!), typeof(OrdinaryDiffEqCore.trivial_limiter!), FastBroadcast.Serial}}, reltol::CTBase.Options.OptionValue{Float64}, save_everystep::CTBase.Options.OptionValue{Symbol}, abstol::CTBase.Options.OptionValue{Float64}, save_start::CTBase.Options.OptionValue{Symbol}, dense::CTBase.Options.OptionValue{Symbol}}}, Dict{Symbol, Any}, Dict{Symbol, Any}}}, Flow{CTBase.Traits.Autonomous, CTBase.Traits.Fixed, CTBase.Traits.StateDynamics, CTFlows.Systems.VectorFieldSystem{CTBase.Traits.Autonomous, CTBase.Traits.Fixed, CTBase.Traits.OutOfPlace, VectorField{Main.var"#5#6", CTBase.Traits.Autonomous, CTBase.Traits.Fixed, CTBase.Traits.OutOfPlace}}, SciML{CPU, CTBase.Strategies.StrategyOptions{@NamedTuple{internalnorm::CTBase.Options.OptionValue{typeof(CTSolvers.Integrators.real_norm)}, alg::CTBase.Options.OptionValue{Tsit5{typeof(OrdinaryDiffEqCore.trivial_limiter!), typeof(OrdinaryDiffEqCore.trivial_limiter!), FastBroadcast.Serial}}, reltol::CTBase.Options.OptionValue{Float64}, save_everystep::CTBase.Options.OptionValue{Symbol}, abstol::CTBase.Options.OptionValue{Float64}, save_start::CTBase.Options.OptionValue{Symbol}, dense::CTBase.Options.OptionValue{Symbol}}}, Dict{Symbol, Any}, Dict{Symbol, Any}}}}, Array{Real, 1}, Array{Any, 1}})
julia
f(0.0, 0.0, 2.0)   # one unit of f1's zero slope, then one unit of f2's slope-1
0.9999999999999998

Jumps

A jump inserts a discontinuity in the state at the switching time — f1 * (t1, jump, f2), where jump is a plain function of the state:

julia
jump(x) = x + 10.0
fj = f1 * (1.0, jump, f2)
fj(0.0, 0.0, 2.0)   # +10 landed at t=1, then one more unit of slope
11.0

On a Hamiltonian flow, the jump can act on state and costate separately — a 4-arg form, h1 * (t, jump_x, jump_p, h2):

julia
htilde(x, p, u) = p * u - 0.5 * u^2
law(x, p) = p
h1 = Flow(PseudoHamiltonian(htilde), DynClosedLoop(law))
h2 = Flow(PseudoHamiltonian(htilde), DynClosedLoop(law))

hj = h1 * (1.0, x -> x, p -> p, h2)   # identity jumps, written out
typeof(hj)
MultiPhaseHamiltonianFlow{Autonomous, Fixed, Tuple{HamiltonianFlow{Autonomous, Fixed, HamiltonianSystem{Autonomous, Fixed, ComposedHamiltonian{Autonomous, Fixed, PseudoHamiltonian{typeof(htilde), Autonomous, Fixed}, ControlLaw{typeof(law), DynClosedLoopFeedback, Autonomous, Fixed}}, DifferentiationInterface{CPU, StrategyOptions{@NamedTuple{ad_backend::OptionValue{AutoForwardDiff{nothing, Nothing}}}}}}, SciML{CPU, StrategyOptions{@NamedTuple{internalnorm::OptionValue{typeof(real_norm)}, alg::OptionValue{Tsit5{typeof(trivial_limiter!), typeof(trivial_limiter!), Serial}}, reltol::OptionValue{Float64}, save_everystep::OptionValue{Symbol}, abstol::OptionValue{Float64}, save_start::OptionValue{Symbol}, dense::OptionValue{Symbol}}}, Dict{Symbol, Any}, Dict{Symbol, Any}}}, HamiltonianFlow{Autonomous, Fixed, HamiltonianSystem{Autonomous, Fixed, ComposedHamiltonian{Autonomous, Fixed, PseudoHamiltonian{typeof(htilde), Autonomous, Fixed}, ControlLaw{typeof(law), DynClosedLoopFeedback, Autonomous, Fixed}}, DifferentiationInterface{CPU, StrategyOptions{@NamedTuple{ad_backend::OptionValue{AutoForwardDiff{nothing, Nothing}}}}}}, SciML{CPU, StrategyOptions{@NamedTuple{internalnorm::OptionValue{typeof(real_norm)}, alg::OptionValue{Tsit5{typeof(trivial_limiter!), typeof(trivial_limiter!), Serial}}, reltol::OptionValue{Float64}, save_everystep::OptionValue{Symbol}, abstol::OptionValue{Float64}, save_start::OptionValue{Symbol}, dense::OptionValue{Symbol}}}, Dict{Symbol, Any}, Dict{Symbol, Any}}}}, Vector{Real}, Vector{Any}} (alias for MultiPhaseFlow{CTBase.Traits.Autonomous, CTBase.Traits.Fixed, CTBase.Traits.HamiltonianDynamics, Tuple{Flow{CTBase.Traits.Autonomous, CTBase.Traits.Fixed, CTBase.Traits.HamiltonianDynamics, CTFlows.Systems.HamiltonianSystem{CTBase.Traits.Autonomous, CTBase.Traits.Fixed, ComposedHamiltonian{CTBase.Traits.Autonomous, CTBase.Traits.Fixed, PseudoHamiltonian{typeof(Main.htilde), CTBase.Traits.Autonomous, CTBase.Traits.Fixed}, ControlLaw{typeof(Main.law), CTBase.Traits.DynClosedLoopFeedback, CTBase.Traits.Autonomous, CTBase.Traits.Fixed}}, CTBase.Differentiation.DifferentiationInterface{CPU, CTBase.Strategies.StrategyOptions{@NamedTuple{ad_backend::CTBase.Options.OptionValue{AutoForwardDiff{nothing, Nothing}}}}}}, SciML{CPU, CTBase.Strategies.StrategyOptions{@NamedTuple{internalnorm::CTBase.Options.OptionValue{typeof(CTSolvers.Integrators.real_norm)}, alg::CTBase.Options.OptionValue{Tsit5{typeof(OrdinaryDiffEqCore.trivial_limiter!), typeof(OrdinaryDiffEqCore.trivial_limiter!), FastBroadcast.Serial}}, reltol::CTBase.Options.OptionValue{Float64}, save_everystep::CTBase.Options.OptionValue{Symbol}, abstol::CTBase.Options.OptionValue{Float64}, save_start::CTBase.Options.OptionValue{Symbol}, dense::CTBase.Options.OptionValue{Symbol}}}, Dict{Symbol, Any}, Dict{Symbol, Any}}}, Flow{CTBase.Traits.Autonomous, CTBase.Traits.Fixed, CTBase.Traits.HamiltonianDynamics, CTFlows.Systems.HamiltonianSystem{CTBase.Traits.Autonomous, CTBase.Traits.Fixed, ComposedHamiltonian{CTBase.Traits.Autonomous, CTBase.Traits.Fixed, PseudoHamiltonian{typeof(Main.htilde), CTBase.Traits.Autonomous, CTBase.Traits.Fixed}, ControlLaw{typeof(Main.law), CTBase.Traits.DynClosedLoopFeedback, CTBase.Traits.Autonomous, CTBase.Traits.Fixed}}, CTBase.Differentiation.DifferentiationInterface{CPU, CTBase.Strategies.StrategyOptions{@NamedTuple{ad_backend::CTBase.Options.OptionValue{AutoForwardDiff{nothing, Nothing}}}}}}, SciML{CPU, CTBase.Strategies.StrategyOptions{@NamedTuple{internalnorm::CTBase.Options.OptionValue{typeof(CTSolvers.Integrators.real_norm)}, alg::CTBase.Options.OptionValue{Tsit5{typeof(OrdinaryDiffEqCore.trivial_limiter!), typeof(OrdinaryDiffEqCore.trivial_limiter!), FastBroadcast.Serial}}, reltol::CTBase.Options.OptionValue{Float64}, save_everystep::CTBase.Options.OptionValue{Symbol}, abstol::CTBase.Options.OptionValue{Float64}, save_start::CTBase.Options.OptionValue{Symbol}, dense::CTBase.Options.OptionValue{Symbol}}}, Dict{Symbol, Any}, Dict{Symbol, Any}}}}, Array{Real, 1}, Array{Any, 1}})

Jumps as callables

A jump argument can be a function (as above), nothing for the identity (no jump at all), or — accepted in addition to the two — a plain Vector added to the state/costate directly:

julia
h_identity = h1 * (1.0, nothing, nothing, h2)   # nothing, nothing ≡ no jump
h_vec = h1 * (1.0, [0.0, 0.0], h2)               # a bare vector jump also works

Calling a multi-phase flow

Exactly like any other flow — point form for the endpoint, trajectory form for the full path; variable= still mandatory on a NonFixed problem, same rule as everywhere else in this section.

Inspecting one

julia
n_phases(f)
2
julia
get_switching_time(f, 1)
1.0
julia
get_switching_times(f)
1-element Vector{Real}:
 1.0
julia
get_flow(f, 1) === f1
true
julia
length(get_flows(f))
2

get_jump(mp, i)/get_jumps(mp) read back the jump functions the same way, on a flow built with jumps.

Worked example: bang-bang time-optimal double integrator

Minimise the final time for  ,   , from    to . The optimum is  , reached by one switch at  :    then   .

julia
ocp = @def begin
    tf  R, variable
    t  [0, tf], time
    x = (q, v)  R², state
    u  R, control
    -1 u(t)  1
    q(0) == -1
    v(0) == 0
    q(tf) == 0
    v(tf) == 0
(t) == [v(t), u(t)]
    tf  min
end

f_plus = Flow(ocp, (x, p, v) -> 1.0)
f_minus = Flow(ocp, (x, p, v) -> -1.0)

t1 = 1.0
tf = 2.0
p0 = [1.0, 1.0]   # the PMP's predicted initial costate

f_bb = f_plus * (t1, f_minus)
xf, pf = f_bb(0.0, [-1.0, 0.0], p0, tf; variable=tf)
xf   # ≈ [0, 0], the target
-1.7586907688283167e-16

See also