Skip to content

Commit 71a9849

Browse files
committed
doc : added feedforward example to manual
1 parent 88e870d commit 71a9849

File tree

1 file changed

+84
-14
lines changed

1 file changed

+84
-14
lines changed

docs/src/manual/linmpc.md

Lines changed: 84 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ At the steady-state operating points:
1818

1919
```math
2020
\begin{aligned}
21-
\mathbf{u_{op}} &= \begin{bmatrix} 10 \\ 10 \end{bmatrix} \\
21+
\mathbf{u_{op}} &= \begin{bmatrix} 20 \\ 20 \end{bmatrix} \\
2222
\mathbf{y_{op}} &= \begin{bmatrix} 50 \\ 30 \end{bmatrix}
2323
\end{aligned}
2424
```
@@ -46,7 +46,7 @@ using ModelPredictiveControl, ControlSystemsBase
4646
sys = [ tf(1.90, [18, 1]) tf(1.90, [18, 1]);
4747
tf(-0.74,[8, 1]) tf(0.74, [8, 1]) ]
4848
Ts = 2.0
49-
model = setop!(LinModel(sys, Ts), uop=[10, 10], yop=[50, 30])
49+
model = setop!(LinModel(sys, Ts), uop=[20, 20], yop=[50, 30])
5050
```
5151

5252
The `model` object will be used for two purposes : to construct our controller, and as a
@@ -66,7 +66,8 @@ We design our [`LinMPC`](@ref) controllers by including the linear level constra
6666
[`setconstraint!`](@ref) (`±Inf` values should be used when there is no bound):
6767

6868
```@example 1
69-
mpc = setconstraint!(LinMPC(model, Hp=15, Hc=2, Mwt=[1, 1], Nwt=[0.1, 0.1]), ymin=[45, -Inf])
69+
mpc = LinMPC(model, Hp=15, Hc=2, Mwt=[1, 1], Nwt=[0.1, 0.1])
70+
mpc = setconstraint!(mpc, ymin=[45, -Inf])
7071
```
7172

7273
in which `Hp` and `Hc` keyword arguments are respectively the predictive and control
@@ -85,33 +86,32 @@ output will initialize the states. [`LinModel`](@ref) objects are callable for t
8586
(an alias for [`evaloutput`](@ref)):
8687

8788
```@example 1
88-
u = model.uop
89-
y = model() # or equivalently : y = evaloutput(model)
89+
u, y = model.uop, model() # or equivalently : y = evaloutput(model)
9090
initstate!(mpc, u, y)
9191
nothing # hide
9292
```
9393

9494
We can then close the loop and test `mpc` performance on the simulator by imposing step
95-
changes on output setpoints ``\mathbf{r_y}`` and on a load disturbance ``\mathbf{u_d}``:
95+
changes on output setpoints ``\mathbf{r_y}`` and on a load disturbance ``u_l``:
9696

9797
```@example 1
9898
function test_mpc(mpc, model)
9999
N = 200
100-
ry, ud = [50, 30], [0, 0]
100+
ry, ul = [50, 30], 0
101101
u_data = zeros(model.nu, N)
102102
y_data = zeros(model.ny, N)
103103
ry_data = zeros(model.ny, N)
104104
for k = 0:N-1
105-
y = model() # simulated measurements
106105
k == 50 && (ry = [50, 35])
107106
k == 100 && (ry = [54, 30])
108-
k == 150 && (ud = [0, -20])
107+
k == 150 && (ul = -20)
108+
y = model() # simulated measurements
109109
u = mpc(ry) # or equivalently : u = moveinput!(mpc, ry)
110110
u_data[:,k+1] = u
111111
y_data[:,k+1] = y
112112
ry_data[:,k+1] = ry
113113
updatestate!(mpc, u, y) # update mpc state estimate
114-
updatestate!(model, u + ud) # update simulator with disturbance
114+
updatestate!(model, u + [0; ul]) # update simulator with the load disturbance
115115
end
116116
return u_data, y_data, ry_data
117117
end
@@ -158,15 +158,85 @@ real-life control problems. Constructing a [`LinMPC`](@ref) with `DAQP` and inpu
158158
using JuMP, DAQP
159159
daqp = Model(DAQP.Optimizer)
160160
estim = SteadyKalmanFilter(model, nint_u=[1, 1], nint_ym=[0, 0])
161-
mpc2 = setconstraint!(LinMPC(estim, Hp=15, Hc=2, optim=daqp), ymin=[45, -Inf])
161+
mpc2 = LinMPC(estim, Hp=15, Hc=2, Mwt=[1, 1], Nwt=[0.1, 0.1], optim=daqp)
162+
mpc2 = setconstraint!(mpc2, ymin=[45, -Inf])
162163
```
163164

164165
leads to similar computational times, but it does accelerate the rejection of the load
165166
disturbance and eliminates the level constraint violation:
166167

167168
```@example 1
168169
setstate!(model, zeros(model.nx))
169-
initstate!(mpc2, model.uop, model())
170-
u_data2, y_data2, ry_data2 = test_mpc(mpc2, model)
171-
plot_data(t_data, u_data2, y_data2, ry_data2)
170+
u, y = model.uop, model()
171+
initstate!(mpc2, u, y)
172+
u_data, y_data, ry_data = test_mpc(mpc2, model)
173+
plot_data(t_data, u_data, y_data, ry_data)
174+
```
175+
176+
## Adding Feedforward Compensation
177+
178+
Suppose that the load disturbance ``u_l`` of the last section is in fact caused by a
179+
separate hot water pipe that discharges into the tank. Measuring this flow rate allows us to
180+
incorporate feedforward compensation in the controller. The new plant model is:
181+
182+
```math
183+
\begin{bmatrix}
184+
y_L(s) \\ y_T(s)
185+
\end{bmatrix} =
186+
\begin{bmatrix}
187+
\frac{1.90}{18s + 1} & \frac{1.90}{18s + 1} & \frac{1.90}{18s + 1} \\[3pt]
188+
\frac{-0.74}{8s + 1} & \frac{0.74}{8s + 1} & \frac{0.74}{8s + 1}
189+
\end{bmatrix}
190+
\begin{bmatrix}
191+
u_c(s) \\ u_h(s) \\ u_l(s)
192+
\end{bmatrix}
193+
```
194+
195+
We construct a new [`LinModel`](@ref) that includes the measured disturbance
196+
``\mathbf{d} = [u_l]`` and the operating point ``\mathbf{d_{op}} = [10]``:
197+
198+
```@example 1
199+
sys_ff = [sys sys[1:2, 2]]
200+
model_ff = setop!(LinModel(sys_ff, Ts, i_d=[3]), uop=[10, 10], yop=[50, 30], dop=[10])
201+
mpc_ff = LinMPC(model_ff, Hp=15, Hc=2, Mwt=[1, 1], Nwt=[0.1, 0.1])
202+
mpc_ff = setconstraint!(mpc_ff, ymin=[45, -Inf])
203+
```
204+
205+
Also, a new test function that feeds the measured disturbance ``\mathbf{d}`` to the
206+
controller is defined:
207+
208+
```@example 1
209+
function test_mpc_ff(mpc_ff, model)
210+
N = 200
211+
ry, ul = [50, 30], 0
212+
dop = mpc_ff.estim.model.dop
213+
u_data = zeros(model.nu, N)
214+
y_data = zeros(model.ny, N)
215+
ry_data = zeros(model.ny, N)
216+
for k = 0:N-1
217+
k == 50 && (ry = [50, 35])
218+
k == 100 && (ry = [54, 30])
219+
k == 150 && (ul = -20)
220+
d = ul .+ dop # simulated measured disturbance
221+
y = model() # simulated measurements
222+
u = mpc_ff(ry, d) # also feed the measured disturbance d to the controller
223+
u_data[:,k+1] = u
224+
y_data[:,k+1] = y
225+
ry_data[:,k+1] = ry
226+
updatestate!(mpc_ff, u, y, d) # update estimate with the measured disturbance d
227+
updatestate!(model, u + [0; ul]) # update simulator
228+
end
229+
return u_data, y_data, ry_data
230+
end
231+
nothing # hide
232+
```
233+
234+
The new feedforward compensation is able to almost perfectly rejet the load disturbance:
235+
236+
```@example 1
237+
setstate!(model, zeros(model.nx))
238+
u, y, d = model.uop, model(), mpc_ff.estim.model.dop
239+
initstate!(mpc_ff, u, y, d)
240+
u_data, y_data, ry_data = test_mpc_ff(mpc_ff, model)
241+
plot_data(t_data, u_data, y_data, ry_data)
172242
```

0 commit comments

Comments
 (0)