Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
d7f0dc1
added: error if `ShootingMethods` with DAEs
franckgaga Sep 20, 2026
cb853a5
bump
franckgaga Sep 20, 2026
c48346e
Merge branch 'debug_d0_colloc' into mhe_dae_oc
franckgaga Sep 20, 2026
b7b79d8
added: pretty-print `na` in `MovingHorizonEstimator`
franckgaga Sep 20, 2026
1bb4a62
changed: new notation for `TrapezoidalCollocation` in MHE+DAE
franckgaga Sep 20, 2026
30ba429
doc: details on why `A0` is needed in `Z` for `OC`
franckgaga Sep 20, 2026
2716b2a
debug: explicitly compute final `q0` for MHE and TC
franckgaga Sep 20, 2026
d59c0eb
test: tighter tolerances in MHE+DAE+TC tests
franckgaga Sep 20, 2026
8b69587
doc: update `jldoctest`
franckgaga Sep 20, 2026
04cc126
doc: update `jldoctest`
franckgaga Sep 20, 2026
b64334f
Merge branch 'main' into mhe_dae_oc
franckgaga Sep 20, 2026
e36716c
added: default to `model.transcription` for MHE + DAE
franckgaga Sep 20, 2026
da2af1f
changed: `a1` notation in `NonLinModelDAE` and `TrapezoidalCollocation`
franckgaga Sep 21, 2026
aa88bf5
doc: under construction admonition
franckgaga Sep 21, 2026
534d202
doc: DAE models in README
franckgaga Sep 21, 2026
176e063
changed: clean-up in various state-space functions
franckgaga Sep 21, 2026
f537735
debug: MHE+TC+DAE with `h=1` now works
franckgaga Sep 21, 2026
2fe9567
debug: skip `a0arr` for `A0` in `predict_mhe!`
franckgaga Sep 21, 2026
6e9f332
changed: simpler `geq` filling when `Nk < He`
franckgaga Sep 21, 2026
e7b8bfd
test: cover TC with `h=1` bugfix with smarter tests
franckgaga Sep 21, 2026
17993df
doc: residuals in `init_orthocolloc` docstring
franckgaga Sep 21, 2026
029bbb9
test: minor correction
franckgaga Sep 21, 2026
f95f23a
debug: weird race condition seems to be solved now
franckgaga Sep 21, 2026
0335df2
debug: comment for posterity
franckgaga Sep 21, 2026
d7f3c35
test: remove useless `@show`
franckgaga Sep 21, 2026
e46280a
added: do not call `fq!` if `h>0` for final residual `q0end`
franckgaga Sep 22, 2026
386619f
added: MHE + MHE + OC now work! šŸ¾
franckgaga Sep 22, 2026
3b8c92e
added: precompute `τendIsNotOne` at construction
franckgaga Sep 22, 2026
b2f68c8
added: reduce allocation in `MovingHorizonEstimator`
franckgaga Sep 22, 2026
cb8d223
added: precompute `nx̃=nε+nxĢ‚` at MHE construction
franckgaga Sep 22, 2026
202d335
changed: support algebraic variables in `fill0unused!`
franckgaga Sep 22, 2026
09e9727
added: remove one allocation in `getinfo(mhe)`
franckgaga Sep 22, 2026
c7636ba
changed: renamed `a0` -> `â0` and `A0` -> `Â0`
franckgaga Sep 22, 2026
d7cb731
debug: correctly dispatch `ĥ!` + rename everything
franckgaga Sep 22, 2026
3e2b37b
doc: minor details
franckgaga Sep 22, 2026
03e38e8
debug: correctly dispatch in `ĥ!`
franckgaga Sep 22, 2026
de63cec
test: new estim. and `getinfo` tests with MHE+DAE+OC
franckgaga Sep 22, 2026
8ac0761
test: looser tolerances for MHE+DAE+OC tests
franckgaga Sep 22, 2026
9af079f
debug: `reset_warmstart!` for MHE and OC now works.
franckgaga Sep 22, 2026
f1b85cd
debug: correct warm-starting for MHE+DAE+TC
franckgaga Sep 22, 2026
2fafb60
doc: detail nonlinear equality constraints for MHE+DAEs+OC
franckgaga Sep 22, 2026
7644575
doc: minor detail
franckgaga Sep 22, 2026
e2615fd
added: MHE+DAE to precompilation workflow
franckgaga Sep 22, 2026
70179c8
debug: precompile workload
franckgaga Sep 22, 2026
c17de8b
debug: idem
franckgaga Sep 22, 2026
2d1180c
debug: compute `nA` in TC warm-starting
franckgaga Sep 22, 2026
be5c998
debug: similar bugfilx
franckgaga Sep 22, 2026
f4ce8b8
added: algebraic variables over `Nk` in `getinfo(mhe)`
franckgaga Sep 22, 2026
ee821ce
debug: warm-starting with MHE and TC
franckgaga Sep 22, 2026
62d5ba6
debug: `getinfo(mhe)`
franckgaga Sep 22, 2026
4c8e9a8
changed : rename `nA` -> `nAĢ‚`
franckgaga Sep 22, 2026
1bb1377
debug: correct filed in `getinfo(mhe)`
franckgaga Sep 22, 2026
63b4f86
doc: minor correction
franckgaga Sep 22, 2026
9752fcd
doc: minor correction
franckgaga Sep 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name = "ModelPredictiveControl"
uuid = "61f9bdb8-6ae4-484a-811f-bbf86720c31c"
version = "2.13.2"
version = "2.14.0"
authors = ["Francis Gagnon"]

