Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
774d84e
wip: `NonLinModelDAE` support in `NonLinMPC`
franckgaga Oct 3, 2026
67e5cb8
doc: minor modification in `SimModel`
franckgaga Oct 5, 2026
00a4a7a
added: `init_predmat` for DAE and MPC
franckgaga Oct 5, 2026
ae5140a
wip: `NonLinModelDAE` for `NonLinMPC`
franckgaga Oct 8, 2026
9f86acf
Merge branch 'main' into dae_mpc
franckgaga Oct 14, 2026
21175e7
Merge branch 'cleanup_dae_code' into dae_mpc
franckgaga Oct 14, 2026
5b39ddd
removed: useless argument in `con_nonlinprogeq_mhe!`
franckgaga Oct 15, 2026
53979c0
added: `reset_warmstart!` for MPC
franckgaga Oct 15, 2026
999185a
changed: combined warm-starting into single generic function
franckgaga Oct 15, 2026
c817d80
added: `NonLinMPC` support `NonLinModelDAE` 🥳
franckgaga Oct 15, 2026
dba86ff
debug: resolve method ambiguities
franckgaga Oct 15, 2026
8f12785
debug: idem
franckgaga Oct 15, 2026
9db2932
doc: warm-starting vector in Extended Help
franckgaga Oct 15, 2026
bf8a387
bump
franckgaga Oct 15, 2026
c5b5cb7
doc: minor corrections
franckgaga Oct 15, 2026
6c24486
doc: idem
franckgaga Oct 15, 2026
21ad5b3
doc: idem
franckgaga Oct 15, 2026
38094c6
doc: idem
franckgaga Oct 15, 2026
48b2503
added: default estimator with `NonLinModelDAE` in `NonLinMPC`
franckgaga Oct 15, 2026
6e4e0a4
debug: correctly reset warm-starting for `NonLinMPC`
franckgaga Oct 15, 2026
88912cc
test: `NonLinMPC` with `NonLinModelDAE` and `TrapezoidalCollocation`
franckgaga Oct 15, 2026
c84cacf
test: `NonLinMPC` with `NonLinModelDAE` and `OrthogonalCollocation`
franckgaga Oct 15, 2026
78b96c3
test: `NonLinMPC` construction with `NonLinModelDAE`
franckgaga Oct 15, 2026
e45dac1
test: debug tests
franckgaga Oct 15, 2026
a647305
debug: move method definition in `internal_model.jl`
franckgaga Oct 15, 2026
f26ee64
added: `NonLinMPC` with `NonLinModelDAE` precompilation
franckgaga Oct 15, 2026
8098f7e
Modifications de l’hôte de l’agent pour dae_mpc
franckgaga Oct 16, 2026
4f05328
changed: avoid explicit `invoke` calls
franckgaga Oct 16, 2026
f9d513a
debug: dispatch pattern
franckgaga Oct 16, 2026
c98c3ac
debug: do not update `beq` if no linear eq. constraints
franckgaga Oct 16, 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.14.4"
version = "2.15.0"
authors = ["Francis Gagnon"]

[deps]
Expand Down
7 changes: 7 additions & 0 deletions docs/src/internals/misc.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,10 @@ ModelPredictiveControl.initpred!
ModelPredictiveControl.linconstraint!
ModelPredictiveControl.linconstrainteq!
```

## Warm-starting

```@docs
ModelPredictiveControl.reset_warmstart!
ModelPredictiveControl.set_warmstart!
```
1 change: 0 additions & 1 deletion docs/src/internals/predictive_control.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ ModelPredictiveControl.get_nonlincon_oracle(::NonLinMPC, ::ModelPredictiveContro

```@docs
ModelPredictiveControl.optim_objective!(::PredictiveController)
ModelPredictiveControl.set_warmstart_mpc!
ModelPredictiveControl.predict!
ModelPredictiveControl.con_nonlinprog!
ModelPredictiveControl.con_nonlinprogeq!
Expand Down
1 change: 0 additions & 1 deletion docs/src/internals/state_estim.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,6 @@ ModelPredictiveControl.update_estimate!

