Skip to content

Commit 9439f05

Browse files
committed
doc: more consise setconstraint! and ExplicitMPC doc
1 parent 412ae42 commit 9439f05

File tree

8 files changed

+164
-220
lines changed

8 files changed

+164
-220
lines changed

src/controller/construct.jl

+27-74
Original file line numberDiff line numberDiff line change
@@ -52,25 +52,17 @@ for details on bounds and softness parameters ``\mathbf{c}``. The output and ter
5252
constraints are all soft by default. See Extended Help for time-varying constraints.
5353
5454
# Arguments
55-
- `mpc::PredictiveController` : predictive controller to set constraints.
56-
- `umin = fill(-Inf,nu)` : manipulated input lower bounds ``\mathbf{u_{min}}``.
57-
- `umax = fill(+Inf,nu)` : manipulated input upper bounds ``\mathbf{u_{max}}``.
58-
- `Δumin = fill(-Inf,nu)` : manipulated input increment lower bounds ``\mathbf{Δu_{min}}``.
59-
- `Δumax = fill(+Inf,nu)` : manipulated input increment upper bounds ``\mathbf{Δu_{max}}``.
60-
- `ymin = fill(-Inf,ny)` : predicted output lower bounds ``\mathbf{y_{min}}``.
61-
- `ymax = fill(+Inf,ny)` : predicted output upper bounds ``\mathbf{y_{max}}``.
62-
- `x̂min = fill(-Inf,nx̂)` : terminal constraint lower bounds ``\mathbf{x̂_{min}}``.
63-
- `x̂max = fill(+Inf,nx̂)` : terminal constraint upper bounds ``\mathbf{x̂_{max}}``.
64-
- `c_umin = fill(0.0,nu)` : `umin` softness weights ``\mathbf{c_{u_{min}}}``.
65-
- `c_umax = fill(0.0,nu)` : `umax` softness weights ``\mathbf{c_{u_{max}}}``.
66-
- `c_Δumin = fill(0.0,nu)` : `Δumin` softness weights ``\mathbf{c_{Δu_{min}}}``.
67-
- `c_Δumax = fill(0.0,nu)` : `Δumax` softness weights ``\mathbf{c_{Δu_{max}}}``.
68-
- `c_ymin = fill(1.0,ny)` : `ymin` softness weights ``\mathbf{c_{y_{min}}}``.
69-
- `c_ymax = fill(1.0,ny)` : `ymax` softness weights ``\mathbf{c_{y_{max}}}``.
70-
- `c_x̂min = fill(1.0,nx̂)` : `x̂min` softness weights ``\mathbf{c_{x̂_{min}}}``.
71-
- `c_x̂max = fill(1.0,nx̂)` : `x̂max` softness weights ``\mathbf{c_{x̂_{max}}}``.
55+
- `mpc::PredictiveController` : predictive controller to set constraints
56+
- `umin=fill(-Inf,nu)` / `umax=fill(+Inf,nu)` : manipulated input bound ``\mathbf{u_{min/max}}``
57+
- `Δumin=fill(-Inf,nu)` / `Δumax=fill(+Inf,nu)` : manipulated input increment bound ``\mathbf{Δu_{min/max}}``
58+
- `ymin=fill(-Inf,ny)` / `ymax=fill(+Inf,ny)` : predicted output bound ``\mathbf{y_{min/max}}``
59+
- `x̂min=fill(-Inf,nx̂)` / `x̂max=fill(+Inf,nx̂)` : terminal constraint bound ``\mathbf{x̂_{min/max}}``
60+
- `c_umin=fill(0.0,nu)` / `c_umax=fill(0.0,nu)` : `umin` / `umax` softness weight ``\mathbf{c_{u_{min/max}}}``
61+
- `c_Δumin=fill(0.0,nu)` / `c_Δumax=fill(0.0,nu)` : `Δumin` / `Δumax` softness weight ``\mathbf{c_{Δu_{min/max}}}``
62+
- `c_ymin=fill(1.0,ny)` / `c_ymax=fill(1.0,ny)` : `ymin` / `ymax` softness weight ``\mathbf{c_{y_{min/max}}}``
63+
- `c_x̂min=fill(1.0,nx̂)` / `c_x̂max=fill(1.0,nx̂)` : `x̂min` / `x̂max` softness weight ``\mathbf{c_{x̂_{min/max}}}``
7264
- all the keyword arguments above but with a first capital letter, except for the terminal
73-
constraints, e.g. `Ymax` or `C_Δumin`: for time-varying constraints (see Extended Help).
65+
constraints, e.g. `Ymax` or `C_Δumin`: for time-varying constraints (see Extended Help)
7466
7567
# Examples
7668
```jldoctest
@@ -134,31 +126,7 @@ function setconstraint!(
134126
C_umax = nothing, C_umin = nothing,
135127
C_Δumax = nothing, C_Δumin = nothing,
136128
C_ymax = nothing, C_ymin = nothing,
137-
# TODO:
138-
# ------------ will be deleted in the future ---------------
139-
ŷmin = nothing, ŷmax = nothing,
140-
c_ŷmin = nothing, c_ŷmax = nothing,
141-
# ----------------------------------------------------------
142129
)
143-
# TODO:
144-
# ----- these 4 `if`s will be deleted in the future --------
145-
if !isnothing(ŷmin)
146-
Base.depwarn("keyword arg ŷmin is deprecated, use ymin instead", :setconstraint!)
147-
ymin = ŷmin
148-
end
149-
if !isnothing(ŷmax)
150-
Base.depwarn("keyword arg ŷmax is deprecated, use ymax instead", :setconstraint!)
151-
ymax = ŷmax
152-
end
153-
if !isnothing(c_ŷmin)
154-
Base.depwarn("keyword arg ŷmin is deprecated, use ymin instead", :setconstraint!)
155-
c_ymin = c_ŷmin
156-
end
157-
if !isnothing(c_ŷmax)
158-
Base.depwarn("keyword arg ŷmax is deprecated, use ymax instead", :setconstraint!)
159-
c_ymax = c_ŷmax
160-
end
161-
# ----------------------------------------------------------
162130
model, con, optim = mpc.estim.model, mpc.con, mpc.optim
163131
nu, ny, nx̂, Hp, Hc = model.nu, model.ny, mpc.estim.nx̂, mpc.Hp, mpc.Hc
164132
notSolvedYet = (termination_status(optim) == OPTIMIZE_NOT_CALLED)
@@ -339,11 +307,22 @@ end
339307
setnonlincon!(::PredictiveController, ::SimModel) = nothing
340308

341309
"""
342-
default_Hp(model::LinModel, Hp)
310+
default_Hp(model::LinModel)
311+
312+
Estimate the default prediction horizon `Hp` for [`LinModel`](@ref).
313+
"""
314+
default_Hp(model::LinModel) = DEFAULT_HP0 + estimate_delays(model)
315+
"Throw an error when model is not a [`LinModel`](@ref)."
316+
function default_Hp(::SimModel)
317+
throw(ArgumentError("Prediction horizon Hp must be explicitly specified if model is not a LinModel."))
318+
end
319+
320+
"""
321+
estimate_delays(model::LinModel)
343322
344-
Estimate the default prediction horizon `Hp` with a security margin for [`LinModel`](@ref).
323+
Estimate the number of delays in `model` with a security margin.
345324
"""
346-
function default_Hp(model::LinModel, Hp)
325+
function estimate_delays(model::LinModel)
347326
# TODO: also check for settling time (poles)
348327
# TODO: also check for non minimum phase systems (zeros)
349328
# TODO: replace sum with max delay between all the I/O
@@ -352,36 +331,10 @@ function default_Hp(model::LinModel, Hp)
352331
# atol=1e-3 to overestimate the number of delays : for closed-loop stability, it is
353332
# better to overestimate the default value of Hp, as a security margin.
354333
nk = sum(isapprox.(abs.(poles), 0.0, atol=1e-3)) # number of delays
355-
if isnothing(Hp)
356-
Hp = DEFAULT_HP0 + nk
357-
end
358-
if Hp nk
359-
@warn("prediction horizon Hp ($Hp) ≤ estimated number of delays in model "*
360-
"($nk), the closed-loop system may be unstable or zero-gain (unresponsive)")
361-
end
362-
return Hp
363-
end
364-
365-
"""
366-
default_Hp(model::SimModel, Hp)
367-
368-
Throw an error if `isnothing(Hp)` when model is not a [`LinModel`](@ref).
369-
"""
370-
function default_Hp(::SimModel, Hp)
371-
if isnothing(Hp)
372-
# TODO:
373-
# ------------ will be deleted in the future ------------------------------------
374-
Base.depwarn("Hp=nothing is deprecated for NonLinModel, explicitly specify an "*
375-
"integer value", :NonLinMPC)
376-
Hp = DEFAULT_HP0
377-
# ------------- and replaced by this -------------------------------------------
378-
# throw(ArgumentError("Prediction horizon Hp must be explicitly specified if "*
379-
# "model is not a LinModel."))
380-
# Hp = 0
381-
# -----------------------------------------------------------------------------
382-
end
383-
return Hp
334+
return nk
384335
end
336+
"Return `0` when model is not a [`LinModel`](@ref)."
337+
estimate_delays(::SimModel) = 0
385338

386339
"""
387340
validate_args(mpc::PredictiveController, ry, d, D̂, R̂y, R̂u)

src/controller/execute.jl

+13-13
Original file line numberDiff line numberDiff line change
@@ -73,19 +73,19 @@ Get additional info about `mpc` [`PredictiveController`](@ref) optimum for troub
7373
The function should be called after calling [`moveinput!`](@ref). It returns the dictionary
7474
`info` with the following fields:
7575
76-
- `:ΔU` : optimal manipulated input increments over ``H_c``, ``\mathbf{ΔU}``.
77-
- `:ϵ` : optimal slack variable, ``ϵ``.
78-
- `:J` : objective value optimum, ``J``.
79-
- `:U` : optimal manipulated inputs over ``H_p``, ``\mathbf{U}``.
80-
- `:u` : current optimal manipulated input, ``\mathbf{u}(k)``.
81-
- `:d` : current measured disturbance, ``\mathbf{d}(k)``.
82-
- `:D̂` : predicted measured disturbances over ``H_p``, ``\mathbf{D̂}``.
83-
- `:ŷ` : current estimated output, ``\mathbf{ŷ}(k)``.
84-
- `:Ŷ` : optimal predicted outputs over ``H_p``, ``\mathbf{Ŷ}``.
85-
- `:x̂end`: optimal terminal states, ``\mathbf{x̂}_{k-1}(k+H_p)``.
86-
- `:Ŷs` : predicted stochastic output over ``H_p`` of [`InternalModel`](@ref), ``\mathbf{Ŷ_s}``.
87-
- `:R̂y` : predicted output setpoint over ``H_p``, ``\mathbf{R̂_y}``.
88-
- `:R̂u` : predicted manipulated input setpoint over ``H_p``, ``\mathbf{R̂_u}``.
76+
- `:ΔU` : optimal manipulated input increments over ``H_c``, ``\mathbf{ΔU}``
77+
- `:ϵ` : optimal slack variable, ``ϵ``
78+
- `:J` : objective value optimum, ``J``
79+
- `:U` : optimal manipulated inputs over ``H_p``, ``\mathbf{U}``
80+
- `:u` : current optimal manipulated input, ``\mathbf{u}(k)``
81+
- `:d` : current measured disturbance, ``\mathbf{d}(k)``
82+
- `:D̂` : predicted measured disturbances over ``H_p``, ``\mathbf{D̂}``
83+
- `:ŷ` : current estimated output, ``\mathbf{ŷ}(k)``
84+
- `:Ŷ` : optimal predicted outputs over ``H_p``, ``\mathbf{Ŷ}``
85+
- `:x̂end`: optimal terminal states, ``\mathbf{x̂}_{k-1}(k+H_p)``
86+
- `:Ŷs` : predicted stochastic output over ``H_p`` of [`InternalModel`](@ref), ``\mathbf{Ŷ_s}``
87+
- `:R̂y` : predicted output setpoint over ``H_p``, ``\mathbf{R̂_y}``
88+
- `:R̂u` : predicted manipulated input setpoint over ``H_p``, ``\mathbf{R̂_u}``
8989
9090
For [`LinMPC`](@ref) and [`NonLinMPC`](@ref), the field `:sol` also contains the optimizer
9191
solution summary that can be printed. Lastly, the optimal economic cost `:JE` is also

src/controller/explicitmpc.jl

+23-30
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ struct ExplicitMPC{NT<:Real, SE<:StateEstimator} <: PredictiveController{NT}
4040
Cwt = Inf # no slack variable ϵ for ExplicitMPC
4141
Ewt = 0 # economic costs not supported for ExplicitMPC
4242
validate_weights(model, Hp, Hc, M_Hp, N_Hc, L_Hp, Cwt)
43-
M_Hp, N_Hc, L_Hp = float(M_Hp), float(N_Hc), float(L_Hp) # debug julia 1.6
43+
M_Hp, N_Hc, L_Hp = Diagonal{NT}(M_Hp), Diagonal{NT}(N_Hc), Diagonal{NT}(L_Hp) # debug julia 1.6
4444
# dummy vals (updated just before optimization):
4545
R̂y, R̂u = zeros(NT, ny*Hp), zeros(NT, nu*Hp)
4646
noR̂u = iszero(L_Hp)
@@ -88,19 +88,11 @@ The controller minimizes the following objective function at each discrete time
8888
8989
See [`LinMPC`](@ref) for the variable definitions. This controller does not support
9090
constraints but the computational costs are extremely low (array division), therefore
91-
suitable for applications that require small sample times. This method uses the default
92-
state estimator, a [`SteadyKalmanFilter`](@ref) with default arguments.
93-
94-
# Arguments
95-
- `model::LinModel` : model used for controller predictions and state estimations.
96-
- `Hp=10+nk`: prediction horizon ``H_p``, `nk` is the number of delays in `model`.
97-
- `Hc=2` : control horizon ``H_c``.
98-
- `Mwt=fill(1.0,model.ny)` : main diagonal of ``\mathbf{M}`` weight matrix (vector).
99-
- `Nwt=fill(0.1,model.nu)` : main diagonal of ``\mathbf{N}`` weight matrix (vector).
100-
- `Lwt=fill(0.0,model.nu)` : main diagonal of ``\mathbf{L}`` weight matrix (vector).
101-
- `M_Hp` / `N_Hc` / `L_Hp` : diagonal matrices ``\mathbf{M}_{H_p}, \mathbf{N}_{H_c},
102-
\mathbf{L}_{H_p}``, for time-varying weights (generated from `Mwt/Nwt/Lwt` args if omitted).
103-
- additional keyword arguments are passed to [`SteadyKalmanFilter`](@ref) constructor.
91+
suitable for applications that require small sample times. The keyword arguments are
92+
identical to [`LinMPC`](@ref), except for `Cwt` and `optim` which are not supported.
93+
94+
This method uses the default state estimator, a [`SteadyKalmanFilter`](@ref) with default
95+
arguments.
10496
10597
# Examples
10698
```jldoctest
@@ -120,14 +112,14 @@ ExplicitMPC controller with a sample time Ts = 4.0 s, SteadyKalmanFilter estimat
120112
"""
121113
function ExplicitMPC(
122114
model::LinModel;
123-
Hp::Union{Int, Nothing} = nothing,
115+
Hp::Int = default_Hp(model),
124116
Hc::Int = DEFAULT_HC,
125117
Mwt = fill(DEFAULT_MWT, model.ny),
126118
Nwt = fill(DEFAULT_NWT, model.nu),
127119
Lwt = fill(DEFAULT_LWT, model.nu),
128-
M_Hp = nothing,
129-
N_Hc = nothing,
130-
L_Hp = nothing,
120+
M_Hp = Diagonal(repeat(Mwt, Hp)),
121+
N_Hc = Diagonal(repeat(Nwt, Hc)),
122+
L_Hp = Diagonal(repeat(Lwt, Hp)),
131123
kwargs...
132124
)
133125
estim = SteadyKalmanFilter(model; kwargs...)
@@ -158,20 +150,21 @@ ExplicitMPC controller with a sample time Ts = 4.0 s, KalmanFilter estimator and
158150
"""
159151
function ExplicitMPC(
160152
estim::SE;
161-
Hp::Union{Int, Nothing} = nothing,
153+
Hp::Int = default_Hp(estim.model),
162154
Hc::Int = DEFAULT_HC,
163-
Mwt = fill(DEFAULT_MWT, estim.model.ny),
164-
Nwt = fill(DEFAULT_NWT, estim.model.nu),
165-
Lwt = fill(DEFAULT_LWT, estim.model.nu),
166-
M_Hp = nothing,
167-
N_Hc = nothing,
168-
L_Hp = nothing
155+
Mwt = fill(DEFAULT_MWT, estim.model.ny),
156+
Nwt = fill(DEFAULT_NWT, estim.model.nu),
157+
Lwt = fill(DEFAULT_LWT, estim.model.nu),
158+
M_Hp = Diagonal(repeat(Mwt, Hp)),
159+
N_Hc = Diagonal(repeat(Nwt, Hc)),
160+
L_Hp = Diagonal(repeat(Lwt, Hp)),
169161
) where {NT<:Real, SE<:StateEstimator{NT}}
170-
isa(estim.model, LinModel) || error("estim.model type must be LinModel")
171-
Hp = default_Hp(estim.model, Hp)
172-
isnothing(M_Hp) && (M_Hp = Diagonal{NT}(repeat(Mwt, Hp)))
173-
isnothing(N_Hc) && (N_Hc = Diagonal{NT}(repeat(Nwt, Hc)))
174-
isnothing(L_Hp) && (L_Hp = Diagonal{NT}(repeat(Lwt, Hp)))
162+
isa(estim.model, LinModel) || error("estim.model type must be a LinModel")
163+
nk = estimate_delays(estim.model)
164+
if Hp nk
165+
@warn("prediction horizon Hp ($Hp) ≤ estimated number of delays in model "*
166+
"($nk), the closed-loop system may be unstable or zero-gain (unresponsive)")
167+
end
175168
return ExplicitMPC{NT, SE}(estim, Hp, Hc, M_Hp, N_Hc, L_Hp)
176169
end
177170

