Skip to content

Commit 1364060

Browse files
committed
try-catch for optimizers without warm-start support
1 parent a18dfa5 commit 1364060

File tree

2 files changed

+64
-52
lines changed

2 files changed

+64
-52
lines changed

example/juMPC.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ initstate!(ssKalmanFilter1,[0,0],[2,1])
6464
nx = linModel4.nx
6565
kf = KalmanFilter(linModel4, σP0=10*ones(nx), σQ=0.01*ones(nx), σR=[0.1, 0.1], σQ_int=0.05*ones(2), σP0_int=10*ones(2))
6666

67-
mpc = LinMPC(kf, Hp=15, Hc=1, Mwt=[1, 1] , Nwt=[0.1, 0.1], Cwt=1e6)
67+
mpc = LinMPC(kf, Hp=15, Hc=1, Mwt=[1, 1] , Nwt=[0.1, 0.1], Cwt=1e5)#, optim=Model(DAQP.Optimizer))
6868

6969
setconstraint!(mpc, c_umin=[0,0], c_umax=[0,0])
7070
setconstraint!(mpc, c_ŷmin=[1,1], c_ŷmax=[1,1])

src/predictive_control.jl

Lines changed: 63 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,10 @@ julia> u = mpc([5]); round.(u, digits=3)
1919
"""
2020
abstract type PredictiveController end
2121

22-
mutable struct OptimData
23-
model::JuMP.Model
24-
J ::Float64
22+
mutable struct OptimInfo
2523
ΔŨ::Vector{Float64}
2624
ϵ ::Union{Nothing, Float64}
25+
J ::Float64
2726
u ::Vector{Float64}
2827
U ::Vector{Float64}
2928
::Vector{Float64}
@@ -36,7 +35,8 @@ end
3635
struct LinMPC <: PredictiveController
3736
model::LinModel
3837
estim::StateEstimator
39-
optim::OptimData
38+
optim::JuMP.Model
39+
info::OptimInfo
4040
Hp::Int
4141
Hc::Int
4242
M_Hp::Diagonal{Float64}
@@ -76,7 +76,7 @@ struct LinMPC <: PredictiveController
7676
Ps::Matrix{Float64}
7777
Yop::Vector{Float64}
7878
Dop::Vector{Float64}
79-
function LinMPC(estim, Hp, Hc, Mwt, Nwt, Lwt, Cwt, ru)
79+
function LinMPC(estim, Hp, Hc, Mwt, Nwt, Lwt, Cwt, ru, optim)
8080
model = estim.model
8181
nu, ny = model.nu, model.ny
8282
validate_weights(model, Hp, Hc, Mwt, Nwt, Lwt, Cwt, ru)
@@ -107,27 +107,27 @@ struct LinMPC <: PredictiveController
107107
= init_quadprog(Ẽ, S̃_Hp, M_Hp, Ñ_Hc, L_Hp)
108108
Ks, Ps = init_stochpred(estim, Hp)
109109
Yop, Dop = repeat(model.yop, Hp), repeat(model.dop, Hp)
110-
u, U = copy(model.uop), repeat(model.uop, Hp)
111-
ϵ = isinf(C) ? nothing : 0.0 # C = Inf means hard constraints only
112-
ŷ, Ŷ = copy(model.yop), repeat(model.yop, Hp)
113-
ŷs, Ŷs = zeros(ny), zeros(ny*Hp)
114110
nvar = size(P̃, 1)
115-
optmodel = Model(OSQP.MathOptInterfaceOSQP.Optimizer, add_bridges=false) # default to OSQP solver
116-
set_silent(optmodel)
117-
@variable(optmodel, ΔŨ[1:nvar])
111+
set_silent(optim)
112+
@variable(optim, ΔŨ[1:nvar])
118113
# dummy q̃ value (the vector is updated just before optimization):
119-
= zeros(nvar)
120-
@objective(optmodel, Min, obj_quadprog(ΔŨ, P̃, q̃))
114+
= zeros(nvar)
115+
@objective(optim, Min, obj_quadprog(ΔŨ, P̃, q̃))
121116
A = [A_Umin; A_Umax; A_ΔŨmin; A_ΔŨmax; A_Ŷmin; A_Ŷmax]
122117
# dummy b vector to detect Inf values (b is updated just before optimization):
123118
b = [-Umin; +Umax; -ΔŨmin; +ΔŨmax; -Ŷmin; +Ŷmax]
124119
i_nonInf = .!isinf.(b)
125120
A = A[i_nonInf, :]
126121
b = b[i_nonInf]
127-
@constraint(optmodel, constraint_lin, A*ΔŨ .≤ b)
128-
optim = OptimData(optmodel, 0, zeros(nvar), ϵ, u, U, ŷ, Ŷ, ŷs, Ŷs)
122+
@constraint(optim, constraint_lin, A*ΔŨ .≤ b)
123+
ΔŨ0 = zeros(nvar)
124+
ϵ = isinf(C) ? nothing : 0.0 # C = Inf means hard constraints only
125+
u, U = copy(model.uop), repeat(model.uop, Hp)
126+
ŷ, Ŷ = copy(model.yop), repeat(model.yop, Hp)
127+
ŷs, Ŷs = zeros(ny), zeros(ny*Hp)
128+
info = OptimInfo(ΔŨ0, ϵ, 0, u, U, ŷ, Ŷ, ŷs, Ŷs)
129129
return new(
130-
model, estim, optim,
130+
model, estim, optim, info,
131131
Hp, Hc,
132132
M_Hp, Ñ_Hc, L_Hp, C, R̂u,
133133
Umin, Umax, ΔŨmin, ΔŨmax, Ŷmin, Ŷmax,
@@ -195,7 +195,9 @@ arguments.
195195
- `Nwt=fill(0.1,model.nu)` : main diagonal of ``\mathbf{N}`` weight matrix (vector)
196196
- `Lwt=fill(0.0,model.nu)` : main diagonal of ``\mathbf{L}`` weight matrix (vector)
197197
- `Cwt=1e5` : slack variable weight ``C`` (scalar), use `Cwt=Inf` for hard constraints only
198-
- `ru=model.uop`: manipulated input setpoints ``\mathbf{r_u}`` (vector)
198+
- `ru=model.uop` : manipulated input setpoints ``\mathbf{r_u}`` (vector)
199+
- `optim=JuMP.Model(OSQP.MathOptInterfaceOSQP.Optimizer)` : the MPC quadratic optimizer,
200+
provided as a [`JuMP.Model`](https://jump.dev/JuMP.jl/stable/reference/models/#JuMP.Model)
199201
200202
# Extended Help
201203
Manipulated inputs setpoints ``\mathbf{r_u}`` are not common but they can be interesting
@@ -220,7 +222,8 @@ function LinMPC(
220222
Nwt = fill(0.1, estim.model.nu),
221223
Lwt = fill(0.0, estim.model.nu),
222224
Cwt = 1e5,
223-
ru = estim.model.uop
225+
ru = estim.model.uop,
226+
optim::JuMP.Model = JuMP.Model(OSQP.MathOptInterfaceOSQP.Optimizer)
224227
)
225228
isa(estim.model, LinModel) || error("estim.model type must be LinModel")
226229
poles = eigvals(estim.model.A)
@@ -232,7 +235,7 @@ function LinMPC(
232235
@warn("prediction horizon Hp ($Hp) ≤ number of delays in model "*
233236
"($nk), the closed-loop system may be zero-gain (unresponsive) or unstable")
234237
end
235-
return LinMPC(estim, Hp, Hc, Mwt, Nwt, Lwt, Cwt, ru)
238+
return LinMPC(estim, Hp, Hc, Mwt, Nwt, Lwt, Cwt, ru, optim)
236239
end
237240

238241
@doc raw"""
@@ -377,10 +380,10 @@ function setconstraint!(
377380
i_nonInf = .!isinf.(b)
378381
A = A[i_nonInf, :]
379382
b = b[i_nonInf]
380-
ΔŨ = mpc.optim.model[:ΔŨ]
381-
delete(mpc.optim.model, mpc.optim.model[:constraint_lin])
382-
unregister(mpc.optim.model, :constraint_lin)
383-
@constraint(mpc.optim.model, constraint_lin, A*ΔŨ .≤ b)
383+
ΔŨ = mpc.optim[:ΔŨ]
384+
delete(mpc.optim, mpc.optim[:constraint_lin])
385+
unregister(mpc.optim, :constraint_lin)
386+
@constraint(mpc.optim, constraint_lin, A*ΔŨ .≤ b)
384387
return mpc
385388
end
386389

@@ -442,13 +445,13 @@ function moveinput!(
442445
::Vector{<:Real} = repeat(d, mpc.Hp),
443446
ym::Union{Vector{<:Real}, Nothing} = nothing
444447
)
445-
lastu = mpc.optim.u
448+
lastu = mpc.info.u
446449
x̂d, x̂s = split_state(mpc.estim)
447450
ŷs, Ŷs = predict_stoch(mpc, mpc.estim, x̂s, d, ym)
448451
F, q̃, p = init_prediction(mpc, mpc.model, d, D̂, Ŷs, R̂y, x̂d, lastu)
449452
b = init_constraint(mpc, mpc.model, F, lastu)
450453
ΔŨ, J = optim_objective!(mpc, b, q̃, p)
451-
write_optimdata!(mpc, ΔŨ, J, ŷs, Ŷs, lastu, F, ym, d)
454+
write_info!(mpc, ΔŨ, J, ŷs, Ŷs, lastu, F, ym, d)
452455
Δu = ΔŨ[1:mpc.model.nu] # receding horizon principle: only Δu(k) is used (first one)
453456
u = lastu + Δu
454457
return u
@@ -458,11 +461,11 @@ end
458461
"""
459462
initstate!(mpc::PredictiveController, u, ym, d=Float64[])
460463
461-
Init `mpc.optim` and the states of `mpc.estim` [`StateEstimator`](@ref).
464+
Init `mpc.info` and the states of `mpc.estim` [`StateEstimator`](@ref).
462465
"""
463466
function initstate!(mpc::PredictiveController, u, ym, d=Float64[])
464-
mpc.optim.u = u
465-
mpc.optim.ΔŨ .= 0
467+
mpc.info.u = u
468+
mpc.info.ΔŨ .= 0
466469
return initstate!(mpc.estim, u, ym, d)
467470
end
468471

@@ -563,41 +566,50 @@ end
563566
Optimize the `mpc` quadratic objective function for [`LinMPC`](@ref) type.
564567
"""
565568
function optim_objective!(mpc::LinMPC, b, q̃, p)
566-
optmodel = mpc.optim.model
567-
ΔŨ = optmodel[:ΔŨ]
568-
lastΔŨ = mpc.optim.ΔŨ
569-
set_objective_function(optmodel, obj_quadprog(ΔŨ, mpc.P̃, q̃))
570-
set_normalized_rhs.(optmodel[:constraint_lin], b)
571-
# initial ΔŨ (warm start): [Δu_{k-1}(k); Δu_{k-1}(k+1); ... ; 0_{nu × 1}]
569+
optim = mpc.optim
570+
ΔŨ = optim[:ΔŨ]
571+
lastΔŨ = mpc.info.ΔŨ
572+
set_objective_function(optim, obj_quadprog(ΔŨ, mpc.P̃, q̃))
573+
set_normalized_rhs.(optim[:constraint_lin], b)
574+
# initial ΔŨ (warm-start): [Δu_{k-1}(k); Δu_{k-1}(k+1); ... ; 0_{nu × 1}]
572575
ΔŨ0 = [lastΔŨ[(mpc.model.nu+1):(mpc.Hc*mpc.model.nu)]; zeros(mpc.model.nu)]
573576
# if soft constraints, append the last slack value ϵ_{k-1}:
574577
!isinf(mpc.C) && (ΔŨ0 = [ΔŨ0; lastΔŨ[end]])
575-
#set_start_value.(ΔŨ, ΔŨ0)
576-
optimize!(optmodel)
577-
status = termination_status(optmodel)
578+
set_start_value.(ΔŨ, ΔŨ0)
579+
try
580+
optimize!(optim)
581+
catch err
582+
if isa(err, MOI.UnsupportedAttribute{MOI.VariablePrimalStart})
583+
MOIU.reset_optimizer(optim)
584+
optimize!(optim)
585+
else
586+
rethrow(err)
587+
end
588+
end
589+
status = termination_status(optim)
578590
if !(status == OPTIMAL || status == LOCALLY_SOLVED)
579591
@warn "MPC termination status not OPTIMAL or LOCALLY_SOLVED ($status)"
580-
@debug solution_summary(optmodel)
592+
@debug solution_summary(optim)
581593
end
582594
ΔŨ = isfatal(status) ? ΔŨ0 : value.(ΔŨ) # fatal status : use last value
583-
J = objective_value(optmodel) + p # optimal objective value by adding constant p
595+
J = objective_value(optim) + p # optimal objective value by adding constant p
584596
return ΔŨ, J
585597
end
586598

587599
"""
588-
write_optimdata!(mpc::LinMPC, ΔŨ, ϵ, J, info, ŷs, Ŷs, lastu, F, ym, d)
600+
write_info!(mpc::LinMPC, ΔŨ, ϵ, J, info, ŷs, Ŷs, lastu, F, ym, d)
589601
590-
Write `mpc.optim` with the [`LinMPC`](@ref) optimization results.
602+
Write `mpc.info` with the [`LinMPC`](@ref) optimization results.
591603
"""
592-
function write_optimdata!(mpc::LinMPC, ΔŨ, J, ŷs, Ŷs, lastu, F, ym, d)
593-
mpc.optim.ΔŨ = ΔŨ
594-
mpc.optim.ϵ = isinf(mpc.C) ? nothing : ΔŨ[end]
595-
mpc.optim.J = J
596-
mpc.optim.U = mpc.S̃_Hp*ΔŨ + mpc.T_Hp*lastu
597-
mpc.optim.u = mpc.optim.U[1:mpc.model.nu]
598-
mpc.optim.= isa(mpc.estim, InternalModel) ? mpc.estim(ym, d) : mpc.estim(d)
599-
mpc.optim.= mpc.*ΔŨ + F
600-
mpc.optim.ŷs, mpc.optim.Ŷs = ŷs, Ŷs
604+
function write_info!(mpc::LinMPC, ΔŨ, J, ŷs, Ŷs, lastu, F, ym, d)
605+
mpc.info.ΔŨ = ΔŨ
606+
mpc.info.ϵ = isinf(mpc.C) ? nothing : ΔŨ[end]
607+
mpc.info.J = J
608+
mpc.info.U = mpc.S̃_Hp*ΔŨ + mpc.T_Hp*lastu
609+
mpc.info.u = mpc.info.U[1:mpc.model.nu]
610+
mpc.info.= isa(mpc.estim, InternalModel) ? mpc.estim(ym, d) : mpc.estim(d)
611+
mpc.info.= mpc.*ΔŨ + F
612+
mpc.info.ŷs, mpc.info.Ŷs = ŷs, Ŷs
601613
end
602614

603615
"Repeat predictive controller constraints over prediction `Hp` and control `Hc` horizons."

0 commit comments

Comments
 (0)