Skip to content

Commit 3db0f96

Browse files
committed
wip: multiple shooting transcription
1 parent ac38d81 commit 3db0f96

File tree

5 files changed

+85
-73
lines changed

5 files changed

+85
-73
lines changed

src/controller/construct.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -379,10 +379,10 @@ function setconstraint!(
379379
)
380380
A = con.A[con.i_b, :]
381381
b = con.b[con.i_b]
382-
ΔŨvar::Vector{JuMP.VariableRef} = optim[:ΔŨvar]
382+
Z̃var::Vector{JuMP.VariableRef} = optim[:Z̃var]
383383
JuMP.delete(optim, optim[:linconstraint])
384384
JuMP.unregister(optim, :linconstraint)
385-
@constraint(optim, linconstraint, A*ΔŨvar .≤ b)
385+
@constraint(optim, linconstraint, A*Z̃var .≤ b)
386386
set_nonlincon!(mpc, model, optim)
387387
else
388388
i_b, i_g = init_matconstraint_mpc(

src/controller/execute.jl

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ function moveinput!(
6767
validate_args(mpc, ry, d, D̂, R̂y, R̂u)
6868
initpred!(mpc, mpc.estim.model, d, D̂, R̂y, R̂u)
6969
linconstraint!(mpc, mpc.estim.model)
70-
linconstrainteq!(mpc, mpc.transcription)
70+
linconstrainteq!(mpc, mpc.estim.model, mpc.transcription)
7171
= optim_objective!(mpc)
7272
return getinput(mpc, Z̃)
7373
end
@@ -268,10 +268,11 @@ predictstoch!(Ŷs, mpc::PredictiveController, ::StateEstimator) = (Ŷs .= 0; n
268268
@doc raw"""
269269
linconstraint!(mpc::PredictiveController, model::LinModel)
270270
271-
Set `b` vector for the linear model inequality constraints (``\mathbf{A ΔŨ ≤ b}``).
271+
Set `b` vector for the linear model inequality constraints (``\mathbf{A ≤ b}``).
272272
273-
Also init ``\mathbf{f_x̂} = \mathbf{g_x̂ d}(k) + \mathbf{j_x̂ D̂} + \mathbf{k_x̂ x̂_0}(k) + \mathbf{v_x̂ u}(k-1) + \mathbf{b_x̂}``
274-
vector for the terminal constraints, see [`init_predmat`](@ref).
273+
Also init ``\mathbf{f_x̂} = \mathbf{g_x̂ d_0}(k) + \mathbf{j_x̂ D̂_0} + \mathbf{k_x̂ x̂_0}(k) +
274+
\mathbf{v_x̂ u_0}(k-1) + \mathbf{b_x̂}`` vector for the terminal constraints, see
275+
[`init_predmat`](@ref).
275276
"""
276277
function linconstraint!(mpc::PredictiveController, model::LinModel)
277278
nU, nΔŨ, nY = length(mpc.con.U0min), length(mpc.con.ΔŨmin), length(mpc.con.Y0min)
@@ -324,22 +325,6 @@ function linconstraint!(mpc::PredictiveController, ::SimModel)
324325
return nothing
325326
end
326327

327-
linconstrainteq!(mpc::PredictiveController, transcription::SingleShooting) = nothing
328-
function linconstrainteq!(mpc::PredictiveController, transcription::MultipleShooting)
329-
nx̂, Fŝ = mpc.estim.nx̂, mpc.con.Fŝ
330-
Fŝ .= mpc.con.Bŝ
331-
mul!(Fŝ, mpc.con.Kŝ, mpc.estim.x̂0, 1, 1)
332-
mul!(Fŝ, mpc.con.Vŝ, mpc.estim.lastu0, 1, 1)
333-
if mpc.estim.model.nd 0
334-
mul!(Fŝ, mpc.con.Gŝ, mpc.d0, 1, 1)
335-
mul!(Fŝ, mpc.con.Jŝ, mpc.D̂0, 1, 1)
336-
end
337-
mpc.con.beq .= @. -Fŝ
338-
linconeq = mpc.optim[:linconstrainteq]
339-
JuMP.set_normalized_rhs(linconeq, mpc.con.beq)
340-
return nothing
341-
end
342-
343328
@doc raw"""
344329
predict!(Ŷ0, x̂0, _, _, _, mpc::PredictiveController, model::LinModel, ΔŨ) -> Ŷ0, x̂0end
345330

src/controller/explicitmpc.jl

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -184,18 +184,18 @@ end
184184
linconstraint!(::ExplicitMPC, ::LinModel) = nothing
185185

186186
@doc raw"""
187-
optim_objective!(mpc::ExplicitMPC) -> ΔŨ
187+
optim_objective!(mpc::ExplicitMPC) ->
188188
189189
Analytically solve the optimization problem for [`ExplicitMPC`](@ref).
190190
191-
The solution is ``\mathbf{ΔŨ = - H̃^{-1} q̃}``, see [`init_quadprog`](@ref).
191+
The solution is ``\mathbf{ = - H̃^{-1} q̃}``, see [`init_quadprog`](@ref).
192192
"""
193-
optim_objective!(mpc::ExplicitMPC) = lmul!(-1, ldiv!(mpc.ΔŨ, mpc.H̃_chol, mpc.q̃))
193+
optim_objective!(mpc::ExplicitMPC) = lmul!(-1, ldiv!(mpc., mpc.H̃_chol, mpc.q̃))
194194

195195
"Compute the predictions but not the terminal states if `mpc` is an [`ExplicitMPC`](@ref)."
196-
function predict!(Ŷ, x̂, _ , _ , _ , mpc::ExplicitMPC, ::LinModel, ΔŨ)
196+
function predict!(Ŷ, x̂, _ , _ , _ , mpc::ExplicitMPC, ::LinModel, )
197197
# in-place operations to reduce allocations :
198-
Ŷ .= mul!(Ŷ, mpc.Ẽ, ΔŨ) .+ mpc.F
198+
Ŷ .= mul!(Ŷ, mpc.Ẽ, ) .+ mpc.F
199199
x̂ .= NaN
200200
return Ŷ, x̂
201201
end

src/controller/nonlinmpc.jl

Lines changed: 46 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -524,10 +524,10 @@ Inspired from: [User-defined operators with vector outputs](https://jump.dev/JuM
524524
function get_optim_functions(mpc::NonLinMPC, ::JuMP.GenericModel{JNT}) where JNT<:Real
525525
model = mpc.estim.model
526526
nu, ny, nx̂, nϵ, Hp = model.nu, model.ny, mpc.estim.nx̂, mpc.nϵ, mpc.Hp
527-
ng, nc, nΔŨ, nU, nŶ = length(mpc.con.i_g), mpc.con.nc, length(mpc.ΔŨ), Hp*nu, Hp*ny
527+
ng, nc, nZ̃, nU, nŶ = length(mpc.con.i_g), mpc.con.nc, length(mpc.), Hp*nu, Hp*ny
528528
nUe, nŶe = nU + nu, nŶ + ny
529-
Ncache = nΔŨ + 3
530-
ΔŨ_cache::DiffCache{Vector{JNT}, Vector{JNT}} = DiffCache(zeros(JNT, nΔŨ), Ncache)
529+
Ncache = nZ̃ + 3
530+
Z̃_cache::DiffCache{Vector{JNT}, Vector{JNT}} = DiffCache(zeros(JNT, nZ̃), Ncache)
531531
Ŷe_cache::DiffCache{Vector{JNT}, Vector{JNT}} = DiffCache(zeros(JNT, nŶe), Ncache)
532532
Ue_cache::DiffCache{Vector{JNT}, Vector{JNT}} = DiffCache(zeros(JNT, nUe), Ncache)
533533
Ȳ_cache::DiffCache{Vector{JNT}, Vector{JNT}} = DiffCache(zeros(JNT, nŶ), Ncache)
@@ -538,91 +538,91 @@ function get_optim_functions(mpc::NonLinMPC, ::JuMP.GenericModel{JNT}) where JNT
538538
û0_cache::DiffCache{Vector{JNT}, Vector{JNT}} = DiffCache(zeros(JNT, nu), Ncache)
539539
g_cache::DiffCache{Vector{JNT}, Vector{JNT}} = DiffCache(zeros(JNT, ng), Ncache)
540540
gc_cache::DiffCache{Vector{JNT}, Vector{JNT}} = DiffCache(zeros(JNT, nc), Ncache)
541-
function update_simulations!(ΔŨ, ΔŨtup::NTuple{N, T}) where {N, T<:Real}
542-
if any(new !== old for (new, old) in zip(ΔŨtup, ΔŨ)) # new ΔŨtup, update predictions:
543-
ΔŨ1 = ΔŨtup[begin]
544-
for i in eachindex(ΔŨtup)
545-
ΔŨ[i] = ΔŨtup[i] # ΔŨ .= ΔŨtup seems to produce a type instability
541+
function update_simulations!(Z̃, Z̃tup::NTuple{N, T}) where {N, T<:Real}
542+
if any(new !== old for (new, old) in zip(Z̃tup, Z̃)) # new Z̃tup, update predictions:
543+
Z̃1 = Z̃tup[begin]
544+
for i in eachindex(Z̃tup)
545+
[i] = Z̃tup[i] # .= Z̃tup seems to produce a type instability
546546
end
547-
Ŷe, Ue = get_tmp(Ŷe_cache, ΔŨ1), get_tmp(Ue_cache, ΔŨ1)
548-
Ȳ, Ū = get_tmp(Ȳ_cache, ΔŨ1), get_tmp(Ū_cache, ΔŨ1)
549-
x̂0, x̂0next = get_tmp(x̂0_cache, ΔŨ1), get_tmp(x̂0next_cache, ΔŨ1)
550-
u0, û0 = get_tmp(u0_cache, ΔŨ1), get_tmp(û0_cache, ΔŨ1)
551-
gc, g = get_tmp(gc_cache, ΔŨ1), get_tmp(g_cache, ΔŨ1)
552-
Ŷ0, x̂0end = predict!(Ȳ, x̂0, x̂0next, u0, û0, mpc, model, ΔŨ)
553-
Ŷe, Ue = extended_predictions!(Ŷe, Ue, Ū, mpc, model, Ŷ0, ΔŨ)
554-
ϵ = (nϵ 0) ? ΔŨ[end] : zero(T) # ϵ = 0 if nϵ == 0 (meaning no relaxation)
547+
Ŷe, Ue = get_tmp(Ŷe_cache, Z̃1), get_tmp(Ue_cache, Z̃1)
548+
Ȳ, Ū = get_tmp(Ȳ_cache, Z̃1), get_tmp(Ū_cache, Z̃1)
549+
x̂0, x̂0next = get_tmp(x̂0_cache, Z̃1), get_tmp(x̂0next_cache, Z̃1)
550+
u0, û0 = get_tmp(u0_cache, Z̃1), get_tmp(û0_cache, Z̃1)
551+
gc, g = get_tmp(gc_cache, Z̃1), get_tmp(g_cache, Z̃1)
552+
Ŷ0, x̂0end = predict!(Ȳ, x̂0, x̂0next, u0, û0, mpc, model, )
553+
Ŷe, Ue = extended_predictions!(Ŷe, Ue, Ū, mpc, model, Ŷ0, )
554+
ϵ = (nϵ 0) ? [end] : zero(T) # ϵ = 0 if nϵ == 0 (meaning no relaxation)
555555
gc = con_custom!(gc, mpc, Ue, Ŷe, ϵ)
556556
g = con_nonlinprog!(g, mpc, model, x̂0end, Ŷ0, gc, ϵ)
557557
end
558558
return nothing
559559
end
560-
function Jfunc(ΔŨtup::Vararg{T, N}) where {N, T<:Real}
561-
ΔŨ1 = ΔŨtup[begin]
562-
ΔŨ = get_tmp(ΔŨ_cache, ΔŨ1)
563-
update_simulations!(ΔŨ, ΔŨtup)
564-
Ȳ, Ū = get_tmp(Ȳ_cache, ΔŨ1), get_tmp(Ū_cache, ΔŨ1)
565-
Ŷe, Ue = get_tmp(Ŷe_cache, ΔŨ1), get_tmp(Ue_cache, ΔŨ1)
566-
return obj_nonlinprog!(Ȳ, Ū, mpc, model, Ue, Ŷe, ΔŨ)::T
560+
function Jfunc(Z̃tup::Vararg{T, N}) where {N, T<:Real}
561+
Z̃1 = Z̃tup[begin]
562+
= get_tmp(Z̃_cache, Z̃1)
563+
update_simulations!(Z̃, Z̃tup)
564+
Ȳ, Ū = get_tmp(Ȳ_cache, Z̃1), get_tmp(Ū_cache, Z̃1)
565+
Ŷe, Ue = get_tmp(Ŷe_cache, Z̃1), get_tmp(Ue_cache, Z̃1)
566+
return obj_nonlinprog!(Ȳ, Ū, mpc, model, Ue, Ŷe, )::T
567567
end
568-
function gfunc_i(i, ΔŨtup::NTuple{N, T}) where {N, T<:Real}
569-
ΔŨ1 = ΔŨtup[begin]
570-
ΔŨ = get_tmp(ΔŨ_cache, ΔŨ1)
571-
update_simulations!(ΔŨ, ΔŨtup)
572-
g = get_tmp(g_cache, ΔŨ1)
568+
function gfunc_i(i, Z̃tup::NTuple{N, T}) where {N, T<:Real}
569+
Z̃1 = Z̃tup[begin]
570+
= get_tmp(Z̃_cache, Z̃1)
571+
update_simulations!(Z̃, Z̃tup)
572+
g = get_tmp(g_cache, Z̃1)
573573
return g[i]::T
574574
end
575575
gfuncs = Vector{Function}(undef, ng)
576576
for i in 1:ng
577577
# this is another syntax for anonymous function, allowing parameters T and N:
578-
gfuncs[i] = function (ΔŨtup::Vararg{T, N}) where {N, T<:Real}
579-
return gfunc_i(i, ΔŨtup)
578+
gfuncs[i] = function (Z̃tup::Vararg{T, N}) where {N, T<:Real}
579+
return gfunc_i(i, Z̃tup)
580580
end
581581
end
582582
return Jfunc, gfuncs
583583
end
584584

585585
function init_nonlincon!(mpc::NonLinMPC, ::LinModel, gfuncs::Vector{<:Function})
586586
optim, con = mpc.optim, mpc.con
587-
nΔŨ = length(mpc.ΔŨ)
587+
nZ̃ = length(mpc.)
588588
if length(con.i_g) 0
589589
i_base = 0
590590
for i in 1:con.nc
591591
name = Symbol("g_c_$i")
592-
optim[name] = JuMP.add_nonlinear_operator(optim, nΔŨ, gfuncs[i_base+i]; name)
592+
optim[name] = JuMP.add_nonlinear_operator(optim, nZ̃, gfuncs[i_base+i]; name)
593593
end
594594
end
595595
return nothing
596596
end
597597

598598
function init_nonlincon!(mpc::NonLinMPC, ::NonLinModel, gfuncs::Vector{<:Function})
599599
optim, con = mpc.optim, mpc.con
600-
ny, nx̂, Hp, nΔŨ = mpc.estim.model.ny, mpc.estim.nx̂, mpc.Hp, length(mpc.ΔŨ)
600+
ny, nx̂, Hp, nZ̃ = mpc.estim.model.ny, mpc.estim.nx̂, mpc.Hp, length(mpc.)
601601
if length(con.i_g) 0
602602
i_base = 0
603603
for i in eachindex(con.Y0min)
604604
name = Symbol("g_Y0min_$i")
605-
optim[name] = JuMP.add_nonlinear_operator(optim, nΔŨ, gfuncs[i_base+i]; name)
605+
optim[name] = JuMP.add_nonlinear_operator(optim, nZ̃, gfuncs[i_base+i]; name)
606606
end
607607
i_base = 1Hp*ny
608608
for i in eachindex(con.Y0max)
609609
name = Symbol("g_Y0max_$i")
610-
optim[name] = JuMP.add_nonlinear_operator(optim, nΔŨ, gfuncs[i_base+i]; name)
610+
optim[name] = JuMP.add_nonlinear_operator(optim, nZ̃, gfuncs[i_base+i]; name)
611611
end
612612
i_base = 2Hp*ny
613613
for i in eachindex(con.x̂0min)
614614
name = Symbol("g_x̂0min_$i")
615-
optim[name] = JuMP.add_nonlinear_operator(optim, nΔŨ, gfuncs[i_base+i]; name)
615+
optim[name] = JuMP.add_nonlinear_operator(optim, nZ̃, gfuncs[i_base+i]; name)
616616
end
617617
i_base = 2Hp*ny + nx̂
618618
for i in eachindex(con.x̂0max)
619619
name = Symbol("g_x̂0max_$i")
620-
optim[name] = JuMP.add_nonlinear_operator(optim, nΔŨ, gfuncs[i_base+i]; name)
620+
optim[name] = JuMP.add_nonlinear_operator(optim, nZ̃, gfuncs[i_base+i]; name)
621621
end
622622
i_base = 2Hp*ny + 2nx̂
623623
for i in 1:con.nc
624624
name = Symbol("g_c_$i")
625-
optim[name] = JuMP.add_nonlinear_operator(optim, nΔŨ, gfuncs[i_base+i]; name)
625+
optim[name] = JuMP.add_nonlinear_operator(optim, nZ̃, gfuncs[i_base+i]; name)
626626
end
627627
end
628628
return nothing
@@ -636,13 +636,13 @@ Set the custom nonlinear inequality constraints for `LinModel`.
636636
function set_nonlincon!(
637637
mpc::NonLinMPC, ::LinModel, optim::JuMP.GenericModel{JNT}
638638
) where JNT<:Real
639-
ΔŨvar = optim[:ΔŨvar]
639+
Z̃var = optim[:Z̃var]
640640
con = mpc.con
641641
nonlin_constraints = JuMP.all_constraints(optim, JuMP.NonlinearExpr, MOI.LessThan{JNT})
642642
map(con_ref -> JuMP.delete(optim, con_ref), nonlin_constraints)
643643
for i in 1:con.nc
644644
gfunc_i = optim[Symbol("g_c_$i")]
645-
@constraint(optim, gfunc_i(ΔŨvar...) <= 0)
645+
@constraint(optim, gfunc_i(Z̃var...) <= 0)
646646
end
647647
return nothing
648648
end
@@ -655,29 +655,29 @@ Also set output prediction `Ŷ` and terminal state `x̂end` constraints when no
655655
function set_nonlincon!(
656656
mpc::NonLinMPC, ::SimModel, optim::JuMP.GenericModel{JNT}
657657
) where JNT<:Real
658-
ΔŨvar = optim[:ΔŨvar]
658+
Z̃var = optim[:Z̃var]
659659
con = mpc.con
660660
nonlin_constraints = JuMP.all_constraints(optim, JuMP.NonlinearExpr, MOI.LessThan{JNT})
661661
map(con_ref -> JuMP.delete(optim, con_ref), nonlin_constraints)
662662
for i in findall(.!isinf.(con.Y0min))
663663
gfunc_i = optim[Symbol("g_Y0min_$(i)")]
664-
@constraint(optim, gfunc_i(ΔŨvar...) <= 0)
664+
@constraint(optim, gfunc_i(Z̃var...) <= 0)
665665
end
666666
for i in findall(.!isinf.(con.Y0max))
667667
gfunc_i = optim[Symbol("g_Y0max_$(i)")]
668-
@constraint(optim, gfunc_i(ΔŨvar...) <= 0)
668+
@constraint(optim, gfunc_i(Z̃var...) <= 0)
669669
end
670670
for i in findall(.!isinf.(con.x̂0min))
671671
gfunc_i = optim[Symbol("g_x̂0min_$(i)")]
672-
@constraint(optim, gfunc_i(ΔŨvar...) <= 0)
672+
@constraint(optim, gfunc_i(Z̃var...) <= 0)
673673
end
674674
for i in findall(.!isinf.(con.x̂0max))
675675
gfunc_i = optim[Symbol("g_x̂0max_$(i)")]
676-
@constraint(optim, gfunc_i(ΔŨvar...) <= 0)
676+
@constraint(optim, gfunc_i(Z̃var...) <= 0)
677677
end
678678
for i in 1:con.nc
679679
gfunc_i = optim[Symbol("g_c_$i")]
680-
@constraint(optim, gfunc_i(ΔŨvar...) <= 0)
680+
@constraint(optim, gfunc_i(Z̃var...) <= 0)
681681
end
682682
return nothing
683683
end

src/controller/transcription.jl

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,33 @@ function init_defectmat(
508508
return Eŝ, Gŝ, Jŝ, Kŝ, Vŝ, Bŝ
509509
end
510510

511+
@doc raw"""
512+
linconstrainteq!(
513+
mpc::PredictiveController, model::LinModel, transcription::MultipleShooting
514+
)
515+
516+
Set `beq` vector for the linear model equality constraints (``\mathbf{Aeq Z̃ = beq}``).
517+
518+
Also init ``\mathbf{F_ŝ} = \mathbf{G_ŝ d_0}(k) + \mathbf{J_ŝ D̂_0} + \mathbf{K_ŝ x̂_0}(k) +
519+
\mathbf{V_ŝ u_0}(k-1) + \mathbf{B_ŝ}``, see [`init_defectmat`](@ref).
520+
"""
521+
function linconstrainteq!(mpc::PredictiveController, model::LinModel, ::MultipleShooting)
522+
Fŝ = mpc.con.Fŝ
523+
Fŝ .= mpc.con.Bŝ
524+
mul!(Fŝ, mpc.con.Kŝ, mpc.estim.x̂0, 1, 1)
525+
mul!(Fŝ, mpc.con.Vŝ, mpc.estim.lastu0, 1, 1)
526+
if model.nd 0
527+
mul!(Fŝ, mpc.con.Gŝ, mpc.d0, 1, 1)
528+
mul!(Fŝ, mpc.con.Jŝ, mpc.D̂0, 1, 1)
529+
end
530+
mpc.con.beq .= @. -Fŝ
531+
linconeq = mpc.optim[:linconstrainteq]
532+
JuMP.set_normalized_rhs(linconeq, mpc.con.beq)
533+
return nothing
534+
end
535+
linconstrainteq!(::PredictiveController, ::SimModel, ::SingleShooting) = nothing
536+
linconstrainteq!(::PredictiveController, ::SimModel, ::MultipleShooting) = nothing
537+
511538
@doc raw"""
512539
set_warmstart!(mpc::PredictiveController, transcription::SingleShooting, Z̃var) -> Z̃0
513540

0 commit comments

Comments
 (0)