Skip to content

Commit b6a92cb

Browse files
committed
added : changing constraints at runtime is supported
1 parent 7859852 commit b6a92cb

File tree

2 files changed

+196
-104
lines changed

2 files changed

+196
-104
lines changed

src/predictive_control.jl

Lines changed: 71 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,24 @@ The predictive controllers support both soft and hard constraints, defined by:
7070
\mathbf{y_{min} - c_{y_{min}}} ϵ &≤ \mathbf{ŷ}(k+j) &&≤ \mathbf{y_{max} + c_{y_{max}}} ϵ &&\qquad j = 1, 2 ,..., H_p \\
7171
\end{alignat*}
7272
```
73-
and also ``ϵ ≥ 0``. All the constraint parameters are vector. Use `±Inf` values when there
74-
is no bound. The constraint softness parameters ``\mathbf{c}``, also called equal concern
75-
for relaxation, are non-negative values that specify the softness of the associated bound.
76-
Use `0.0` values for hard constraints. The output constraints ``\mathbf{y_{min}}`` and
73+
and also ``ϵ ≥ 0``. All the constraint parameters are vector. Use `±Inf` values when there
74+
is no bound. The constraint softness parameters ``\mathbf{c}``, also called equal concern
75+
for relaxation, are non-negative values that specify the softness of the associated bound.
76+
Use `0.0` values for hard constraints. The output constraints ``\mathbf{y_{min}}`` and
7777
``\mathbf{y_{max}}`` are soft by default.
7878
79+
The bounds can be modified after calling [`moveinput!`](@ref), that is, at runtime, but not
80+
the softness parameters ``\mathbf{c}``. It is not possible to modify `±Inf` constraints
81+
at runtime.
82+
83+
!!! tip
84+
To keep a variable unconstrained while maintaining the ability to add a constraint later
85+
at runtime, set the bound to an absolute value sufficiently large when you create the
86+
controller.
87+
88+
Varying the constraints over the prediction/control horizon is not supported yet but it will
89+
be implemented soon.
90+
7991
# Arguments
8092
!!! info
8193
The default constraints are mentioned here for clarity but omitting a keyword argument
@@ -123,13 +135,16 @@ function setconstraint!(
123135
ŷmin = nothing, ŷmax = nothing,
124136
c_ŷmin = nothing, c_ŷmax = nothing,
125137
)
126-
model = mpc.estim.model
127-
con = mpc.con
128-
nu, ny = model.nu, model.ny
129-
Hp, Hc = mpc.Hp, mpc.Hc
138+
model, con, optim = mpc.estim.model, mpc.con, mpc.optim
139+
nu, ny, Hp, Hc = model.nu, model.ny, mpc.Hp, mpc.Hc
140+
notSolvedYet = (termination_status(optim) == OPTIMIZE_NOT_CALLED)
130141
C, E = mpc.C, mpc.Ẽ[:, 1:nu*Hc]
142+
if !all(isnothing.([c_umin, c_umax, c_Δumin, c_Δumax, c_ymin, c_ymax]))
143+
!isinf(C) || throw(ArgumentError("Slack variable Cwt must be finite to set softness parameters"))
144+
notSolvedYet || error("Cannot set softness parameters after calling moveinput!")
145+
end
131146

132-
# these 4 if will be deleted in the future:
147+
# these 4 `if`s will be deleted in the future:
133148
if !isnothing(ŷmin)
134149
Base.depwarn("keyword arg ŷmin is deprecated, use ymin instead", :setconstraint!)
135150
ymin = ŷmin
@@ -148,75 +163,69 @@ function setconstraint!(
148163
end
149164

150165
if !isnothing(umin)
151-
size(umin) == (nu,) || error("umin size must be $((nu,))")
166+
size(umin) == (nu,) || throw(ArgumentError("umin size must be $((nu,))"))
152167
Umin = repeat(umin, Hc)
153168
con.Umin[:] = Umin
154169
end
155170
if !isnothing(umax)
156-
size(umax) == (nu,) || error("umax size must be $((nu,))")
157-
Umax = repeat(umax, Hc)
171+
size(umax) == (nu,) || throw(ArgumentError("umax size must be $((nu,))"))
172+
Umax = repeat(umax, Hc)
158173
con.Umax[:] = Umax
159174
end
160175
if !isnothing(Δumin)
161-
size(Δumin) == (nu,) || error("Δumin size must be $((nu,))")
176+
size(Δumin) == (nu,) || throw(ArgumentError("Δumin size must be $((nu,))"))
162177
ΔUmin = repeat(Δumin, Hc)
163178
con.ΔŨmin[1:nu*Hc] = ΔUmin
164179
end
165180
if !isnothing(Δumax)
166-
size(Δumax) == (nu,) || error("Δumax size must be $((nu,))")
181+
size(Δumax) == (nu,) || throw(ArgumentError("Δumax size must be $((nu,))"))
167182
ΔUmax = repeat(Δumax, Hc)
168183
con.ΔŨmax[1:nu*Hc] = ΔUmax
169184
end
170185
if !isnothing(ymin)
171-
size(ymin) == (ny,) || error("ymin size must be $((ny,))")
186+
size(ymin) == (ny,) || throw(ArgumentError("ymin size must be $((ny,))"))
172187
Ymin = repeat(ymin, Hp)
173188
con.Ymin[:] = Ymin
174189
end
175190
if !isnothing(ymax)
176-
size(ymax) == (ny,) || error("ymax size must be $((ny,))")
191+
size(ymax) == (ny,) || throw(ArgumentError("ymax size must be $((ny,))"))
177192
Ymax = repeat(ymax, Hp)
178193
con.Ymax[:] = Ymax
179194
end
180195
if !isnothing(c_umin)
181-
!isinf(C) || error("Slack variable Cwt must be finite to set softness parameters")
182-
size(c_umin) == (nu,) || error("c_umin size must be $((nu,))")
196+
size(c_umin) == (nu,) || throw(ArgumentError("c_umin size must be $((nu,))"))
183197
any(c_umin .< 0) && error("c_umin weights should be non-negative")
184198
c_Umin = repeat(c_umin, Hc)
185199
con.A_Umin[:, end] = -c_Umin
186200
end
187201
if !isnothing(c_umax)
188-
!isinf(C) || error("Slack variable Cwt must be finite to set softness parameters")
189-
size(c_umax) == (nu,) || error("c_umax size must be $((nu,))")
202+
size(c_umax) == (nu,) || throw(ArgumentError("c_umax size must be $((nu,))"))
190203
any(c_umax .< 0) && error("c_umax weights should be non-negative")
191204
c_Umax = repeat(c_umax, Hc)
192205
con.A_Umax[:, end] = -c_Umax
193206
end
194207
if !isnothing(c_Δumin)
195-
!isinf(C) || error("Slack variable Cwt must be finite to set softness parameters")
196-
size(c_Δumin) == (nu,) || error("c_Δumin size must be $((nu,))")
208+
size(c_Δumin) == (nu,) || throw(ArgumentError("c_Δumin size must be $((nu,))"))
197209
any(c_Δumin .< 0) && error("c_Δumin weights should be non-negative")
198210
c_ΔUmin = repeat(c_Δumin, Hc)
199211
con.A_ΔŨmin[1:end-1, end] = -c_ΔUmin
200212
end
201213
if !isnothing(c_Δumax)
202-
!isinf(C) || error("Slack variable Cwt must be finite to set softness parameters")
203-
size(c_Δumax) == (nu,) || error("c_Δumax size must be $((nu,))")
214+
size(c_Δumax) == (nu,) || throw(ArgumentError("c_Δumax size must be $((nu,))"))
204215
any(c_Δumax .< 0) && error("c_Δumax weights should be non-negative")
205216
c_ΔUmax = repeat(c_Δumax, Hc)
206217
con.A_ΔŨmax[1:end-1, end] = -c_ΔUmax
207218
end
208219
if !isnothing(c_ymin)
209-
!isinf(C) || error("Slack variable Cwt must be finite to set softness parameters")
210-
size(c_ymin) == (ny,) || error("c_ymin size must be $((ny,))")
220+
size(c_ymin) == (ny,) || throw(ArgumentError("c_ymin size must be $((ny,))"))
211221
any(c_ymin .< 0) && error("c_ymin weights should be non-negative")
212222
c_Ymin = repeat(c_ymin, Hp)
213223
con.c_Ymin[:] = c_Ymin
214224
A_Ymin ,_ = relaxŶ(model, C, con.c_Ymin, con.c_Ymax, E)
215225
con.A_Ymin[:] = A_Ymin
216226
end
217227
if !isnothing(c_ymax)
218-
!isinf(C) || error("Slack variable Cwt must be finite to set softness parameters")
219-
size(c_ymax) == (ny,) || error("c_ymax size must be $((ny,))")
228+
size(c_ymax) == (ny,) || throw(ArgumentError("c_ymax size must be $((ny,))"))
220229
any(c_ymax .< 0) && error("c_ymax weights should be non-negative")
221230
c_Ymax = repeat(c_ymax, Hp)
222231
con.c_Ymax[:] = c_Ymax
@@ -226,17 +235,25 @@ function setconstraint!(
226235
i_Umin, i_Umax = .!isinf.(con.Umin), .!isinf.(con.Umax)
227236
i_ΔŨmin, i_ΔŨmax = .!isinf.(con.ΔŨmin), .!isinf.(con.ΔŨmin)
228237
i_Ymin, i_Ymax = .!isinf.(con.Ymin), .!isinf.(con.Ymax)
229-
con.A[:], con.i_b[:] = init_linconstraint(model,
230-
con.A_Umin, con.A_Umax, con.A_ΔŨmin, con.A_ΔŨmax, con.A_Ymin, con.A_Ymax,
231-
i_Umin, i_Umax, i_ΔŨmin, i_ΔŨmax, i_Ymin, i_Ymax
232-
)
233-
A = con.A[con.i_b, :]
234-
b = con.b[con.i_b]
235-
ΔŨvar = mpc.optim[:ΔŨvar]
236-
delete(mpc.optim, mpc.optim[:linconstraint])
237-
unregister(mpc.optim, :linconstraint)
238-
@constraint(mpc.optim, linconstraint, A*ΔŨvar .≤ b)
239-
setnonlincon!(mpc, model)
238+
if notSolvedYet
239+
con.i_b[:], con.A[:] = init_linconstraint(model,
240+
i_Umin, i_Umax, i_ΔŨmin, i_ΔŨmax, i_Ymin, i_Ymax,
241+
con.A_Umin, con.A_Umax, con.A_ΔŨmin, con.A_ΔŨmax, con.A_Ymin, con.A_Ymax
242+
)
243+
A = con.A[con.i_b, :]
244+
b = con.b[con.i_b]
245+
ΔŨvar = mpc.optim[:ΔŨvar]
246+
delete(mpc.optim, mpc.optim[:linconstraint])
247+
unregister(mpc.optim, :linconstraint)
248+
@constraint(mpc.optim, linconstraint, A*ΔŨvar .≤ b)
249+
setnonlincon!(mpc, model)
250+
else
251+
i_b, _ = init_linconstraint(model,
252+
i_Umin, i_Umax, i_ΔŨmin, i_ΔŨmax, i_Ymin, i_Ymax,
253+
con.A_Umin, con.A_Umax, con.A_ΔŨmin, con.A_ΔŨmax, con.A_Ymin, con.A_Ymax,
254+
)
255+
i_b == con.i_b || error("Cannot modify ±Inf constraints after calling moveinput!")
256+
end
240257
return mpc
241258
end
242259

@@ -826,11 +843,12 @@ function init_defaultcon(model, Hp, Hc, C, S_Hp, S_Hc, N_Hc, E)
826843
i_Umin, i_Umax = .!isinf.(Umin), .!isinf.(Umax)
827844
i_ΔŨmin, i_ΔŨmax = .!isinf.(ΔŨmin), .!isinf.(ΔŨmax)
828845
i_Ymin, i_Ymax = .!isinf.(Ymin), .!isinf.(Ymax)
829-
A, i_b, b = init_linconstraint(
846+
i_b, A = init_linconstraint(
830847
model,
831-
A_Umin, A_Umax, A_ΔŨmin, A_ΔŨmax, A_Ymin, A_Ymax,
832-
i_Umin, i_Umax, i_ΔŨmin, i_ΔŨmax, i_Ymin, i_Ymax
848+
i_Umin, i_Umax, i_ΔŨmin, i_ΔŨmax, i_Ymin, i_Ymax,
849+
A_Umin, A_Umax, A_ΔŨmin, A_ΔŨmax, A_Ymin, A_Ymax
833850
)
851+
b = zeros(size(A, 1)) # dummy b vector (updated just before optimization)
834852
con = ControllerConstraint(
835853
Umin , Umax , ΔŨmin , ΔŨmax , Ymin , Ymax,
836854
A_Umin , A_Umax, A_ΔŨmin, A_ΔŨmax , A_Ymin, A_Ymax,
@@ -998,34 +1016,32 @@ init_stochpred(::StateEstimator, _ ) = zeros(0, 0), zeros(0, 0)
9981016

9991017

10001018
@doc raw"""
1001-
init_linconstraint(model::LinModel,
1019+
init_linconstraint(model::LinModel,
1020+
i_Umin, i_Umax, i_ΔŨmin, i_ΔŨmax, i_Ymin, i_Ymax,
10021021
A_Umin, A_Umax, A_ΔŨmin, A_ΔŨmax, A_Ymin, A_Ymax,
1003-
i_Umin, i_Umax, i_ΔŨmin, i_ΔŨmax, i_Ymin, i_Ymax
1004-
) -> A, i_b, b
1022+
) -> i_b, A
10051023
1006-
Init `A`, `b` and `i_b` for the linear inequality constraints (``\mathbf{A ΔŨ ≤ b}``).
1024+
Init `i_b` and `A` for the linear inequality constraints (``\mathbf{A ΔŨ ≤ b}``).
10071025
10081026
`i_b` is a `BitVector` including the indices of ``\mathbf{b}`` that are finite numbers.
10091027
"""
10101028
function init_linconstraint(::LinModel,
1029+
i_Umin, i_Umax, i_ΔŨmin, i_ΔŨmax, i_Ymin, i_Ymax,
10111030
A_Umin, A_Umax, A_ΔŨmin, A_ΔŨmax, A_Ymin, A_Ymax,
1012-
i_Umin, i_Umax, i_ΔŨmin, i_ΔŨmax, i_Ymin, i_Ymax
10131031
)
1014-
A = [A_Umin; A_Umax; A_ΔŨmin; A_ΔŨmax; A_Ymin; A_Ymax]
10151032
i_b = [i_Umin; i_Umax; i_ΔŨmin; i_ΔŨmax; i_Ymin; i_Ymax]
1016-
b = zeros(size(A, 1)) # dummy b vector (updated just before optimization)
1017-
return A, i_b, b
1033+
A = [A_Umin; A_Umax; A_ΔŨmin; A_ΔŨmax; A_Ymin; A_Ymax]
1034+
return i_b, A
10181035
end
10191036

10201037
"Init values without predicted output constraints if `model` is not a [`LinModel`](@ref)."
10211038
function init_linconstraint(::SimModel,
1022-
A_Umin, A_Umax, A_ΔŨmin, A_ΔŨmax, _ , _ ,
1023-
i_Umin, i_Umax, i_ΔŨmin, i_ΔŨmax, _ , _
1039+
i_Umin, i_Umax, i_ΔŨmin, i_ΔŨmax, _ , _ ,
1040+
A_Umin, A_Umax, A_ΔŨmin, A_ΔŨmax, _ , _
10241041
)
1025-
A = [A_Umin; A_Umax; A_ΔŨmin; A_ΔŨmax]
10261042
i_b = [i_Umin; i_Umax; i_ΔŨmin; i_ΔŨmax]
1027-
b = zeros(size(A, 1)) # dummy b vector (updated just before optimization)
1028-
return A, i_b, b
1043+
A = [A_Umin; A_Umax; A_ΔŨmin; A_ΔŨmax]
1044+
return i_b, A
10291045
end
10301046

10311047
"Validate predictive controller weight and horizon specified values."

0 commit comments

Comments
 (0)