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.
using OptimalControl
using OrdinaryDiffEqTsit5Concatenating two flows
* glues two flows together at a switching time — the result is itself a flow:
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}})f(0.0, 0.0, 2.0) # one unit of f1's zero slope, then one unit of f2's slope-10.9999999999999998Jumps
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:
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 slope11.0On a Hamiltonian flow, the jump can act on state and costate separately — a 4-arg form, h1 * (t, jump_x, jump_p, h2):
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:
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 worksCalling 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
n_phases(f)2get_switching_time(f, 1)1.0get_switching_times(f)1-element Vector{Real}:
1.0get_flow(f, 1) === f1truelength(get_flows(f))2get_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
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-16See also
Constrained arcs — assembling arcs around a boundary arc specifically, where the jump usually comes from a costate discontinuity.
Writing a shooting function — solving for the switching time(s) themselves, not just assuming them known as here.
From an OCP — the single-arc case this section generalises.
Time minimisation (bang–bang) — the full story behind the double-integrator example used throughout this page.