Skip to content

Commit 8daa441

Browse files
authored
Merge pull request #261 from JuliaControl/cond_hessian
added: ill-conditioned QP warning at controller construction
2 parents 438c8e2 + f1c3b5e commit 8daa441

File tree

8 files changed

+58
-27
lines changed

8 files changed

+58
-27
lines changed

.github/workflows/benchmark.yml

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,6 @@ on:
33
pull_request_target:
44
branches: [ main ]
55
types: [labeled, opened, synchronize, reopened]
6-
concurrency:
7-
# Skip intermediate builds: always.
8-
# Cancel intermediate builds: only if it is a pull request build.
9-
group: ${{ github.workflow }}-${{ github.ref }}
10-
cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }}
116
permissions:
127
pull-requests: write # needed to post comments
138
jobs:

docs/src/manual/nonlinmpc.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ Nonlinear MPC is more computationally expensive than [`LinMPC`](@ref). Solving t
331331
should always be faster than the sampling time ``T_s = 0.1`` s for real-time operation. This
332332
requirement is sometimes hard to meet on electronics or mechanical systems because of the
333333
fast dynamics. To ease the design and comparison with [`LinMPC`](@ref), the [`linearize`](@ref)
334-
function allows automatic linearization of [`NonLinModel`](@ref) based on [`ForwardDiff`](@extref ForwardDiff).
334+
function allows automatic linearization of [`NonLinModel`](@ref) defaulting to [`ForwardDiff`](@extref ForwardDiff).
335335
We first linearize `model` at the point ``θ = π`` rad and ``ω = τ = 0`` (inverted position):
336336

337337
```@example man_nonlin
@@ -341,7 +341,6 @@ linmodel = linearize(model, x=[π, 0], u=[0])
341341
A [`SteadyKalmanFilter`](@ref) and a [`LinMPC`](@ref) are designed from `linmodel`:
342342

343343
```@example man_nonlin
344-
345344
skf = SteadyKalmanFilter(linmodel; σQ, σR, nint_u, σQint_u)
346345
mpc = LinMPC(skf; Hp, Hc, Mwt, Nwt, Cwt=Inf)
347346
mpc = setconstraint!(mpc, umin=[-1.5], umax=[+1.5])

src/controller/construct.jl

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -564,7 +564,10 @@ function validate_args(mpc::PredictiveController, ry, d, D̂, R̂y, R̂u)
564564
end
565565

