Skip to content

Commit 1cc93d2

Browse files
committed
debug: terminal constr. for NonLinMPC now work
- added : terminal constraint tests for `NonLinMPC` - added : reduce allocation for `NonLinMPC` based on `NonLinModel`
1 parent b9a26e3 commit 1cc93d2

File tree

7 files changed

+175
-148
lines changed

7 files changed

+175
-148
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "ModelPredictiveControl"
22
uuid = "61f9bdb8-6ae4-484a-811f-bbf86720c31c"
33
authors = ["Francis Gagnon"]
4-
version = "0.10.2"
4+
version = "0.10.3"
55

66
[deps]
77
ControlSystemsBase = "aaaaaaaa-a6ca-5380-bf3e-84a91bcd477e"

docs/src/internals/predictive_control.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ ModelPredictiveControl.init_predmat
1616
ModelPredictiveControl.init_ΔUtoU
1717
ModelPredictiveControl.init_quadprog
1818
ModelPredictiveControl.init_stochpred
19-
ModelPredictiveControl.init_linconstraint
19+
ModelPredictiveControl.init_matconstraint
2020
```
2121

2222
## Constraint Relaxation

docs/src/manual/linmpc.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,8 @@ automatically added to the model outputs by default if observability is preserve
8787
[`SteadyKalmanFilter`](@ref) for details).
8888

8989
[^1]: As an alternative to state observer, we could have use an [`InternalModel`](@ref)
90-
structure with `mpc = LinMPC(InternalModel(model), Hp=15, Hc=2, Mwt=[1, 1], Nwt=[0.1, 0.1])` . It was
91-
tested on the example of this page and it gives similar results.
90+
structure with `mpc = LinMPC(InternalModel(model), Hp=15, Hc=2, Mwt=[1, 1], Nwt=[0.1, 0.1])`.
91+
It was tested on the example of this page and it gives similar results.
9292

9393
Before closing the loop, we call [`initstate!`](@ref) with the actual plant inputs and
9494
measurements to ensure a bumpless transfer. Since `model` simulates our plant here, its

docs/src/manual/nonlinmpc.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,10 @@ nu, nx, ny = 1, 2, 1
5656
model = NonLinModel(f, h, Ts, nu, nx, ny)
5757
```
5858

59-
The output function ``\mathbf{h}`` converts the ``θ`` angle to degrees. It is good practice
60-
to first simulate `model` using [`sim!`](@ref) as a quick sanity check:
59+
The output function ``\mathbf{h}`` converts the ``θ`` angle to degrees. Note that special
60+
characters like ``θ`` can be typed in the Julia REPL or VS Code by typing `\theta` and
61+
pressing the `<TAB>` key. It is good practice to first simulate `model` using [`sim!`](@ref)
62+
as a quick sanity check:
6163