```@docs
ModelPredictiveControl.optim_objective!(::MovingHorizonEstimator)
ModelPredictiveControl.set_warmstart_mhe!
ModelPredictiveControl.disturbedinput!
ModelPredictiveControl.predict_mhe!
ModelPredictiveControl.con_nonlinprog_mhe!
Expand Down
4 changes: 2 additions & 2 deletions src/controller/construct.jl
Original file line number Diff line number Diff line change
Expand Up @@ -94,15 +94,15 @@ end

"Outer constructor to validate and convert weight matrices if necessary."
function ControllerWeights(
model::SimModelODE{NT}, Hp, Hc, M_Hp, N_Hc, L_Hp, Cwt=Inf, Ewt=0
model::SimModel{NT}, Hp, Hc, M_Hp, N_Hc, L_Hp, Cwt=Inf, Ewt=0
) where {NT<:Real}
validate_weights(model, Hp, Hc, M_Hp, N_Hc, L_Hp, Cwt, Ewt)
M_Hp, N_Hc, L_Hp = NT.(M_Hp), NT.(N_Hc), NT.(L_Hp)
return ControllerWeights{NT}(M_Hp, N_Hc, L_Hp, Cwt, Ewt)
end

"Validate predictive controller weight and horizon specified values."
function validate_weights(model, Hp, Hc, M_Hp, N_Hc, L_Hp, C=Inf, E=nothing)
function validate_weights(model::SimModel, Hp, Hc, M_Hp, N_Hc, L_Hp, C=Inf, E=nothing)
nu, ny = model.nu, model.ny
nM, nN, nL = ny*Hp, nu*Hc, nu*Hp
Hp < 1 && throw(ArgumentError("Prediction horizon Hp should be ≥ 1"))
Expand Down
27 changes: 14 additions & 13 deletions src/controller/execute.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ It also stores `u - mpc.estim.model.uop` at `mpc.lastu0` for converting the inpu
``\mathbf{ΔU}`` to inputs ``\mathbf{U}``.
"""
function initstate!(mpc::PredictiveController, u, ym, d=mpc.estim.buffer.empty)
mpc.Z̃ .= 0
mpc.lastu0 .= u .- mpc.estim.model.uop
reset_warmstart!(mpc, mpc.transcription)
return initstate!(mpc.estim, u, ym, d)
end

Expand Down Expand Up @@ -144,14 +144,15 @@ julia> round.(getinfo(mpc)[:Ŷ], digits=3)
"""
function getinfo(mpc::PredictiveController{NT}) where NT<:Real
model, buffer, transcription = mpc.estim.model, mpc.buffer, mpc.transcription
nΔŨ, nK = mpc.Hc*model.nu + mpc.nϵ, mpc.Hp*model.nk̄
nk̄ = get_nk̄(model, transcription)
nΔŨ, nK̄ = mpc.Hc*model.nu + mpc.nϵ, mpc.Hp*nk̄
nŶe, nUe = (mpc.Hp+1)*model.ny, (mpc.Hp+1)*model.nu
nX̂0, nÛ0 = mpc.estim.nx̂*mpc.Hp, model.nu*mpc.Hp
Z̃ = mpc.Z̃
info = Dict{Symbol, Any}()
ΔŨ = Vector{NT}(undef, nΔŨ)
x̂0end = similar(mpc.estim.x̂0)
K = Vector{NT}(undef, nK)
K = Vector{NT}(undef, nK̄)
Ue, Ŷe = Vector{NT}(undef, nUe), Vector{NT}(undef, nŶe)
U0, Ŷ0 = similar(mpc.Uop), similar(mpc.Yop)
Û0, X̂0 = Vector{NT}(undef, nÛ0), Vector{NT}(undef, nX̂0)
Expand Down Expand Up @@ -277,25 +278,25 @@ function initpred!(mpc::PredictiveController, model::LinModel, ry, d, lastu, D̂
end

@doc raw"""
initpred!(mpc::PredictiveController, model::SimModelODE, ry, d, lastu, D̂, R̂y, R̂u) -> nothing
initpred!(mpc::PredictiveController, model::SimModel, ry, d, lastu, D̂, R̂y, R̂u) -> nothing

Init `lastu0, ŷ, F, d0, D̂0, D̂e, R̂y, R̂u` vectors when model is not a [`LinModel`](@ref).
"""
function initpred!(mpc::PredictiveController, model::SimModelODE, ry, d, lastu, D̂, R̂y, R̂u)
function initpred!(mpc::PredictiveController, model::SimModel, ry, d, lastu, D̂, R̂y, R̂u)
initpred_common!(mpc, model, ry, d, lastu, D̂, R̂y, R̂u)
return nothing
end

"""
initpred_common!(mpc::PredictiveController, model::SimModelODE, ry, d, lastu, D̂, R̂y, R̂u) -> F
initpred_common!(mpc::PredictiveController, model::SimModel, ry, d, lastu, D̂, R̂y, R̂u) -> F

Common computations of `initpred!` for all types of [`SimModelODE`](@ref).
Common computations of `initpred!` for all types of [`SimModel`](@ref).

Will also init `mpc.F` with 0 values, or with the stochastic predictions `Ŷs` if `mpc.estim`
is an [`InternalModel`](@ref). The function returns `mpc.F`.
"""
function initpred_common!(
mpc::PredictiveController, model::SimModelODE, ry, d, lastu, D̂, R̂y, R̂u
mpc::PredictiveController, model::SimModel, ry, d, lastu, D̂, R̂y, R̂u
)
mpc.lastu0 .= lastu .- model.uop
mul!(mpc.Tu_lastu0, mpc.Tu, mpc.lastu0)
Expand Down Expand Up @@ -327,14 +328,14 @@ end
predictstoch!(Ŷs, ::PredictiveController, ::StateEstimator) = (Ŷs .= 0; nothing)

@doc raw"""
linconstraint_custom!(mpc::PredictiveController, model::SimModelODE)
linconstraint_custom!(mpc::PredictiveController, model::SimModel)

Init the ``\mathbf{F_w}`` vector for the custom linear inequality constraints.

See [`relaxW`](@ref) for the definition of the vector. The function does nothing if
`mpc.con.nw < 1`.
"""
function linconstraint_custom!(mpc::PredictiveController, model::SimModelODE)
function linconstraint_custom!(mpc::PredictiveController, model::SimModel)
mpc.con.nw < 1 && return nothing
ny, nu, nd, buffer = model.ny, model.nu, model.nd, mpc.buffer
Fw = mpc.con.Fw
Expand Down Expand Up @@ -458,15 +459,15 @@ end

Optimize the objective function of `mpc` [`PredictiveController`](@ref) and return the solution `Z̃`.

If first warm-starts the solver with [`set_warmstart_mpc!`](@ref). It then calls
If first warm-starts the solver with [`set_warmstart!`](@ref). It then calls
`JuMP.optimize!(mpc.optim)` and extract the solution. A failed optimization prints an
`@error` log in the REPL and returns the warm-start value. A failed optimization also prints
[`getinfo`](@ref) results in the debug log [if activated](https://docs.julialang.org/en/v1/stdlib/Logging/#Example:-Enable-debug-level-messages).
"""
function optim_objective!(mpc::PredictiveController{NT}) where {NT<:Real}
model, optim = mpc.estim.model, mpc.optim
Z̃var::Vector{JuMP.VariableRef} = optim[:Z̃var]
Z̃s = set_warmstart_mpc!(mpc, mpc.transcription, Z̃var)
Z̃s = set_warmstart!(mpc, mpc.transcription, Z̃var)
set_force∇!(mpc)
set_objective_linear_coef!(mpc, model, Z̃var)
try
Expand Down Expand Up @@ -509,7 +510,7 @@ end
set_force∇!(::PredictiveController) = nothing

"By default, no need to update the objective function."
set_objective_linear_coef!(::PredictiveController, ::SimModelODE, _) = nothing
set_objective_linear_coef!(::PredictiveController, ::SimModel, _) = nothing

"Update the linear coefficients of the quadratic objective with `mpc.q̃` for `LinModel`."
function set_objective_linear_coef!(mpc::PredictiveController, ::LinModel, Z̃var)
Expand Down
1 change: 1 addition & 0 deletions src/controller/linmpc.jl
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ struct LinMPC{
Uop, Yop, Dop,
buffer
)
reset_warmstart!(mpc, transcription)
init_optimization!(mpc, model, optim)
return mpc
end
Expand Down
31 changes: 18 additions & 13 deletions src/controller/nonlinmpc.jl
Original file line number Diff line number Diff line change
Expand Up @@ -147,15 +147,16 @@ struct NonLinMPC{
Uop, Yop, Dop,
buffer
)
reset_warmstart!(mpc, transcription)
init_optimization!(mpc, model, optim)
return mpc
end
end

@doc raw"""
NonLinMPC(model::SimModelODE; <keyword arguments>)
NonLinMPC(model::SimModel; <keyword arguments>)

Construct a nonlinear predictive controller based on [`SimModelODE`](@ref) `model`.
Construct a nonlinear predictive controller based on [`SimModel`](@ref) `model`.

Both [`NonLinModel`](@ref) and [`LinModel`](@ref) are supported (see Extended Help). The
controller minimizes the following objective function at each discrete time ``k``:
Expand Down Expand Up @@ -196,7 +197,8 @@ definition of the other variables.
This method uses the default state estimator:

- if `model` is a [`LinModel`](@ref), a [`SteadyKalmanFilter`](@ref) with default arguments;
- else, an [`UnscentedKalmanFilter`](@ref) with default arguments.
- if `model` is a [`NonLinModel`](@ref), an [`UnscentedKalmanFilter`](@ref) with default arguments;
- else, a [`MovingHorizonEstimator`](@ref) with default arguments.

This controller allocates memory at each time step for the optimization.

Expand All @@ -205,7 +207,7 @@ This controller allocates memory at each time step for the optimization.
`MethodError: no method matching Float64(::ForwardDiff.Dual)`.

# Arguments
- `model::SimModelODE` : model used for controller predictions and state estimations.
- `model::SimModel` : model used for controller predictions and state estimations.
- `Hp::Int=10+nk` : prediction horizon ``H_p``, `nk` is the number of delays if `model` is a
[`LinModel`](@ref) (must be specified otherwise).
- `Hc::Union{Int, Vector{Int}}=2` : control horizon ``H_c``, custom move blocking pattern is
Expand All @@ -229,7 +231,8 @@ This controller allocates memory at each time step for the optimization.
Extended Help).
- `nc=0` : number of custom nonlinear inequality constraints.
- `p=model.p` : ``J_E`` and ``\mathbf{g_c}`` functions parameter ``\mathbf{p}`` (any type).
- `transcription=SingleShooting()` : a [`TranscriptionMethod`](@ref) for the optimization.
- `transcription=default_transcription(model)` : a [`TranscriptionMethod`](@ref) object,
default to [`SingleShooting`](@ref) or `model.transcription` for [`NonLinModelDAE`](@ref).
- `optim=JuMP.Model(Ipopt.Optimizer)` : nonlinear optimizer used in the predictive
controller, provided as a [`JuMP.Model`](@extref) object (default to [`Ipopt`](https://github.com/jump-dev/Ipopt.jl) optimizer).
- `gradient=AutoForwardDiff()` : an `AbstractADType` backend for the gradient of the objective
Expand Down Expand Up @@ -344,7 +347,7 @@ NonLinMPC controller with a sample time Ts = 5.0 s:
`10/Cwt` (if not already set), to scale the small values of ``ϵ``.
"""
function NonLinMPC(
model::SimModelODE;
model::SimModel;
Hp::Int = default_Hp(model),
Hc::IntVectorOrInt = DEFAULT_HC,
Mwt = fill(DEFAULT_MWT, model.ny),
Expand All @@ -364,7 +367,7 @@ function NonLinMPC(
gc ::Function = gc!,
nc::Int = 0,
p = model.p,
transcription::TranscriptionMethod = SingleShooting(),
transcription::TranscriptionMethod = default_transcription(model),
optim::JuMP.GenericModel = JuMP.Model(DEFAULT_NLP_OPTIMIZER, add_bridges=false),
gradient::AbstractADType = DEFAULT_GRADIENT,
jacobian::AbstractADType = default_jacobian(transcription),
Expand Down Expand Up @@ -437,7 +440,7 @@ function NonLinMPC(
gc ::Function = gc!,
nc = 0,
p = estim.model.p,
transcription::TranscriptionMethod = SingleShooting(),
transcription::TranscriptionMethod = default_transcription(estim.model),
optim::JuMP.GenericModel = JuMP.Model(DEFAULT_NLP_OPTIMIZER, add_bridges=false),
gradient::AbstractADType = DEFAULT_GRADIENT,
jacobian::AbstractADType = default_jacobian(transcription),
Expand All @@ -464,6 +467,7 @@ function NonLinMPC(
end

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

"""
Expand Down Expand Up @@ -739,11 +743,11 @@ function addinfo!(info, mpc::NonLinMPC{NT}) where NT<:Real
end

"""
init_optimization!(mpc::NonLinMPC, model::SimModelODE, optim::JuMP.GenericModel) -> nothing
init_optimization!(mpc::NonLinMPC, model::SimModel, optim::JuMP.GenericModel) -> nothing

Init the nonlinear optimization for [`NonLinMPC`](@ref) controllers.
"""
function init_optimization!(mpc::NonLinMPC, model::SimModelODE, optim::JuMP.GenericModel)
function init_optimization!(mpc::NonLinMPC, model::SimModel, optim::JuMP.GenericModel)
# --- variables and linear constraints ---
con = mpc.con
nZ̃ = length(mpc.Z̃)
Expand Down Expand Up @@ -844,8 +848,8 @@ function get_nonlinobj_op(mpc::NonLinMPC, optim::JuMP.GenericModel{JNT}) where J
Û0::Vector{JNT}, X̂0::Vector{JNT} = zeros(JNT, nU), zeros(JNT, nX̂)
gc::Vector{JNT}, g::Vector{JNT} = zeros(JNT, nc), zeros(JNT, ng)
geq::Vector{JNT} = zeros(JNT, neq)
function J!(Z̃, ΔŨ, x̂0end, Ue, Ŷe, U0, Ŷ0, Û0, K, X̂0, gc, g, geq)
update_predictions!(ΔŨ, x̂0end, Ue, Ŷe, U0, Ŷ0, Û0, K, X̂0, gc, g, geq, mpc, Z̃)
function J!(Z̃, ΔŨ, x̂0end, Ue, Ŷe, U0, Ŷ0, Û0, K̄, X̂0, gc, g, geq)
update_predictions!(ΔŨ, x̂0end, Ue, Ŷe, U0, Ŷ0, Û0, K̄, X̂0, gc, g, geq, mpc, Z̃)
return obj_nonlinprog!(Ŷ0, U0, mpc, Ue, Ŷe, ΔŨ)
end
Z̃_J = zeros(JNT, nZ̃)
Expand Down Expand Up @@ -1124,7 +1128,8 @@ Update in-place all vectors for the predictions of `mpc` controller at decision
The method mutates all the arguments before the `mpc` argument.
"""
function update_predictions!(
ΔŨ, x̂0end, Ue, Ŷe, U0, Ŷ0, Û0, K̄, X̂0, gc, g, geq, mpc::PredictiveController, Z̃
ΔŨ, x̂0end, Ue, Ŷe, U0, Ŷ0, Û0, K̄, X̂0, gc, g, geq,
mpc::PredictiveController, Z̃
)
model, transcription = mpc.estim.model, mpc.transcription
U0 = getU0!(U0, mpc, Z̃)
Expand Down
Loading
Loading