[deps]
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ for more detailed examples.

### šŸŽÆ Model Predictive Control Features

- šŸ­ļø **Plant Model**: Linear or nonlinear models exploiting multiple dispatch.
- šŸ­ļø **Plant Model**: Linear, nonlinear ODE and nonlinear DAE models exploiting multiple dispatch.
- ā›³ļø **Objectives**: Tracking for inputs/outputs, move suppression, terminal costs, and economic costs.
- ā³ļø **Horizons**: Distinct prediction/control horizons with custom move blocking.
- šŸ“ø **Linearization**: Auto-differentiation for exact Jacobians.
Expand Down
2 changes: 2 additions & 0 deletions docs/src/internals/sim_model.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ ModelPredictiveControl.init_defectmat_dae
```@docs
ModelPredictiveControl.f!
ModelPredictiveControl.h!
ModelPredictiveControl.fq_dae!
ModelPredictiveControl.h_dae!
```

## Init State
Expand Down
2 changes: 0 additions & 2 deletions docs/src/internals/state_estim.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,6 @@ ModelPredictiveControl.get_nonlincon_oracle(::MovingHorizonEstimator, ::ModelPre
```@docs
ModelPredictiveControl.f̂!
ModelPredictiveControl.ĥ!
ModelPredictiveControl.fq_dae!
ModelPredictiveControl.ĥ_dae!
```

## Remove Operating Points
Expand Down
4 changes: 4 additions & 0 deletions docs/src/manual/nonlinmpc2.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
Pages = ["nonlinmpc2.md"]
```

!!! todo "Under Construction"
This tutorial is currently under construction. Only the modeling part is written for
now.

## Nonlinear Model (DAE)

In this example, the goal is to control the pH of a solution in a continuously stirred
Expand Down
6 changes: 2 additions & 4 deletions src/controller/linmpc.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
const DEFAULT_LINMPC_TRANSCRIPTION = SingleShooting()

struct LinMPC{
NT<:Real,
SE<:StateEstimator,
Expand Down Expand Up @@ -241,7 +239,7 @@ function LinMPC(
Wd = nothing,
Wr = nothing,
Cwt = DEFAULT_CWT,
transcription::ShootingMethod = DEFAULT_LINMPC_TRANSCRIPTION,
transcription::ShootingMethod = SingleShooting(),
optim::JuMP.GenericModel = JuMP.Model(DEFAULT_QP_OPTIMIZER, add_bridges=true),
kwargs...
)
Expand Down Expand Up @@ -300,7 +298,7 @@ function LinMPC(
Wd = nothing,
Wr = nothing,
Cwt = DEFAULT_CWT,
transcription::ShootingMethod = DEFAULT_LINMPC_TRANSCRIPTION,
transcription::ShootingMethod = SingleShooting(),
optim::JM = JuMP.Model(DEFAULT_QP_OPTIMIZER, add_bridges=true)
) where {NT<:Real, SE<:StateEstimator{NT}, JM<:JuMP.GenericModel}
isa(estim.model, LinModel) || error(MSG_LINMODEL_ERR)
Expand Down
11 changes: 5 additions & 6 deletions src/controller/nonlinmpc.jl
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
const DEFAULT_NONLINMPC_TRANSCRIPTION = SingleShooting()
const DEFAULT_NONLINMPC_HESSIAN = AutoSparse(
AutoForwardDiff();
sparsity_detector=TracerSparsityDetector(),
Expand Down Expand Up @@ -365,7 +364,7 @@ function NonLinMPC(
gc ::Function = gc!,
nc::Int = 0,
p = model.p,
transcription::TranscriptionMethod = DEFAULT_NONLINMPC_TRANSCRIPTION,
transcription::TranscriptionMethod = SingleShooting(),
optim::JuMP.GenericModel = JuMP.Model(DEFAULT_NLP_OPTIMIZER, add_bridges=false),
gradient::AbstractADType = DEFAULT_GRADIENT,
jacobian::AbstractADType = default_jacobian(transcription),
Expand All @@ -381,9 +380,6 @@ function NonLinMPC(
)
end

default_estimator(model::SimModelODE; kwargs...) = UnscentedKalmanFilter(model; kwargs...)
default_estimator(model::LinModel; kwargs...) = SteadyKalmanFilter(model; kwargs...)

"""
NonLinMPC(estim::StateEstimator; <keyword arguments>)

Expand Down Expand Up @@ -441,7 +437,7 @@ function NonLinMPC(
gc ::Function = gc!,
nc = 0,
p = estim.model.p,
transcription::TranscriptionMethod = DEFAULT_NONLINMPC_TRANSCRIPTION,
transcription::TranscriptionMethod = SingleShooting(),
optim::JuMP.GenericModel = JuMP.Model(DEFAULT_NLP_OPTIMIZER, add_bridges=false),
gradient::AbstractADType = DEFAULT_GRADIENT,
jacobian::AbstractADType = default_jacobian(transcription),
Expand All @@ -467,6 +463,9 @@ function NonLinMPC(
)
end

default_estimator(model::SimModelODE; kwargs...) = UnscentedKalmanFilter(model; kwargs...)
default_estimator(model::LinModel; kwargs...) = SteadyKalmanFilter(model; kwargs...)

"""
validate_JE(NT, JE) -> nothing

Expand Down
14 changes: 8 additions & 6 deletions src/estimator/construct.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ struct StateEstimatorBuffer{NT<:Real}
û ::Vector{NT}
k̄ ::Vector{NT}
x̂ ::Vector{NT}
a ::Vector{NT}
â ::Vector{NT}
Z̃ ::Vector{NT}
VĢ‚ ::Vector{NT}
WĢ‚ ::Vector{NT}
XĢ‚ ::Vector{NT}
A ::Vector{NT}
AĢ‚ ::Vector{NT}
YĢ‚ ::Vector{NT}
U ::Vector{NT}
D ::Vector{NT}
PĢ‚ ::Matrix{NT}
QĢ‚ ::Matrix{NT}
Expand Down Expand Up @@ -38,18 +39,19 @@ function StateEstimatorBuffer{NT}(
transcription::TranscriptionMethod = SingleShooting()
) where NT <: Real
nZ̃ = nε + get_nZ_mhe(transcription, He, nxĢ‚, nkĢ„, nwĢ‚, na)
nV̂, nŴ, nX̂, nA, nŶ, nD = nym*He, nŵ*He, nx̂*He, na*He, ny*He, nd*(He+1)
nV̂, nŴ, nX̂, nÂ, nŶ, nU, nD = nym*He, nŵ*He, nx̂*He, na*He, ny*He, nu*He, nd*(He+1)
u = Vector{NT}(undef, nu)
û = Vector{NT}(undef, nu)
k̄ = Vector{NT}(undef, nk̄)
x̂ = Vector{NT}(undef, nx̂)
a = Vector{NT}(undef, na)
â = Vector{NT}(undef, na)
Z̃ = Vector{NT}(undef, nZ̃)
VĢ‚ = Vector{NT}(undef, nVĢ‚)
WĢ‚ = Vector{NT}(undef, nWĢ‚)
XĢ‚ = Vector{NT}(undef, nXĢ‚)
A = Vector{NT}(undef, nA)
AĢ‚ = Vector{NT}(undef, nAĢ‚)
YĢ‚ = Vector{NT}(undef, nYĢ‚)
U = Vector{NT}(undef, nU)
D = Vector{NT}(undef, nD)
P̂ = Matrix{NT}(undef, nx̂, nx̂)
Q̂ = Matrix{NT}(undef, nx̂, nx̂)
Expand All @@ -60,7 +62,7 @@ function StateEstimatorBuffer{NT}(
d = Vector{NT}(undef, nd)
empty = Vector{NT}(undef, 0)
return StateEstimatorBuffer{NT}(
u, uĢ‚, kĢ„, xĢ‚, a, Z̃, VĢ‚, WĢ‚, XĢ‚, A, YĢ‚, D, PĢ‚, QĢ‚, RĢ‚, KĢ‚, ym, yĢ‚, d, empty
u, uĢ‚, kĢ„, xĢ‚, aĢ‚, Z̃, VĢ‚, WĢ‚, XĢ‚, AĢ‚, YĢ‚, U, D, PĢ‚, QĢ‚, RĢ‚, KĢ‚, ym, yĢ‚, d, empty
)
end

Expand Down
45 changes: 14 additions & 31 deletions src/estimator/execute.jl
Original file line number Diff line number Diff line change
Expand Up @@ -98,12 +98,12 @@ Same than [`f̂!`](@ref) for [`SimModel`](@ref) but without the `estim` argument
"""
function f̂!(x̂0next, û0, k̄, model::SimModel, As, Cs_u, f̂op, x̂op, x̂0, u0, d0)
# `@views` macro avoid copies with matrix slice operator e.g. [a:b]
@views xd, xs = x̂0[1:model.nx], x̂0[model.nx+1:end]
@views xdnext, xsnext = x̂0next[1:model.nx], x̂0next[model.nx+1:end]
mul!(û0, Cs_u, xs) # ys_u = Cs_u*xs
@views x̂d, x̂s = x̂0[1:model.nx], x̂0[model.nx+1:end]
@views x̂dnext, x̂snext = x̂0next[1:model.nx], x̂0next[model.nx+1:end]
mul!(û0, Cs_u, x̂s) # ys_u = Cs_u*xs
û0 .+= u0 # û0 = u0 + ys_u
f!(xdnext, k̄, model, xd, û0, d0, model.p)
mul!(xsnext, As, xs)
f!(x̂dnext, k̄, model, x̂d, û0, d0, model.p)
mul!(x̂snext, As, x̂s)
x̂0next .+= f̂op .- x̂op
return nothing
end
Expand Down Expand Up @@ -135,42 +135,25 @@ Same than [`ĥ!`](@ref) for [`SimModel`](@ref) but without the `estim` argument
"""
function ĥ!(ŷ0, model::SimModel, Cs_y::AbstractMatrix, x̂0, d0)
# `@views` macro avoid copies with matrix slice operator e.g. [a:b]
@views xd, xs = x̂0[1:model.nx], x̂0[model.nx+1:end]
h!(ŷ0, model, xd, d0, model.p) # y0 = h(xd, d0)
mul!(ŷ0, Cs_y, xs, 1, 1) # ŷ0 = y0 + Cs_y*xs
@views x̂d, x̂s = x̂0[1:model.nx], x̂0[model.nx+1:end]
h!(ŷ0, model, x̂d, d0, model.p) # y0 = h(xd, d0)
mul!(ŷ0, Cs_y, x̂s, 1, 1) # ŷ0 = y0 + Cs_y*xs
return nothing
end

"""
fq_dae!(ẋ0, q0, model, x0, a0, u0, d0)
ĥ!(ŷ0, estim::StateEstimator, model::SimModel, x̂0, â0, d0)

Call `model.fq!` for [`NonLinModelDAE`](@ref) or `model.f!` for [`NonLinModel`](@ref).
Same than [`ĥ!`](@ref) for [`SimModel`](@ref), but call [`h_dae!`](@ref) internally.
"""
function fq_dae!(ẋ0, q0, model::NonLinModelDAE, x0, a0, u0, d0)
return model.fq!(ẋ0, q0, x0, a0, u0, d0, model.p)
end
fq_dae!(ẋ0, _ , model::NonLinModel, x0, _ , u0, d0)= model.f!(ẋ0, x0, u0, d0, model.p)

"""
ĥ_dae!(ŷ0, estim::StateEstimator, model::NonLinModelDAE, x0, a0, d0)

Similar than [`ĥ!`](@ref) but with a algebraic variable `a0` for [`NonLinModelDAE`](@ref) .
"""
function ĥ_dae!(ŷ0, estim::StateEstimator, model::NonLinModelDAE, x̂0, a0, d0)
function ĥ!(ŷ0, estim::StateEstimator, model::SimModel, x̂0, â0, d0)
# `@views` macro avoid copies with matrix slice operator e.g. [a:b]
@views xd, xs = x̂0[1:model.nx], x̂0[model.nx+1:end]
model.h!(ŷ0, xd, a0, d0, model.p)
mul!(ŷ0, estim.Cs_y, xs, 1, 1) # ŷ0 = y0 + Cs_y*xs
@views x̂d, x̂s = x̂0[1:model.nx], x̂0[model.nx+1:end]
h_dae!(ŷ0, model, x̂d, â0, d0)
mul!(ŷ0, estim.Cs_y, x̂s, 1, 1) # ŷ0 = y0 + Cs_y*xs
return nothing
end

"""
ĥ_dae!(ŷ0, estim::StateEstimator, model::SimModelODE, x0, _ , d0)

Ignore the algebraic variable argument for other [`SimModelODE`](@ref) types.
"""
ĥ_dae!(ŷ0, estim::StateEstimator, model::SimModelODE, x̂0, _, d0) = ĥ!(ŷ0, estim, model, x̂0, d0)

"""
disturbedinput!(Û0, estim::StateEstimator, x̂0, X̂0, U0) -> Û0

Expand Down
4 changes: 3 additions & 1 deletion src/estimator/mhe.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ include("mhe/execute.jl")
include("mhe/transcription.jl")

"Return estimation horizon He and slack variables length nε for `MovingHorizonEstimator`."
get_other_dims(estim::MovingHorizonEstimator) = (estim.He, estim.nε)
get_other_dims(estim::MovingHorizonEstimator) = (estim.He, estim.nε, get_na(estim.model))

"Print optimizer and other information for `MovingHorizonEstimator`."
function print_details(io::IO, estim::MovingHorizonEstimator)
Expand All @@ -27,11 +27,13 @@ print_backends(::IO, ::MovingHorizonEstimator, ::LinModel) = nothing
function print_estim_dim(io::IO, estim::MovingHorizonEstimator, n; firstchars=nothing)
nu, nd = estim.model.nu, estim.model.nd
nx̂, nym, nyu = estim.nx̂, estim.nym, estim.nyu
na = get_na(estim.model)
He, nε = estim.He, estim.nε
niu, niym = sum(estim.nint_u), sum(estim.nint_ym)
println(io, " │ ā”œ$(lpad(He, n)) estimation steps He")
println(io, " │ ā”œ$(lpad(nu, n)) manipulated inputs u ($niu integrating states)")
println(io, " │ ā”œ$(lpad(nxĢ‚, n)) estimated states xĢ‚")
println(io, " │ ā”œ$(lpad(na, n)) algebraic variables a")
println(io, " │ ā”œ$(lpad(nym, n)) measured outputs ym ($niym integrating states)")
println(io, " │ ā”œ$(lpad(nyu, n)) unmeasured outputs yu")
print(io, " │ ā””$(lpad(nd, n)) measured disturbances d")
Expand Down
Loading
Loading