src/controller/linmpc.jl

+27-24
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ struct LinMPC{
4848
= copy(model.yop) # dummy vals (updated just before optimization)
4949
Ewt = 0 # economic costs not supported for LinMPC
5050
validate_weights(model, Hp, Hc, M_Hp, N_Hc, L_Hp, Cwt)
51-
M_Hp, N_Hc, L_Hp = float(M_Hp), float(N_Hc), float(L_Hp) # debug julia 1.6
51+
M_Hp, N_Hc, L_Hp = Diagonal{NT}(M_Hp), Diagonal{NT}(N_Hc), Diagonal{NT}(L_Hp) # debug julia 1.6
5252
# dummy vals (updated just before optimization):
5353
R̂y, R̂u = zeros(NT, ny*Hp), zeros(NT, nu*Hp)
5454
noR̂u = iszero(L_Hp)
@@ -93,7 +93,7 @@ The controller minimizes the following objective function at each discrete time
9393
+ C ϵ^2
9494
\end{aligned}
9595
```
96-
in which the weight matrices are repeated ``H_p`` or ``H_c`` times:
96+
in which the weight matrices are repeated ``H_p`` or ``H_c`` times by default:
9797
```math
9898
\begin{aligned}
9999
\mathbf{M}_{H_p} &= \text{diag}\mathbf{(M,M,...,M)} \\
@@ -118,12 +118,14 @@ arguments.
118118
- `Mwt=fill(1.0,model.ny)` : main diagonal of ``\mathbf{M}`` weight matrix (vector).
119119
- `Nwt=fill(0.1,model.nu)` : main diagonal of ``\mathbf{N}`` weight matrix (vector).
120120
- `Lwt=fill(0.0,model.nu)` : main diagonal of ``\mathbf{L}`` weight matrix (vector).
121+
- `M_Hp=Diagonal(repeat(Mwt),Hp)` : diagonal weight matrix ``\mathbf{M}_{H_p}``.
122+
- `N_Hc=Diagonal(repeat(Nwt),Hc)` : diagonal weight matrix ``\mathbf{N}_{H_c}``.
123+
- `L_Hp=Diagonal(repeat(Lwt),Hp)` : diagonal weight matrix ``\mathbf{L}_{H_p}``.
121124
- `Cwt=1e5` : slack variable weight ``C`` (scalar), use `Cwt=Inf` for hard constraints only.
122125
- `optim=JuMP.Model(OSQP.MathOptInterfaceOSQP.Optimizer)` : quadratic optimizer used in
123126
the predictive controller, provided as a [`JuMP.Model`](https://jump.dev/JuMP.jl/stable/api/JuMP/#JuMP.Model)
124127
(default to [`OSQP`](https://osqp.org/docs/parsers/jump.html) optimizer).
125-
- `M_Hp` / `N_Hc` / `L_Hp` : diagonal matrices ``\mathbf{M}_{H_p}, \mathbf{N}_{H_c},
126-
\mathbf{L}_{H_p}``, for time-varying weights (generated from `Mwt/Nwt/Lwt` args if omitted).
128+
127129
- additional keyword arguments are passed to [`SteadyKalmanFilter`](@ref) constructor.
128130
129131
# Examples
@@ -166,15 +168,15 @@ LinMPC controller with a sample time Ts = 4.0 s, OSQP optimizer, SteadyKalmanFil
166168
"""
167169
function LinMPC(
168170
model::LinModel;
169-
Hp::Union{Int, Nothing} = nothing,
171+
Hp::Int = default_Hp(model),
170172
Hc::Int = DEFAULT_HC,
171-
Mwt = fill(DEFAULT_MWT, model.ny),
172-
Nwt = fill(DEFAULT_NWT, model.nu),
173-
Lwt = fill(DEFAULT_LWT, model.nu),
173+
Mwt = fill(DEFAULT_MWT, model.ny),
174+
Nwt = fill(DEFAULT_NWT, model.nu),
175+
Lwt = fill(DEFAULT_LWT, model.nu),
176+
M_Hp = Diagonal(repeat(Mwt, Hp)),
177+
N_Hc = Diagonal(repeat(Nwt, Hc)),
178+
L_Hp = Diagonal(repeat(Lwt, Hp)),
174179
Cwt = DEFAULT_CWT,
175-
M_Hp = nothing,
176-
N_Hc = nothing,
177-
L_Hp = nothing,
178180
optim::JuMP.GenericModel = JuMP.Model(DEFAULT_LINMPC_OPTIMIZER, add_bridges=false),
179181
kwargs...
180182
)
@@ -207,22 +209,23 @@ LinMPC controller with a sample time Ts = 4.0 s, OSQP optimizer, KalmanFilter es
207209
"""
208210
function LinMPC(
209211
estim::SE;
210-
Hp::Union{Int, Nothing} = nothing,
212+
Hp::Int = default_Hp(estim.model),
211213
Hc::Int = DEFAULT_HC,
212-
Mwt = fill(DEFAULT_MWT, estim.model.ny),
213-
Nwt = fill(DEFAULT_NWT, estim.model.nu),
214-
Lwt = fill(DEFAULT_LWT, estim.model.nu),
215-
Cwt = DEFAULT_CWT,
216-
M_Hp = nothing,
217-
N_Hc = nothing,
218-
L_Hp = nothing,
214+
Mwt = fill(DEFAULT_MWT, estim.model.ny),
215+
Nwt = fill(DEFAULT_NWT, estim.model.nu),
216+
Lwt = fill(DEFAULT_LWT, estim.model.nu),
217+
M_Hp = Diagonal(repeat(Mwt, Hp)),
218+
N_Hc = Diagonal(repeat(Nwt, Hc)),
219+
L_Hp = Diagonal(repeat(Lwt, Hp)),
220+
Cwt = DEFAULT_CWT,
219221
optim::JM = JuMP.Model(DEFAULT_LINMPC_OPTIMIZER, add_bridges=false),
220222
) where {NT<:Real, SE<:StateEstimator{NT}, JM<:JuMP.GenericModel}
221-
isa(estim.model, LinModel) || error("estim.model type must be LinModel")
222-
Hp = default_Hp(estim.model, Hp)
223-
isnothing(M_Hp) && (M_Hp = Diagonal{NT}(repeat(Mwt, Hp)))
224-
isnothing(N_Hc) && (N_Hc = Diagonal{NT}(repeat(Nwt, Hc)))
225-
isnothing(L_Hp) && (L_Hp = Diagonal{NT}(repeat(Lwt, Hp)))
223+
isa(estim.model, LinModel) || error("estim.model type must be a LinModel")
224+
nk = estimate_delays(estim.model)
225+
if Hp nk
226+
@warn("prediction horizon Hp ($Hp) ≤ estimated number of delays in model "*
227+
"($nk), the closed-loop system may be unstable or zero-gain (unresponsive)")
228+
end
226229
return LinMPC{NT, SE, JM}(estim, Hp, Hc, M_Hp, N_Hc, L_Hp, Cwt, optim)
227230
end
228231

0 commit comments

Comments
 (0)