6264
```@example 1
6365
using Plots

src/controller/nonlinmpc.jl

Lines changed: 22 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -258,8 +258,7 @@ function init_optimization!(mpc::NonLinMPC)
258258
@constraint(optim, linconstraint, A*ΔŨvar .≤ b)
259259
# --- nonlinear optimization init ---
260260
model = mpc.estim.model
261-
ny, nu, nx̂, Hp = model.ny, model.nu, mpc.estim.nx̂, mpc.Hp
262-
nC = (2*Hp*nu + 2*nvar + 2*Hp*ny + 2*nx̂) - length(mpc.con.b)
261+
ny, nx̂, Hp, nC = model.ny, mpc.estim.nx̂, mpc.Hp, length(con.i_C)
263262
# inspired from https://jump.dev/JuMP.jl/stable/tutorials/nonlinear/tips_and_tricks/#User-defined-operators-with-vector-outputs
264263
Jfunc, Cfunc = let mpc=mpc, model=model, nC=nC, nvar=nvar , nŶ=Hp*ny, nx̂=nx̂
265264
last_ΔŨtup_float, last_ΔŨtup_dual = nothing, nothing
@@ -341,11 +340,7 @@ function init_optimization!(mpc::NonLinMPC)
341340
return nothing
342341
end
343342

344-
345-
"No nonlinear constraint for [`NonLinMPC`](@ref) based on [`LinModel`](@ref)."
346-
setnontlincon!(::NonLinMPC, ::LinModel) = nothing
347-
348-
"Set the nonlinear constraints on the output predictions `Ŷ`."
343+
"Set the nonlinear constraints on the output predictions `Ŷ` ans terminal states `x̂end`."
349344
function setnonlincon!(mpc::NonLinMPC, ::NonLinModel)
350345
optim = mpc.optim
351346
ΔŨvar = mpc.optim[:ΔŨvar]
@@ -371,27 +366,31 @@ function setnonlincon!(mpc::NonLinMPC, ::NonLinModel)
371366
end
372367

373368
"""
374-
con_nonlinprog!(C, mpc::NonLinMPC, model::SimModel, x̂end, Ŷ, ΔŨ)
369+
con_nonlinprog!(C, mpc::NonLinMPC, model::SimModel, x̂end, Ŷ, ΔŨ) -> C
375370
376371
Nonlinear constrains for [`NonLinMPC`](@ref) when `model` is not a [`LinModel`](@ref).
372+
373+
The method mutates the `C` vector in argument and returns it.
377374
"""
378375
function con_nonlinprog!(C, mpc::NonLinMPC, model::SimModel, x̂end, Ŷ, ΔŨ)
379-
ny, nx̂, Hp = model.ny, mpc.estim.nx̂, mpc.Hp
380-
i_end_Ymin, i_end_Ymax = 1Hp*ny , 2Hp*ny
381-
i_end_x̂min, i_end_x̂max = 2Hp*ny + 1nx̂, 2Hp*ny + 2nx̂
382-
if !isinf(mpc.C) # constraint softening activated :
383-
ϵ = ΔŨ[end]
384-
C[ 1:i_end_Ymin] = (mpc.con.Ymin - Ŷ) - ϵ*mpc.con.c_Ymin
385-
C[i_end_Ymin+1:i_end_Ymax] = (Ŷ - mpc.con.Ymax) - ϵ*mpc.con.c_Ymax
386-
C[i_end_Ymax+1:i_end_x̂min] = (mpc.con.x̂min -end) - ϵ*mpc.con.c_x̂min
387-
C[i_end_x̂min+1:i_end_x̂max] = (x̂end - mpc.con.x̂max) - ϵ*mpc.con.c_x̂max
388-
else # no constraint softening :
389-
C[ 1:i_end_Ymin] = mpc.con.Ymin -
390-
C[i_end_Ymin+1:i_end_Ymax] =- mpc.con.Ymax
391-
C[i_end_Ymax+1:i_end_x̂min] = mpc.con.x̂min -end
392-
C[i_end_x̂min+1:i_end_x̂max] =end - mpc.con.x̂max
376+
nx̂, nŶ = mpc.estim.nx̂, model.ny*mpc.Hp
377+
ϵ = !isinf(mpc.C) ? ΔŨ[end] : 0.0 # ϵ = 0.0 if Cwt=Inf (meaning: no relaxation)
378+
for i in eachindex(C)
379+
mpc.con.i_C[i] || continue
380+
if i nŶ
381+
j = i
382+
C[i] = (mpc.con.Ymin[j] - Ŷ[j]) - ϵ*mpc.con.c_Ymin[j]
383+
elseif i 2nŶ
384+
j = i - nŶ
385+
C[i] = (Ŷ[j] - mpc.con.Ymax[j]) - ϵ*mpc.con.c_Ymax[j]
386+
elseif i 2nŶ + nx̂
387+
j = i - 2nŶ
388+
C[i] = (mpc.con.x̂min[j] -end[j]) - ϵ*mpc.con.c_x̂min[j]
389+
else
390+
j = i - 2nŶ - nx̂
391+
C[i] = (x̂end[j] - mpc.con.x̂max[j]) - ϵ*mpc.con.c_x̂max[j]
392+
end
393393
end
394-
C[isinf.(C)] .= 0 # replace ±Inf with 0 to avoid INVALID_MODEL error
395394
return C
396395
end
397396

src/predictive_control.jl

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ struct ControllerConstraint
6767
c_Ymax ::Vector{Float64}
6868
c_x̂min ::Vector{Float64}
6969
c_x̂max ::Vector{Float64}
70+
i_C ::BitVector
7071
end
7172

7273
@doc raw"""
@@ -306,9 +307,11 @@ function setconstraint!(
306307
i_Ymin, i_Ymax = .!isinf.(con.Ymin), .!isinf.(con.Ymax)
307308
i_x̂min, i_x̂max = .!isinf.(con.x̂min), .!isinf.(con.x̂max)
308309
if notSolvedYet
309-
con.i_b[:], con.A[:] = init_linconstraint(model,
310-
i_Umin, i_Umax, i_ΔŨmin, i_ΔŨmax, i_Ymin, i_Ymax, i_x̂min, i_x̂max,
311-
con.A_Umin, con.A_Umax, con.A_ΔŨmin, con.A_ΔŨmax, con.A_Ymin, con.A_Ymax, con.A_x̂min, con.A_x̂max
310+
con.i_b[:], con.i_C[:], con.A[:] = init_matconstraint(model,
311+
i_Umin, i_Umax, i_ΔŨmin, i_ΔŨmax,
312+
i_Ymin, i_Ymax, i_x̂min, i_x̂max,
313+
con.A_Umin, con.A_Umax, con.A_ΔŨmin, con.A_ΔŨmax,
314+
con.A_Ymin, con.A_Ymax, con.A_x̂min, con.A_x̂max
312315
)
313316
A = con.A[con.i_b, :]
314317
b = con.b[con.i_b]
@@ -318,8 +321,13 @@ function setconstraint!(
318321
@constraint(mpc.optim, linconstraint, A*ΔŨvar .≤ b)
319322
setnonlincon!(mpc, model)
320323
else
321-
i_b, _ = init_linconstraint(model, i_Umin, i_Umax, i_ΔŨmin, i_ΔŨmax, i_Ymin, i_Ymax, i_x̂min, i_x̂max)
322-
i_b == con.i_b || error("Cannot modify ±Inf constraints after calling moveinput!")
324+
i_b, i_C = init_matconstraint(model,
325+
i_Umin, i_Umax, i_ΔŨmin, i_ΔŨmax,
326+
i_Ymin, i_Ymax, i_x̂min, i_x̂max
327+
)
328+
if i_b con.i_b || i_C con.i_C
329+
error("Cannot modify ±Inf constraints after calling moveinput!")
330+
end
323331
end
324332
return mpc
325333
end
@@ -998,7 +1006,7 @@ function init_defaultcon(estim, Hp, Hc, C, S, N_Hc, E, ex̂, fx̂, gx̂, jx̂, k
9981006
i_ΔŨmin, i_ΔŨmax = .!isinf.(ΔŨmin), .!isinf.(ΔŨmax)
9991007
i_Ymin, i_Ymax = .!isinf.(Ymin), .!isinf.(Ymax)
10001008
i_x̂min, i_x̂max = .!isinf.(x̂min), .!isinf.(x̂max)
1001-
i_b, A = init_linconstraint(
1009+
i_b, i_C, A = init_matconstraint(
10021010
model,
10031011
i_Umin, i_Umax, i_ΔŨmin, i_ΔŨmax, i_Ymin, i_Ymax, i_x̂min, i_x̂max,
10041012
A_Umin, A_Umax, A_ΔŨmin, A_ΔŨmax, A_Ymin, A_Ymax, A_x̂max, A_x̂min
@@ -1008,7 +1016,7 @@ function init_defaultcon(estim, Hp, Hc, C, S, N_Hc, E, ex̂, fx̂, gx̂, jx̂, k
10081016
ẽx̂ , fx̂ , gx̂ , jx̂ , kx̂ , vx̂ ,
10091017
Umin , Umax , ΔŨmin , ΔŨmax , Ymin , Ymax , x̂min , x̂max,
10101018
A_Umin , A_Umax, A_ΔŨmin, A_ΔŨmax , A_Ymin , A_Ymax , A_x̂min , A_x̂max,
1011-
A , b , i_b , c_Ymin , c_Ymax , c_x̂min , c_x̂max ,
1019+
A , b , i_b , c_Ymin , c_Ymax , c_x̂min , c_x̂max , i_C
10121020
)
10131021
return con, S̃, Ñ_Hc, Ẽ
10141022
end
@@ -1214,42 +1222,52 @@ init_stochpred(estim::StateEstimator, _ ) = zeros(0, estim.nxs), zeros(0, estim.
12141222

12151223

12161224
@doc raw"""
1217-
init_linconstraint(::LinModel,
1218-
i_Umin, i_Umax, i_ΔŨmin, i_ΔŨmax, i_Ymin, i_Ymax, args...
1219-
) -> i_b, A
1225+
init_matconstraint(model::LinModel,
1226+
i_Umin, i_Umax, i_ΔŨmin, i_ΔŨmax, i_Ymin, i_Ymax, i_x̂min, i_x̂max, args...
1227+
) -> i_b, i_C, A
12201228
1221-
Init `i_b` and `A` for the linear inequality constraints (``\mathbf{A ΔŨ ≤ b}``).
1229+
Init `i_b`, `i_C` and `A` matrices for the linear and nonlinear inequality constraints.
12221230
1223-
If provided, the arguments in `args` should be all the inequality constraint matrices:
1224-
`A_Umin, A_Umax, A_ΔŨmin, A_ΔŨmax, A_Ymin, A_Ymax`. If not provided, it returns an empty `A`
1225-
matrix. `i_b` is a `BitVector` including the indices of ``\mathbf{b}`` that are finite
1226-
numbers.
1231+
The linear and nonlinear inequality constraints are respectively defined as:
1232+
```math
1233+
\begin{aligned}
1234+
\mathbf{A ΔŨ } &≤ \mathbf{b} \\
1235+
\mathbf{C(ΔŨ)} &≤ \mathbf{0}
1236+
\end{aligned}
1237+
```
1238+
`i_b` is a `BitVector` including the indices of ``\mathbf{b}`` that are finite numbers.
1239+
`i_C` is a similar vector but for the indices of ``\mathbf{C}`` (empty if `model` is a
1240+
[`LinModel`](@ref)). The method also returns the ``\mathbf{A}`` matrix if `args` is
1241+
provided. In such a case, `args` needs to contain all the inequality constraint matrices:
1242+
`A_Umin, A_Umax, A_ΔŨmin, A_ΔŨmax, A_Ymin, A_Ymax, A_x̂min, A_x̂max`.
12271243
"""
1228-
function init_linconstraint(::LinModel,
1244+
function init_matconstraint(::LinModel,
12291245
i_Umin, i_Umax, i_ΔŨmin, i_ΔŨmax, i_Ymin, i_Ymax, i_x̂min, i_x̂max, args...
12301246
)
12311247
i_b = [i_Umin; i_Umax; i_ΔŨmin; i_ΔŨmax; i_Ymin; i_Ymax; i_x̂min; i_x̂max]
1248+
i_C = BitVector()
12321249
if isempty(args)
12331250
A = zeros(length(i_b), 0)
12341251
else
12351252
A_Umin, A_Umax, A_ΔŨmin, A_ΔŨmax, A_Ymin, A_Ymax, A_x̂min, A_x̂max = args
12361253
A = [A_Umin; A_Umax; A_ΔŨmin; A_ΔŨmax; A_Ymin; A_Ymax; A_x̂min; A_x̂max]
12371254
end
1238-
return i_b, A
1255+
return i_b, i_C, A
12391256
end
12401257

1241-
"Init values without predicted output and terminal constraints if `model` is not a [`LinModel`](@ref)."
1242-
function init_linconstraint(::SimModel,
1243-
i_Umin, i_Umax, i_ΔŨmin, i_ΔŨmax, _ , _ , _ , _ , args...
1258+
"Init `i_b` and `A` without predicted output and terminal constraints if `model` is not a [`LinModel`](@ref)."
1259+
function init_matconstraint(::SimModel,
1260+
i_Umin, i_Umax, i_ΔŨmin, i_ΔŨmax, i_Ymin, i_Ymax, i_x̂min, i_x̂max, args...
12441261
)
12451262
i_b = [i_Umin; i_Umax; i_ΔŨmin; i_ΔŨmax]
1263+
i_C = [i_Ymin; i_Ymax; i_x̂min; i_x̂max]
12461264
if isempty(args)
12471265
A = zeros(length(i_b), 0)
12481266
else
12491267
A_Umin, A_Umax, A_ΔŨmin, A_ΔŨmax, _ , _ , _ , _ = args
12501268
A = [A_Umin; A_Umax; A_ΔŨmin; A_ΔŨmax]
12511269
end
1252-
return i_b, A
1270+
return i_b, i_C, A
12531271
end
12541272

12551273
"Validate predictive controller weight and horizon specified values."

0 commit comments

Comments
 (0)