Skip to content

Commit 0975312

Browse files
committed
Merge branch 'main' into move_block
2 parents 20d20d9 + 03cb818 commit 0975312

25 files changed

Lines changed: 572 additions & 265 deletions

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 = "1.6.1"
4+
version = "1.7.0"
55

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

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ for more detailed examples.
129129
- extended Kalman filter
130130
- unscented Kalman filter
131131
- moving horizon estimator
132+
- disable built-in observer to manually provide your own state estimate
132133
- easily estimate unmeasured disturbances by adding one or more integrators at the:
133134
- manipulated inputs
134135
- measured outputs

docs/make.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ links = InterLinks(
1212
"JuMP" => "https://jump.dev/JuMP.jl/stable/objects.inv",
1313
"DifferentiationInterface" => "https://juliadiff.org/DifferentiationInterface.jl/DifferentiationInterface/stable/objects.inv",
1414
"ForwardDiff" => "https://juliadiff.org/ForwardDiff.jl/stable/objects.inv",
15+
"LowLevelParticleFilters" => "https://baggepinnen.github.io/LowLevelParticleFilters.jl/stable/objects.inv",
1516
)
1617

1718
DocMeta.setdocmeta!(

docs/src/internals/predictive_control.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,5 +40,5 @@ ModelPredictiveControl.linconstrainteq!
4040
```@docs
4141
ModelPredictiveControl.optim_objective!(::PredictiveController)
4242
ModelPredictiveControl.set_warmstart!
43-
ModelPredictiveControl.getinput
43+
ModelPredictiveControl.getinput!
4444
```

docs/src/internals/state_estim.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ ModelPredictiveControl.linconstraint!(::MovingHorizonEstimator, ::LinModel)
3939

4040
```@docs
4141
ModelPredictiveControl.optim_objective!(::MovingHorizonEstimator)
42+
ModelPredictiveControl.set_warmstart_mhe!
4243
```
4344

4445
## Remove Operating Points

docs/src/public/state_estim.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,12 @@ MovingHorizonEstimator
8181
InternalModel
8282
```
8383

84+
## ManualEstimator
85+
86+
```@docs
87+
ManualEstimator
88+
```
89+
8490
## Default Model Augmentation
8591

8692
```@docs

src/ModelPredictiveControl.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export savetime!, periodsleep
3636
export StateEstimator, InternalModel, Luenberger
3737
export SteadyKalmanFilter, KalmanFilter, UnscentedKalmanFilter, ExtendedKalmanFilter
3838
export MovingHorizonEstimator
39+
export ManualEstimator
3940
export default_nint, initstate!
4041
export PredictiveController, ExplicitMPC, LinMPC, NonLinMPC, setconstraint!, moveinput!
4142
export TranscriptionMethod, SingleShooting, MultipleShooting

src/controller/construct.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
const MSG_LINMODEL_ERR = "estim.model type must be a LinModel, see ManualEstimator docstring "*
2+
"to use a nonlinear state estimator with a linear controller"
3+
14
struct PredictiveControllerBuffer{NT<:Real}
25
u ::Vector{NT}
36
::Vector{NT}

src/controller/execute.jl

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,13 @@
22
initstate!(mpc::PredictiveController, u, ym, d=[]) -> x̂
33
44
Init the states of `mpc.estim` [`StateEstimator`](@ref) and warm start `mpc.Z̃` at zero.
5+
6+
It also stores `u - mpc.estim.model.uop` at `mpc.lastu0` for converting the input increments
7+
``\mathbf{ΔU}`` to inputs ``\mathbf{U}``.
58
"""
69
function initstate!(mpc::PredictiveController, u, ym, d=mpc.estim.buffer.empty)
710
mpc.Z̃ .= 0
11+
mpc.lastu0 .= u .- mpc.estim.model.uop
812
return initstate!(mpc.estim, u, ym, d)
913
end
1014

@@ -16,10 +20,11 @@ Compute the optimal manipulated input value `u` for the current control period.
1620
Solve the optimization problem of `mpc` [`PredictiveController`](@ref) and return the
1721
results ``\mathbf{u}(k)``. Following the receding horizon principle, the algorithm discards
1822
the optimal future manipulated inputs ``\mathbf{u}(k+1), \mathbf{u}(k+2), ...`` Note that
19-
the method mutates `mpc` internal data but it does not modifies `mpc.estim` states. Call
20-
[`preparestate!(mpc, ym, d)`](@ref) before `moveinput!`, and [`updatestate!(mpc, u, ym, d)`](@ref)
21-
after, to update `mpc` state estimates. Setpoint and measured disturbance previews can
22-
be implemented with the `R̂y`, `R̂u` and `D̂` keyword arguments.
23+
the method mutates `mpc` internal data (it stores `u - mpc.estim.model.uop` at `mpc.lastu0`
24+
for instance) but it does not modifies `mpc.estim` states. Call [`preparestate!(mpc, ym, d)`](@ref)
25+
before `moveinput!`, and [`updatestate!(mpc, u, ym, d)`](@ref) after, to update `mpc` state
26+
estimates. Setpoint and measured disturbance previews can be implemented with the `R̂y`, `R̂u`
27+
and `D̂` keyword arguments.
2328
2429
Calling a [`PredictiveController`](@ref) object calls this method.
2530
@@ -69,7 +74,7 @@ function moveinput!(
6974
linconstraint!(mpc, mpc.estim.model, mpc.transcription)
7075
linconstrainteq!(mpc, mpc.estim.model, mpc.transcription)
7176
= optim_objective!(mpc)
72-
return getinput(mpc, Z̃)
77+
return getinput!(mpc, Z̃)
7378
end
7479

7580
@doc raw"""
@@ -207,7 +212,7 @@ function initpred!(mpc::PredictiveController, model::LinModel, d, D̂, R̂y, R̂
207212
F = initpred_common!(mpc, model, d, D̂, R̂y, R̂u)
208213
F .+= mpc.B # F = F + B
209214
mul!(F, mpc.K, mpc.estim.x̂0, 1, 1) # F = F + K*x̂0
210-
mul!(F, mpc.V, mpc.estim.lastu0, 1, 1) # F = F + V*lastu0
215+
mul!(F, mpc.V, mpc.lastu0, 1, 1) # F = F + V*lastu0
211216
if model.nd > 0
212217
mul!(F, mpc.G, mpc.d0, 1, 1) # F = F + G*d0
213218
mul!(F, mpc.J, mpc.D̂0, 1, 1) # F = F + J*D̂0
@@ -254,7 +259,7 @@ Will also init `mpc.F` with 0 values, or with the stochastic predictions `Ŷs`
254259
is an [`InternalModel`](@ref). The function returns `mpc.F`.
255260
"""
256261
function initpred_common!(mpc::PredictiveController, model::SimModel, d, D̂, R̂y, R̂u)
257-
mul!(mpc.Tu_lastu0, mpc.Tu, mpc.estim.lastu0)
262+
mul!(mpc.Tu_lastu0, mpc.Tu, mpc.lastu0)
258263
mpc.ŷ .= evaloutput(mpc.estim, d)
259264
if model.nd > 0
260265
mpc.d0 .= d .- model.dop
@@ -446,20 +451,23 @@ function preparestate!(mpc::PredictiveController, ym, d=mpc.estim.buffer.empty)
446451
end
447452

448453
@doc raw"""
449-
getinput(mpc::PredictiveController, Z̃) -> u
454+
getinput!(mpc::PredictiveController, Z̃) -> u
450455
451-
Get current manipulated input `u` from a [`PredictiveController`](@ref) solution `Z̃`.
456+
Get current manipulated input `u` from the solution `Z̃`, store it and return it.
452457
453458
The first manipulated input ``\mathbf{u}(k)`` is extracted from the decision vector
454-
``\mathbf{Z̃}`` and applied on the plant (from the receding horizon principle).
459+
``\mathbf{Z̃}`` and applied on the plant (from the receding horizon principle). It also
460+
stores `u - mpc.estim.model.uop` at `mpc.lastu0`.
455461
"""
456-
function getinput(mpc, Z̃)
462+
function getinput!(mpc, Z̃)
463+
model = mpc.estim.model
457464
Δu = mpc.buffer.u
458-
for i in 1:mpc.estim.model.nu
465+
for i in 1:model.nu
459466
Δu[i] = Z̃[i]
460467
end
461468
u = Δu
462-
u .+= mpc.estim.lastu0 .+ mpc.estim.model.uop
469+
u .+= mpc.lastu0 .+ model.uop
470+
mpc.lastu0 .= u .- model.uop
463471
return u
464472
end
465473

@@ -548,6 +556,7 @@ function setmodel!(
548556
Ñ_Hc = Ntilde_Hc,
549557
kwargs...
550558
)
559+
uop_old = copy(mpc.estim.model.uop)
551560
x̂op_old = copy(mpc.estim.x̂op)
552561
nu, ny, Hp, Hc, nϵ = model.nu, model.ny, mpc.Hp, mpc.Hc, mpc.
553562
setmodel!(mpc.estim, model; kwargs...)
@@ -593,12 +602,12 @@ function setmodel!(
593602
mpc.weights.L_Hp .= L_Hp
594603
mpc.weights.iszero_L_Hp[] = iszero(mpc.weights.L_Hp)
595604
end
596-
setmodel_controller!(mpc, x̂op_old)
605+
setmodel_controller!(mpc, uop_old, x̂op_old)
597606
return mpc
598607
end
599608

600609
"Update the prediction matrices, linear constraints and JuMP optimization."
601-
function setmodel_controller!(mpc::PredictiveController, x̂op_old)
610+
function setmodel_controller!(mpc::PredictiveController, uop_old, x̂op_old)
602611
model, estim, transcription = mpc.estim.model, mpc.estim, mpc.transcription
603612
nu, ny, nd, Hp, Hc = model.nu, model.ny, model.nd, mpc.Hp, mpc.Hc
604613
optim, con = mpc.optim, mpc.con
@@ -628,6 +637,7 @@ function setmodel_controller!(mpc::PredictiveController, x̂op_old)
628637
con.x̂0min .+= x̂op_old # convert x̂0 to x̂ with the old operating point
629638
con.x̂0max .+= x̂op_old # convert x̂0 to x̂ with the old operating point
630639
# --- operating points ---
640+
mpc.lastu0 .+= uop_old .- model.uop
631641
for i in 0:Hp-1
632642
mpc.Uop[(1+nu*i):(nu+nu*i)] .= model.uop
633643
mpc.Yop[(1+ny*i):(ny+ny*i)] .= model.yop

src/controller/explicitmpc.jl

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ struct ExplicitMPC{
1414
weights::CW
1515
R̂u::Vector{NT}
1616
R̂y::Vector{NT}
17+
lastu0::Vector{NT}
1718
P̃Δu::Matrix{NT}
1819
P̃u ::Matrix{NT}
1920
Tu ::Matrix{NT}
@@ -47,12 +48,13 @@ struct ExplicitMPC{
4748
= 0 # no slack variable ϵ for ExplicitMPC
4849
# dummy vals (updated just before optimization):
4950
R̂y, R̂u, Tu_lastu0 = zeros(NT, ny*Hp), zeros(NT, nu*Hp), zeros(NT, nu*Hp)
51+
lastu0 = zeros(NT, nu)
5052
transcription = SingleShooting() # explicit MPC only supports SingleShooting
5153
PΔu = init_ZtoΔU(estim, transcription, Hp, Hc)
5254
Pu, Tu = init_ZtoU(estim, transcription, Hp, Hc)
5355
E, G, J, K, V, B = init_predmat(model, estim, transcription, Hp, Hc)
5456
# dummy val (updated just before optimization):
55-
F, fx̂ = zeros(NT, ny*Hp), zeros(NT, nx̂)
57+
F = zeros(NT, ny*Hp)
5658
P̃Δu, P̃u, Ẽ = PΔu, Pu, E # no slack variable ϵ for ExplicitMPC
5759
= init_quadprog(model, weights, Ẽ, P̃Δu, P̃u)
5860
# dummy vals (updated just before optimization):
@@ -72,6 +74,7 @@ struct ExplicitMPC{
7274
Hp, Hc, nϵ, nb,
7375
weights,
7476
R̂u, R̂y,
77+
lastu0,
7578
P̃Δu, P̃u, Tu, Tu_lastu0,
7679
Ẽ, F, G, J, K, V, B,
7780
H̃, q̃, r,
@@ -158,7 +161,7 @@ function ExplicitMPC(
158161
N_Hc = Diagonal(repeat(Nwt, Hc)),
159162
L_Hp = Diagonal(repeat(Lwt, Hp)),
160163
) where {NT<:Real, SE<:StateEstimator{NT}}
161-
isa(estim.model, LinModel) || error("estim.model type must be a LinModel")
164+
isa(estim.model, LinModel) || error(MSG_LINMODEL_ERR)
162165
nk = estimate_delays(estim.model)
163166
if Hp nk
164167
@warn("prediction horizon Hp ($Hp) ≤ estimated number of delays in model "*
@@ -217,7 +220,7 @@ addinfo!(info, mpc::ExplicitMPC) = info
217220

218221

219222
"Update the prediction matrices and Cholesky factorization."
220-
function setmodel_controller!(mpc::ExplicitMPC, _ )
223+
function setmodel_controller!(mpc::ExplicitMPC, uop_old, _ )
221224
model, estim, transcription = mpc.estim.model, mpc.estim, mpc.transcription
222225
nu, ny, nd, Hp, Hc = model.nu, model.ny, model.nd, mpc.Hp, mpc.Hc
223226
# --- predictions matrices ---
@@ -234,6 +237,7 @@ function setmodel_controller!(mpc::ExplicitMPC, _ )
234237
mpc.H̃ .=
235238
set_objective_hessian!(mpc)
236239
# --- operating points ---
240+
mpc.lastu0 .+= uop_old .- model.uop
237241
for i in 0:Hp-1
238242
mpc.Uop[(1+nu*i):(nu+nu*i)] .= model.uop
239243
mpc.Yop[(1+ny*i):(ny+ny*i)] .= model.yop

0 commit comments

Comments
 (0)