@@ -49,12 +49,13 @@ struct NonLinMPC{
4949 Dop:: Vector{NT}
5050 buffer:: PredictiveControllerBuffer{NT}
5151 function NonLinMPC {NT, SE, JM, JEfunc, P} (
52- estim:: SE , Hp, Hc, M_Hp, N_Hc, L_Hp, Cwt, Ewt, JE:: JEfunc , gE, nE , p:: P , optim:: JM
52+ estim:: SE , Hp, Hc, M_Hp, N_Hc, L_Hp, Cwt, Ewt, JE:: JEfunc , gc, nc , p:: P , optim:: JM
5353 ) where {NT<: Real , SE<: StateEstimator , JM<: JuMP.GenericModel , JEfunc<: Function , P<: Any }
5454 model = estim. model
5555 nu, ny, nd, nx̂ = model. nu, model. ny, model. nd, estim. nx̂
5656 ŷ = copy (model. yop) # dummy vals (updated just before optimization)
5757 validate_JE (NT, JE)
58+ gc! = get_mutating_gc (NT, gc)
5859 validate_weights (model, Hp, Hc, M_Hp, N_Hc, L_Hp, Cwt, Ewt)
5960 # convert `Diagonal` to normal `Matrix` if required:
6061 M_Hp = Hermitian (convert (Matrix{NT}, M_Hp), :L )
@@ -68,7 +69,7 @@ struct NonLinMPC{
6869 # dummy vals (updated just before optimization):
6970 F, fx̂ = zeros (NT, ny* Hp), zeros (NT, nx̂)
7071 con, nϵ, S̃, Ñ_Hc, Ẽ = init_defaultcon_mpc (
71- estim, Hp, Hc, Cwt, S, N_Hc, E, ex̂, fx̂, gx̂, jx̂, kx̂, vx̂, bx̂, gE, nE
72+ estim, Hp, Hc, Cwt, S, N_Hc, E, ex̂, fx̂, gx̂, jx̂, kx̂, vx̂, bx̂, gc!, nc
7273 )
7374 H̃ = init_quadprog (model, Ẽ, S̃, M_Hp, Ñ_Hc, L_Hp)
7475 # dummy vals (updated just before optimization):
@@ -112,30 +113,32 @@ controller minimizes the following objective function at each discrete time ``k`
112113 + \m athbf{(ΔU)}' \m athbf{N}_{H_c} \m athbf{(ΔU)} \\ &
113114 + \m athbf{(R̂_u - U)}' \m athbf{L}_{H_p} \m athbf{(R̂_u - U)}
114115 + C ϵ^2
115- + E J_E(\m athbf{U}_E , \m athbf{Ŷ}_E , \m athbf{D̂}_E , \m athbf{p})
116+ + E J_E(\m athbf{U_e} , \m athbf{Ŷ_e} , \m athbf{D̂_e} , \m athbf{p})
116117\e nd{aligned}
117118```
118- subject to [`setconstraint!`](@ref) bounds, and the economic inequality constraints:
119+ subject to [`setconstraint!`](@ref) bounds, and the custom inequality constraints:
119120```math
120- \m athbf{g}_E (\m athbf{U}_E , \m athbf{Ŷ}_E , \m athbf{D̂}_E, ϵ, \m athbf{p}) ≤ \m athbf{0}
121+ \m athbf{g_c} \B ig (\m athbf{U_e} , \m athbf{Ŷ_e} , \m athbf{D̂_e}, \m athbf{p}, ϵ(k) \B ig ) ≤ \m athbf{0}
121122```
122123The economic function ``J_E`` can penalizes solutions with high economic costs. Setting all
123- the weights to 0 except ``E`` creates a pure economic model predictive controller (EMPC).
124- The arguments of ``J_E`` and ``\m athbf{g}_E`` include the manipulated inputs, the predicted
125- outputs and measured disturbances from ``k`` to ``k+H_p`` inclusively:
124+ the weights to 0 except ``E`` creates a pure economic model predictive controller (EMPC).
125+ As a matter of fact, ``J_E`` can be any nonlinear function to customize the objective, even
126+ if there is no economic interpretation to it. The arguments of ``J_E`` and ``\m athbf{g_c}``
127+ include the manipulated inputs, predicted outputs and measured disturbances, extended from
128+ ``k`` to ``k + H_p`` (inclusively):
126129```math
127- \m athbf{U}_E = \b egin{bmatrix} \m athbf{U} \\ \m athbf{u}(k+H_p-1) \e nd{bmatrix} , \q uad
128- \m athbf{Ŷ}_E = \b egin{bmatrix} \m athbf{ŷ}(k) \\ \m athbf{Ŷ} \e nd{bmatrix} , \q uad
129- \m athbf{D̂}_E = \b egin{bmatrix} \m athbf{d}(k) \\ \m athbf{D̂} \e nd{bmatrix}
130+ \m athbf{U_e} = \b egin{bmatrix} \m athbf{U} \\ \m athbf{u}(k+H_p-1) \e nd{bmatrix} , \q uad
131+ \m athbf{Ŷ_e} = \b egin{bmatrix} \m athbf{ŷ}(k) \\ \m athbf{Ŷ} \e nd{bmatrix} , \q uad
132+ \m athbf{D̂_e} = \b egin{bmatrix} \m athbf{d}(k) \\ \m athbf{D̂} \e nd{bmatrix}
130133```
131134since ``H_c ≤ H_p`` implies that ``\m athbf{Δu}(k+H_p) = \m athbf{0}`` or ``\m athbf{u}(k+H_p)=
132- \m athbf{u}(k+H_p-1)``. The vector ``\m athbf{D̂}`` includes the predicted measured disturbance
133- over ``H_p``. The argument ``\m athbf{p}`` is a custom parameter object of any type but use a
134- mutable one if you want to modify it later e.g.: a vector.
135+ \m athbf{u}(k+H_p-1)``. The vector ``\m athbf{D̂}`` comprises the measured disturbance
136+ predictions over ``H_p``. The argument ``\m athbf{p}`` is a custom parameter object of any
137+ type, but use a mutable one if you want to modify it later e.g.: a vector.
135138
136139!!! tip
137- Replace any of the arguments of ``J_E`` and ``\m athbf{g}_E `` functions with `_` if not
138- needed (details in Extended Help ).
140+ Replace any of the arguments of ``J_E`` and ``\m athbf{g_c} `` functions with `_` if not
141+ needed (see e.g. the default value of `JE` below ).
139142
140143See [`LinMPC`](@ref) for the definition of the other variables. This method uses the default
141144state estimator :
@@ -159,12 +162,13 @@ state estimator :
159162- `L_Hp=diagm(repeat(Lwt,Hp))` : positive semidefinite symmetric matrix ``\m athbf{L}_{H_p}``.
160163- `Cwt=1e5` : slack variable weight ``C`` (scalar), use `Cwt=Inf` for hard constraints only.
161164- `Ewt=0.0` : economic costs weight ``E`` (scalar).
162- - `JE=(_,_,_,_)->0.0` : economic (or custom) cost function ``J_E(\m athbf{U}_E, \m athbf{Ŷ}_E,
163- \m athbf{D̂}_E, \m athbf{p})`` (details in Extended Help).
164- - `gE=(_,_,_,_,_,_)->nothing` : economic (or custom) constraint function ``\m athbf{g}_E(
165- \m athbf{U}_E, \m athbf{Ŷ}_E, \m athbf{D̂}_E, ϵ, \m athbf{p})`` (details in Extended Help).
166- - `nE=0` : number of economic constraints.
167- - `p=model.p` : ``J_E`` and ``\m athbf{g}_E`` functions parameter ``\m athbf{p}`` (any type).
165+ - `JE=(_,_,_,_)->0.0` : economic or custom cost function ``J_E(\m athbf{U_e}, \m athbf{Ŷ_e},
166+ \m athbf{D̂_e}, \m athbf{p})``.
167+ - `gc=(_,_,_,_,_,_)->nothing` or `gc!` : custom inequality constraint function
168+ ``\m athbf{g_c}(\m athbf{U_e}, \m athbf{Ŷ_e}, \m athbf{D̂_e}, \m athbf{p}, ϵ)`` (mutating or
169+ not, details in Extended Help).
170+ - `nc=0` : number of custom inequality constraints.
171+ - `p=model.p` : ``J_E`` and ``\m athbf{g_c}`` functions parameter ``\m athbf{p}`` (any type).
168172- `optim=JuMP.Model(Ipopt.Optimizer)` : nonlinear optimizer used in the predictive
169173 controller, provided as a [`JuMP.Model`](https://jump.dev/JuMP.jl/stable/api/JuMP/#JuMP.Model)
170174 (default to [`Ipopt`](https://github.com/jump-dev/Ipopt.jl) optimizer).
@@ -193,22 +197,24 @@ NonLinMPC controller with a sample time Ts = 10.0 s, Ipopt optimizer, UnscentedK
193197 algebra instead of a `for` loop. This feature can accelerate the optimization, especially
194198 for the constraint handling, and is not available in any other package, to my knowledge.
195199
196- The J
197-
198- 1. **Non-mutating functions** (out-of-place): define them as `f(x, u, d, p) -> ẋ` and
199- `h(x, d, p) -> y`. This syntax is simple and intuitive but it allocates more memory.
200- 2. **Mutating functions** (in-place): define them as `f!(ẋ, x, u, d, p) -> nothing` and
201- `h!(y, x, d, p) -> nothing`. This syntax reduces the allocations and potentially the
202- computational burden as well.
203-
204-
205200 The optimization relies on [`JuMP`](https://github.com/jump-dev/JuMP.jl) automatic
206201 differentiation (AD) to compute the objective and constraint derivatives. Optimizers
207202 generally benefit from exact derivatives like AD. However, the [`NonLinModel`](@ref)
208203 state-space functions must be compatible with this feature. See [Automatic differentiation](https://jump.dev/JuMP.jl/stable/manual/nlp/#Automatic-differentiation)
209204 for common mistakes when writing these functions.
210205
211- Note that if `Cwt≠Inf`, the attribute `nlp_scaling_max_gradient` of `Ipopt` is set to
206+ If `LHS` represents the result of the left-hand side in the inequality
207+ ``\m athbf{g_c}(\m athbf{U_e}, \m athbf{Ŷ_e}, \m athbf{D̂_e}, \m athbf{p}, ϵ) ≤ \m athbf{0}``,
208+ the function `gc` can be implemented in two ways:
209+
210+ 1. **Non-mutating function** (out-of-place): define it as `gc(Ue, Ŷe, D̂e, p, ϵ) -> LHS`.
211+ This syntax is simple and intuitive but it allocates more memory.
212+ 2. **Mutating function** (in-place): define it as `gc!(LHS, Ue, Ŷe, D̂e, p, ϵ) -> nothing`.
213+ This syntax reduces the allocations and potentially the computational burden as well.
214+
215+ The keyword argument `nc` is the number of elements in the `LHS` vector, and `gc!`, an
216+ alias for the `gc` argument (both accepts non-mutating and mutating functions). Note
217+ that if `Cwt≠Inf`, the attribute `nlp_scaling_max_gradient` of `Ipopt` is set to
212218 `10/Cwt` (if not already set), to scale the small values of ``ϵ``.
213219"""
214220function NonLinMPC (
@@ -224,16 +230,17 @@ function NonLinMPC(
224230 Cwt = DEFAULT_CWT,
225231 Ewt = DEFAULT_EWT,
226232 JE:: Function = (_,_,_,_) -> 0.0 ,
227- gE:: Function = (_,_,_,_,_,_) -> nothing ,
228- nE = 0 ,
233+ gc!:: Function = (_,_,_,_,_,_) -> nothing ,
234+ gc :: Function = gc!,
235+ nc:: Int = 0 ,
229236 p = model. p,
230237 optim:: JuMP.GenericModel = JuMP. Model (DEFAULT_NONLINMPC_OPTIMIZER, add_bridges= false ),
231238 kwargs...
232239)
233240 estim = UnscentedKalmanFilter (model; kwargs... )
234241 return NonLinMPC (
235242 estim;
236- Hp, Hc, Mwt, Nwt, Lwt, Cwt, Ewt, JE, gE, nE , p, M_Hp, N_Hc, L_Hp, optim
243+ Hp, Hc, Mwt, Nwt, Lwt, Cwt, Ewt, JE, gc, nc , p, M_Hp, N_Hc, L_Hp, optim
237244 )
238245end
239246
@@ -249,17 +256,18 @@ function NonLinMPC(
249256 L_Hp = diagm (repeat (Lwt, Hp)),
250257 Cwt = DEFAULT_CWT,
251258 Ewt = DEFAULT_EWT,
252- JE:: Function = (_,_,_,_) -> 0.0 ,
253- gE:: Function = (_,_,_,_,_,_) -> nothing ,
254- nE = 0 ,
259+ JE :: Function = (_,_,_,_) -> 0.0 ,
260+ gc!:: Function = (_,_,_,_,_,_) -> nothing ,
261+ gc :: Function = gc!,
262+ nc:: Int = 0 ,
255263 p = model. p,
256264 optim:: JuMP.GenericModel = JuMP. Model (DEFAULT_NONLINMPC_OPTIMIZER, add_bridges= false ),
257265 kwargs...
258266)
259267 estim = SteadyKalmanFilter (model; kwargs... )
260268 return NonLinMPC (
261269 estim;
262- Hp, Hc, Mwt, Nwt, Lwt, Cwt, Ewt, JE, gE, nE , p, M_Hp, N_Hc, L_Hp, optim
270+ Hp, Hc, Mwt, Nwt, Lwt, Cwt, Ewt, JE, gc, nc , p, M_Hp, N_Hc, L_Hp, optim
263271 )
264272end
265273
@@ -299,17 +307,17 @@ function NonLinMPC(
299307 L_Hp = diagm (repeat (Lwt, Hp)),
300308 Cwt = DEFAULT_CWT,
301309 Ewt = DEFAULT_EWT,
302- JE:: JEfunc = (_,_,_,_) -> 0.0 ,
303- gE:: GEfunc = (_,_,_,_,_,_) -> nothing ,
304- nE = 0 ,
310+ JE :: JEfunc = (_,_,_,_) -> 0.0 ,
311+ gc!:: Function = (_,_,_,_,_,_) -> nothing ,
312+ gc :: Function = gc!,
313+ nc = 0 ,
305314 p:: P = estim. model. p,
306315 optim:: JM = JuMP. Model (DEFAULT_NONLINMPC_OPTIMIZER, add_bridges= false ),
307316) where {
308317 NT<: Real ,
309318 SE<: StateEstimator{NT} ,
310319 JM<: JuMP.GenericModel ,
311- JEfunc<: Function ,
312- GEfunc<: Function ,
320+ JEfunc<: Function ,
313321 P<: Any
314322}
315323 nk = estimate_delays (estim. model)
@@ -318,7 +326,7 @@ function NonLinMPC(
318326 " ($nk ), the closed-loop system may be unstable or zero-gain (unresponsive)" )
319327 end
320328 return NonLinMPC {NT, SE, JM, JEfunc, P} (
321- estim, Hp, Hc, M_Hp, N_Hc, L_Hp, Cwt, Ewt, JE, gE, nE , p, optim
329+ estim, Hp, Hc, M_Hp, N_Hc, L_Hp, Cwt, Ewt, JE, gc, nc , p, optim
322330 )
323331end
324332
@@ -328,15 +336,54 @@ end
328336Validate `JE` function argument signature.
329337"""
330338function validate_JE (NT, JE)
339+ # Ue, Ŷe, D̂e, p
331340 if ! hasmethod (JE, Tuple{Vector{NT}, Vector{NT}, Vector{NT}, Any})
332341 error (
333- " the economic function has no method with type signature " *
334- " JE(UE ::Vector{$(NT) }, ŶE ::Vector{$(NT) }, D̂E ::Vector{$(NT) }, p::Any)"
342+ " the economic cost function has no method with type signature " *
343+ " JE(Ue ::Vector{$(NT) }, Ŷe ::Vector{$(NT) }, D̂e ::Vector{$(NT) }, p::Any)"
335344 )
336345 end
337346 return nothing
338347end
339348
349+ " Get mutating custom constraint function `gc!` from the provided function in argument."
350+ function get_mutating_gc (NT, gc)
351+ ismutating_gc = validate_gc (NT, gc)
352+ gc! = if ismutating_gc
353+ gc
354+ else
355+ function gc! (LHS, Ue, Ŷe, D̂e, p, ϵ)
356+ LHS .= gc (Ue, Ŷe, D̂e, p, ϵ)
357+ return nothing
358+ end
359+ end
360+ return gc!
361+ end
362+
363+ """
364+ validate_gc(NT, gc) -> ismutating
365+
366+ Validate `gc` function argument signature and return `true` if it is mutating.
367+ """
368+ function validate_gc (NT, gc)
369+ ismutating = hasmethod (
370+ gc,
371+ # LHS, Ue, Ŷe, D̂e, p, ϵ
372+ Tuple{Vector{NT}, Vector{NT}, Vector{NT}, Vector{NT}, Any, NT}
373+ )
374+ println (ismutating)
375+ # Ue, Ŷe, D̂e, p, ϵ
376+ if ! (ismutating || hasmethod (gc, Tuple{Vector{NT}, Vector{NT}, Vector{NT}, Any, NT}))
377+ error (
378+ " the custom constraint function has no method with type signature " *
379+ " gc(UE::Vector{$(NT) }, ŶE::Vector{$(NT) }, D̂E::Vector{$(NT) }, p::Any, ϵ::$(NT) ) " *
380+ " or mutating form gc!(LHS::Vector{$(NT) }, Ue::Vector{$(NT) }, Ŷe::Vector{$(NT) }, " *
381+ " D̂e::Vector{$(NT) }, p::Any, ϵ::$(NT) )"
382+ )
383+ end
384+ return ismutating
385+ end
386+
340387"""
341388 addinfo!(info, mpc::NonLinMPC) -> info
342389
0 commit comments