Skip to content

Commit 92bb1b0

Browse files
committed
added: input increment constraint conversion
1 parent 6b42b92 commit 92bb1b0

1 file changed

Lines changed: 59 additions & 21 deletions

File tree

ext/LinearMPCext.jl

Lines changed: 59 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
module LinearMPCext
22

3-
using ModelPredictiveControl, LinearMPC
3+
using ModelPredictiveControl
44
using LinearAlgebra, SparseArrays
55
using JuMP
66

7+
import LinearMPC
78
import ModelPredictiveControl: isblockdiag
89

910
function Base.convert(::Type{LinearMPC.MPC}, mpc::ModelPredictiveControl.LinMPC)
@@ -25,16 +26,20 @@ function Base.convert(::Type{LinearMPC.MPC}, mpc::ModelPredictiveControl.LinMPC)
2526
LinearMPC.set_offset!(newmpc; uo=uoff, ho=yoff, doff=doff, xo=xoff, fo=foff)
2627
# --- State observer parameters ---
2728
Q, R = estim.cov.Q̂, estim.cov.
28-
set_state_observer!(newmpc; C=estim.Ĉm, Q, R)
29+
LinearMPC.set_state_observer!(newmpc; C=estim.Ĉm, Q, R)
2930
# --- Objective function weights ---
3031
Q = weights.M_Hp[1:ny, 1:ny]
3132
Qf = weights.M_Hp[end-ny+1:end, end-ny+1:end]
3233
Rr = weights.Ñ_Hc[1:nu, 1:nu]
3334
R = weights.L_Hp[1:nu, 1:nu]
3435
LinearMPC.set_objective!(newmpc; Q, Rr, R, Qf)
35-
if !weights.isinf_C
36+
only_hard = weights.isinf_C
37+
if !only_hard
38+
# LinearMPC relies on a different softening mechanism, so we apply
39+
# an approximate conversion factor on the softening weight:
3640
Cwt = weights.Ñ_Hc[end, end]
37-
newmpc.settings.soft_weight = Cwt
41+
conversion_factor = 0.1 #0.09066
42+
newmpc.settings.soft_weight = conversion_factor*Cwt
3843
end
3944
# --- Custom move blocking ---
4045
LinearMPC.move_block!(newmpc, mpc.nb) # un-comment when debugged
@@ -59,9 +64,26 @@ function Base.convert(::Type{LinearMPC.MPC}, mpc::ModelPredictiveControl.LinMPC)
5964
for i in 1:nu
6065
lb = isfinite(umin_k[i]) ? [umin_k[i]] : zeros(0)
6166
ub = isfinite(umax_k[i]) ? [umax_k[i]] : zeros(0)
62-
soft = (c_u_k[i] > 0)
67+
soft = !only_hard && c_u_k[i] > 0
6368
Au = I_u[i:i, :]
64-
add_constraint!(newmpc; Au, lb, ub, ks, soft)
69+
LinearMPC.add_constraint!(newmpc; Au, lb, ub, ks, soft)
70+
end
71+
end
72+
# --- Input increment constraints ---
73+
nΔU = Hc * nu
74+
ΔUmin, ΔUmax = mpc.con.ΔŨmin[1:nΔU], mpc.con.ΔŨmax[1:nΔU]
75+
C_Δu = -mpc.con.A_ΔŨmin[1:nΔU, end]
76+
I_Δu = Matrix{Float64}(I, nu, nu)
77+
for k in 0:Hc-1
78+
Δumin_k, Δumax_k = ΔUmin[k*nu+1:(k+1)*nu], ΔUmax[k*nu+1:(k+1)*nu]
79+
c_Δu_k = C_Δu[k*nu+1:(k+1)*nu]
80+
ks = [k + 1] # a `1` in ks argument corresponds to the present time step k+0
81+
for i in 1:nu
82+
lb = isfinite(Δumin_k[i]) ? [Δumin_k[i]] : zeros(0)
83+
ub = isfinite(Δumax_k[i]) ? [Δumax_k[i]] : zeros(0)
84+
soft = !only_hard && c_Δu_k[i] > 0
85+
Au, Aup = I_Δu[i:i, :], -I_Δu[i:i, :]
86+
LinearMPC.add_constraint!(newmpc; Au, Aup, lb, ub, ks, soft)
6587
end
6688
end
6789
# --- Output constraints ---
@@ -74,9 +96,9 @@ function Base.convert(::Type{LinearMPC.MPC}, mpc::ModelPredictiveControl.LinMPC)
7496
for i in 1:ny
7597
lb = isfinite(ymin_k[i]) ? [ymin_k[i]] : zeros(0)
7698
ub = isfinite(ymax_k[i]) ? [ymax_k[i]] : zeros(0)
77-
soft = (c_y_k[i] > 0)
99+
soft = !only_hard && c_y_k[i] > 0
78100
Ax, Ad = C[i:i, :], Dd[i:i, :]
79-
add_constraint!(newmpc; Ax, Ad, lb, ub, ks, soft)
101+
LinearMPC.add_constraint!(newmpc; Ax, Ad, lb, ub, ks, soft)
80102
end
81103
end
82104
# --- Terminal constraints ---
@@ -87,9 +109,9 @@ function Base.convert(::Type{LinearMPC.MPC}, mpc::ModelPredictiveControl.LinMPC)
87109
for i in 1:nx̂
88110
lb = isfinite(x̂0min[i]) ? [x̂0min[i]] : zeros(0)
89111
ub = isfinite(x̂0max[i]) ? [x̂0max[i]] : zeros(0)
90-
soft = (c_x̂[i] > 0)
112+
soft = !only_hard && c_x̂[i] > 0
91113
Ax = I_x̂[i:i, :]
92-
add_constraint!(newmpc; Ax, lb, ub, ks, soft)
114+
LinearMPC.add_constraint!(newmpc; Ax, lb, ub, ks, soft)
93115
end
94116
return newmpc
95117
end
@@ -142,20 +164,36 @@ function validate_weights(mpc::ModelPredictiveControl.LinMPC)
142164
end
143165