566566
@doc raw"""
567-
init_quadprog(model::LinModel, weights::ControllerWeights, Ẽ, P̃Δu, P̃u) -> H̃
567+
init_quadprog(
568+
model::LinModel, transcriptions::TranscriptionMethod, weights::ControllerWeights,
569+
Ẽ, P̃Δu, P̃u; warn_cond=1e6
570+
) -> H̃
568571
569572
Init the quadratic programming Hessian `H̃` for MPC.
570573
@@ -582,19 +585,43 @@ in which ``\mathbf{Ẽ}``, ``\mathbf{P̃_{Δu}}`` and ``\mathbf{P̃_{u}}`` matri
582585
at [`relaxŶ`](@ref), [`relaxΔU`](@ref) and [`relaxU`](@ref) documentation, respectively. The
583586
vector ``\mathbf{q̃}`` and scalar ``r`` need recalculation each control period ``k``, see
584587
[`initpred!`](@ref). ``r`` does not impact the minima position. It is thus useless at
585-
optimization but required to evaluate the minimal ``J`` value.
588+
optimization but required to evaluate the minimal ``J`` value. A `@warn` will be displayed
589+
if the condition number `cond(H̃) ≥ warn_cond` and `transcription` is a `SingleShooting`
590+
(`warn_cond=Inf` for no warning).
586591
"""
587-
function init_quadprog(::LinModel, weights::ControllerWeights, Ẽ, P̃Δu, P̃u)
592+
function init_quadprog(
593+
::LinModel, transcription::TranscriptionMethod, weights::ControllerWeights,
594+
Ẽ, P̃Δu, P̃u; warn_cond=1e6
595+
)
588596
M_Hp, Ñ_Hc, L_Hp = weights.M_Hp, weights.Ñ_Hc, weights.L_Hp
589597
= Hermitian(2*(Ẽ'*M_Hp*+ P̃Δu'*Ñ_Hc*P̃Δu + P̃u'*L_Hp*P̃u), :L)
598+
verify_cond(transcription, H̃, warn_cond)
590599
return
591600
end
592601
"Return empty matrix if `model` is not a [`LinModel`](@ref)."
593-
function init_quadprog(::SimModel{NT}, weights::ControllerWeights, _, _, _) where {NT<:Real}
602+
function init_quadprog(
603+
::SimModel{NT}, ::TranscriptionMethod, ::ControllerWeights, args...; kwargs...
604+
) where {NT<:Real}
594605
= Hermitian(zeros(NT, 0, 0), :L)
595606
return
596607
end
597608

609+
"Check the condition number of `H̃` and display a `@warn` if `cond(H̃) > warn_cond`."
610+
function verify_cond(::SingleShooting, H̃, warn_cond)
611+
if !isinf(warn_cond)
612+
cond_H̃ = cond(H̃)
613+
cond_H̃ > warn_cond && @warn(
614+
"The Hessian condition number cond_H̃ > $warn_cond. The optimization "*
615+
"problem may be ill-conditioned.\n Consider changing the tunings, using "*
616+
"MultipleShooting, or using an optimizer more robust to this like DAQP.",
617+
cond_H̃,
618+
)
619+
end
620+
return nothing
621+
end
622+
"No check if `transcription` is not a `SingleShooting`."
623+
verify_cond(::TranscriptionMethod,_,_) = nothing
624+
598625
"""
599626
init_defaultcon_mpc(
600627
estim::StateEstimator,

src/controller/execute.jl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -609,6 +609,7 @@ end
609609
"Update the prediction matrices, linear constraints and JuMP optimization."
610610
function setmodel_controller!(mpc::PredictiveController, uop_old, x̂op_old)
611611
model, estim, transcription = mpc.estim.model, mpc.estim, mpc.transcription
612+
weights = mpc.weights
612613
nu, ny, nd, Hp, Hc = model.nu, model.ny, model.nd, mpc.Hp, mpc.Hc
613614
optim, con = mpc.optim, mpc.con
614615
# --- prediction matrices ---
@@ -676,7 +677,8 @@ function setmodel_controller!(mpc::PredictiveController, uop_old, x̂op_old)
676677
con.x̂0min .-= estim.x̂op # convert x̂ to x̂0 with the new operating point
677678
con.x̂0max .-= estim.x̂op # convert x̂ to x̂0 with the new operating point
678679
# --- quadratic programming Hessian matrix ---
679-
= init_quadprog(model, mpc.weights, mpc.Ẽ, mpc.P̃Δu, mpc.P̃u)
680+
# do not verify the condition number of the Hessian here:
681+
= init_quadprog(model, transcription, weights, mpc.Ẽ, mpc.P̃Δu, mpc.P̃u; warn_cond=Inf)
680682
mpc.H̃ .=
681683
# --- JuMP optimization ---
682684
Z̃var::Vector{JuMP.VariableRef} = optim[:Z̃var]

src/controller/explicitmpc.jl

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ struct ExplicitMPC{
5757
# dummy val (updated just before optimization):
5858
F = zeros(NT, ny*Hp)
5959
P̃Δu, P̃u, Ẽ = PΔu, Pu, E # no slack variable ϵ for ExplicitMPC
60-
= init_quadprog(model, weights, Ẽ, P̃Δu, P̃u)
60+
= init_quadprog(model, transcription, weights, Ẽ, P̃Δu, P̃u)
6161
# dummy vals (updated just before optimization):
6262
q̃, r = zeros(NT, size(H̃, 1)), zeros(NT, 1)
6363
H̃_chol = cholesky(H̃)
@@ -225,6 +225,7 @@ addinfo!(info, mpc::ExplicitMPC) = info
225225
"Update the prediction matrices and Cholesky factorization."
226226
function setmodel_controller!(mpc::ExplicitMPC, uop_old, _ )
227227
model, estim, transcription = mpc.estim.model, mpc.estim, mpc.transcription
228+
weights = mpc.weights
228229
nu, ny, nd, Hp, Hc = model.nu, model.ny, model.nd, mpc.Hp, mpc.Hc
229230
# --- predictions matrices ---
230231
E, G, J, K, V, B = init_predmat(model, estim, transcription, Hp, Hc)
@@ -236,7 +237,8 @@ function setmodel_controller!(mpc::ExplicitMPC, uop_old, _ )
236237
mpc.V .= V
237238
mpc.B .= B
238239
# --- quadratic programming Hessian matrix ---
239-
= init_quadprog(model, mpc.weights, mpc.Ẽ, mpc.P̃Δu, mpc.P̃u)
240+
# do not verify the condition number of the Hessian here:
241+
= init_quadprog(model, transcription, weights, mpc.Ẽ, mpc.P̃Δu, mpc.P̃u, warn_cond=Inf)
240242
mpc.H̃ .=
241243
set_objective_hessian!(mpc)
242244
# --- operating points ---

src/controller/linmpc.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ struct LinMPC{
7979
ex̂, fx̂, gx̂, jx̂, kx̂, vx̂, bx̂,
8080
Eŝ, Fŝ, Gŝ, Jŝ, Kŝ, Vŝ, Bŝ
8181
)
82-
= init_quadprog(model, weights, Ẽ, P̃Δu, P̃u)
82+
= init_quadprog(model, transcription, weights, Ẽ, P̃Δu, P̃u)
8383
# dummy vals (updated just before optimization):
8484
q̃, r = zeros(NT, size(H̃, 1)), zeros(NT, 1)
8585
Ks, Ps = init_stochpred(estim, Hp)

src/controller/nonlinmpc.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ struct NonLinMPC{
103103
Eŝ, Fŝ, Gŝ, Jŝ, Kŝ, Vŝ, Bŝ,
104104
gc!, nc
105105
)
106-
= init_quadprog(model, weights, Ẽ, P̃Δu, P̃u)
106+
= init_quadprog(model, transcription, weights, Ẽ, P̃Δu, P̃u)
107107
# dummy vals (updated just before optimization):
108108
q̃, r = zeros(NT, size(H̃, 1)), zeros(NT, 1)
109109
Ks, Ps = init_stochpred(estim, Hp)

src/precompile.jl

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ mpc_kf.estim()
2020
u = mpc_kf([55, 30])
2121
sim!(mpc_kf, 2, [55, 30])
2222

23-
mpc_lo = setconstraint!(LinMPC(Luenberger(model)), ymin=[45, -Inf])
23+
transcription = MultipleShooting()
24+
mpc_lo = setconstraint!(LinMPC(Luenberger(model); transcription), ymin=[45, -Inf])
2425
initstate!(mpc_lo, model.uop, model())
2526
preparestate!(mpc_lo, [55, 30])
2627
mpc_lo.estim()
@@ -79,18 +80,21 @@ exmpc.estim()
7980
u = exmpc([55, 30])
8081
sim!(exmpc, 2, [55, 30])
8182

82-
function f!(xnext, x, u, _, model)
83-
mul!(xnext, model.A , x)
84-
mul!(xnext, model.Bu, u, 1, 1)
83+
function f!(xnext, x, u, _ , p)
84+
A, B, _ = p
85+
mul!(xnext, A , x)
86+
mul!(xnext, B, u, 1, 1)
8587
return nothing
8688
end
87-
function h!(y, x, _, model)
88-
mul!(y, model.C, x)
89+
function h!(y, x, _ , p)
90+
_, _, C = p
91+
mul!(y, C, x)
8992
return nothing
9093
end
9194

95+
sys2 = minreal(ss(sys))
9296
nlmodel = setop!(
93-
NonLinModel(f!, h!, Ts, 2, 2, 2, solver=nothing, p=model),
97+
NonLinModel(f!, h!, Ts, 2, 2, 2, solver=RungeKutta(4), p=(sys2.A, sys2.B, sys2.C)),
9498
uop=[10, 10], yop=[50, 30]
9599
)
96100
y = nlmodel()
@@ -101,8 +105,9 @@ nmpc_im.estim()
101105
u = nmpc_im([55, 30])
102106
sim!(nmpc_im, 2, [55, 30])
103107

104-
nmpc_ukf = setconstraint!(
105-
NonLinMPC(UnscentedKalmanFilter(nlmodel), Hp=10, Cwt=1e3), ymin=[45, -Inf]
108+
transcription = MultipleShooting(f_threads=true)
109+
nmpc_ukf = setconstraint!(NonLinMPC(
110+
UnscentedKalmanFilter(nlmodel); Hp=10, transcription, Cwt=1e3), ymin=[45, -Inf]
106111
)
107112
initstate!(nmpc_ukf, nlmodel.uop, y)
108113
preparestate!(nmpc_ukf, [55, 30])
@@ -115,8 +120,9 @@ preparestate!(nmpc_ekf, [55, 30])
115120
u = nmpc_ekf([55, 30])
116121
sim!(nmpc_ekf, 2, [55, 30])
117122

118-
nmpc_mhe = setconstraint!(
119-
NonLinMPC(MovingHorizonEstimator(nlmodel, He=2), Hp=10, Cwt=Inf), ymin=[45, -Inf]
123+
transcription = TrapezoidalCollocation()
124+
nmpc_mhe = setconstraint!(NonLinMPC(
125+
MovingHorizonEstimator(nlmodel, He=2); transcription, Hp=10, Cwt=Inf), ymin=[45, -Inf]
120126
)
121127
setconstraint!(nmpc_mhe.estim, x̂min=[-50,-50,-50,-50], x̂max=[50,50,50,50])
122128
initstate!(nmpc_mhe, nlmodel.uop, y)

0 commit comments

Comments
 (0)