144166
function validate_constraints(mpc::ModelPredictiveControl.LinMPC)
145-
ΔŨmin, ΔŨmax = mpc.con.ΔŨmin, mpc.con.ΔŨmax
146-
C_umin, C_umax = -mpc.con.A_Umin[:, end], -mpc.con.A_Umax[:, end]
147-
C_ymin, C_ymax = -mpc.con.A_Ymin[:, end], -mpc.con.A_Ymax[:, end]
148-
C_x̂min, C_x̂max = -mpc.con.A_x̂min[:, end], -mpc.con.A_x̂max[:, end]
167+
nΔU = mpc.Hc * mpc.estim.model.nu
168+
C_umin, C_umax = -mpc.con.A_Umin[:, end], -mpc.con.A_Umax[:, end]
169+
C_Δumin, C_Δumax = -mpc.con.A_ΔŨmin[1:nΔU, end], -mpc.con.A_ΔŨmax[1:nΔU, end]
170+
C_ymin, C_ymax = -mpc.con.A_Ymin[:, end], -mpc.con.A_Ymax[:, end]
171+
C_x̂min, C_x̂max = -mpc.con.A_x̂min[:, end], -mpc.con.A_x̂max[:, end]
149172
is0or1(C) = all(x -> x 0 || x 1, C)
150-
if !is0or1(C_umin) || !is0or1(C_umax) || !is0or1(C_ymin) || !is0or1(C_ymax)
173+
if (
174+
!is0or1(C_umin) || !is0or1(C_umax) ||
175+
!is0or1(C_Δumin) || !is0or1(C_Δumax) ||
176+
!is0or1(C_ymin) || !is0or1(C_ymax) ||
177+
!is0or1(C_x̂min) || !is0or1(C_x̂max)
178+
179+
)
151180
error("LinearMPC only supports softness parameters c = 0 or 1.")
152181
end
153-
if !isapprox(C_umin, C_umax) || !isapprox(C_ymin, C_ymax) || !isapprox(C_x̂min, C_x̂max)
182+
if (
183+
!isapprox(C_umin, C_umax) ||
184+
!isapprox(C_Δumin, C_Δumax) ||
185+
!isapprox(C_ymin, C_ymax) ||
186+
!isapprox(C_x̂min, C_x̂max)
187+
)
154188
error("LinearMPC only supports identical softness parameters for lower and upper bounds.")
155189
end
156-
nΔU = mpc.Hc * mpc.estim.model.nu
157-
if any(isfinite, ΔŨmin[1:nΔU]) || any(isfinite, ΔŨmax[1:nΔU])
158-
error("LinearMPC does not support constraints on input increments Δu")
190+
issoft(C) = any(x -> x > 0, C)
191+
if !mpc.weights.isinf_C && sum(mpc.con.i_b) > 1 # ignore the slack variable ϵ bound
192+
if issoft(C_umin) || issoft(C_Δumin) || issoft(C_ymin) || issoft(C_x̂min)
193+
@warn "The LinearMPC conversion applies an approximate conversion " *
194+
"of the soft constraints.\n You may need to adjust the soft_weight "*
195+
"field of the LinearMPC.MPC object to replicate behaviors."
196+
end
159197
end
160198
return nothing
161199
end
@@ -174,8 +212,8 @@ are supported, including these restrictions:
174212
- the transcription method must be [`SingleShooting`](@ref).
175213
- the state estimator must be a [`SteadyKalmanFilter`](@ref) with `direct=true`.
176214
- only block-diagonal weights are allowed.
177-
- input increment constraints ``\mathbf{Δu_{min}}`` and ``\mathbf{Δu_{max}}`` are not
178-
supported for now.
215+
- the constraint relaxation mechanism is different, so a 1-on-1 conversion of the soft
216+
constraints is impossible (use `Cwt=Inf` to disable relaxation).
179217
180218
But the package has also several exclusive functionalities, such as pre-stabilization,
181219
constrained explicit MPC, and binary manipulated inputs. See the [`LinearMPC.jl`](@extref LinearMPC)

0 commit comments

Comments
 (0)