22
33## Linear Model
44
5- The example considers a well-stirred tank with a cold and hot water inlet as a plant. The
6- water flows out of an opening at the bottom of the tank. The manipulated inputs are the cold
7- `` u_c `` and hot `` u_h `` water flow rate , and the measured outputs are the liquid level
8- `` y_L `` and temperature `` y_T `` :
5+ The example considers a continuously stirred- tank reactor (CSTR) with a cold and hot water
6+ inlet as a plant. The water flows out of an opening at the bottom of the tank. The
7+ manipulated inputs are the cold `` u_c `` and hot `` u_h `` water flow rates , and the measured
8+ outputs are the liquid level `` y_L `` and temperature `` y_T `` :
99
1010``` math
1111\begin{aligned}
@@ -41,41 +41,49 @@ the following linear model accurately describes the plant dynamics:
4141We first need to construct a [ ` LinModel ` ] ( @ref ) objet with [ ` setop! ` ] ( @ref ) to handle the
4242operating points:
4343
44- ``` @example 1
44+ ``` julia
4545using ModelPredictiveControl, ControlSystemsBase
4646sys = [ tf (1.90 , [18 , 1 ]) tf (1.90 , [18 , 1 ]);
4747 tf (- 0.74 ,[8 , 1 ]) tf (0.74 , [8 , 1 ]) ]
48- Ts = 4 .0
48+ Ts = 2 .0
4949model = setop! (LinModel (sys, Ts), uop= [10 , 10 ], yop= [50 , 30 ])
5050```
5151
5252The ` model ` object will be used for two purposes : to construct our controller, and as a
5353plant simulator to test the design.
5454
55- ## Linear Predictive Controller
55+ ## Linear Model Predictive Controller
5656
57- A predictive feedback will control both the water level `` y_L `` and temperature `` y_T `` in
58- the tank, at a sampling time of 4 s. The tank level should also never fall below 45:
57+ A linear model predictive controller (MPC) will control both the water level `` y_L `` and
58+ temperature `` y_T `` in the tank, at a sampling time of 4 s. The tank level should also never
59+ fall below 45:
5960
6061``` math
6162y_L ≥ 45
6263```
6364
64- We design our [ ` LinMPC ` ] ( @ref ) controllers by including the level constraint with
65- [ ` setconstraint! ` ] ( @ref ) :
65+ We design our [ ` LinMPC ` ] ( @ref ) controllers by including the linear level constraint with
66+ [ ` setconstraint! ` ] ( @ref ) ( ` ±Inf ` values should be used when there is no bound) :
6667
67- ``` @example 1
68+ ``` julia
6869mpc = setconstraint! (LinMPC (model, Hp= 15 , Hc= 2 ), ŷmin= [45 , - Inf ])
6970```
7071
71- By default, [ ` LinMPC ` ] ( @ref ) controllers use [ ` OSQP ` ] ( https://osqp.org/ ) to solve the
72- problem and a [ ` SteadyKalmanFilter ` ] ( @ref ) to estimate the plant states. Before closing the
73- loop, we call [ ` initstate! ` ] ( @ref ) with the actual plant inputs and measurements to ensure a
74- bumpless transfer. Since ` model ` simulates our plant here, its output will initialize the
75- states. [ ` LinModel ` ] ( @ref ) objects are callable for this purpose (an alias for
76- [ ` evaloutput ` ] ( @ref ) ):
77-
78- ``` @example 1
72+ in which ` Hp ` and ` Hc ` keyword arguments are the predictive and control horizons
73+ respectively. By default, [ ` LinMPC ` ] ( @ref ) controllers use [ ` OSQP ` ] ( https://osqp.org/ ) to
74+ solve the problem, soft constraints on output predictions `` \mathbf{ŷ} `` to ensure
75+ feasibility, and a [ ` SteadyKalmanFilter ` ] ( @ref ) to estimate the plant states. An attentive
76+ reader will also notice that the Kalman filter estimates two additional states compared to
77+ the plant model. These are the integrating states for the unmeasured plant disturbances, and
78+ they are automatically added the model outputs if feasible (see [ ` SteadyKalmanFilter ` ] ( @ref )
79+ for details).
80+
81+ Before closing the loop, we call [ ` initstate! ` ] ( @ref ) with the actual plant inputs and
82+ measurements to ensure a bumpless transfer. Since ` model ` simulates our plant here, its
83+ output will initialize the states. [ ` LinModel ` ] ( @ref ) objects are callable for this purpose
84+ (an alias for [ ` evaloutput ` ] ( @ref ) ):
85+
86+ ``` julia
7987u = model. uop
8088y = model () # or equivalently : y = evaloutput(model)
8189initstate! (mpc, u, y)
@@ -85,24 +93,25 @@ nothing # hide
8593We can then close the loop and test ` mpc ` performance on the simulator by imposing step
8694changes on output setpoints `` \mathbf{r_y} `` and on a load disturbance `` \mathbf{u_d} `` :
8795
88- ``` @example 1
96+ ``` julia
8997function test_mpc (mpc, model)
90- N = 100
98+ N = 200
9199 ry, ud = [50 , 30 ], [0 , 0 ]
92100 u_data = zeros (model. nu, N)
93101 y_data = zeros (model. ny, N)
94102 ry_data = zeros (model. ny, N)
95103 for k = 0 : N- 1
96104 y = model () # simulated measurements
97- k == 25 && (ry = [50, 35])
98- k == 50 && (ry = [55, 30])
99- k == 75 && (ud = [-15 , 0])
105+ k == 50 && (ry = [50 , 35 ])
106+ k == 100 && (ry = [55 , 30 ])
107+ k == 150 && (ud = [- 25 , 0 ])
100108 u = mpc (ry) # or equivalently : u = moveinput!(mpc, ry)
101109 u_data[:,k+ 1 ] = u
102110 y_data[:,k+ 1 ] = y
103111 ry_data[:,k+ 1 ] = ry
104112 updatestate! (mpc, u, y) # update mpc state estimate
105113 updatestate! (model, u + ud) # update simulator with disturbance
114+ println (getinfo (mpc)[2 ][:Ŷs ])
106115 end
107116 return u_data, y_data, ry_data
108117end
@@ -119,13 +128,13 @@ end of the `for` loop. The same logic applies for `model`.
119128
120129Lastly, we plot the closed-loop test with the ` Plots ` package:
121130
122- ``` @example 1
131+ ``` julia
123132using Plots
124133function plot_data (t_data, u_data, y_data, ry_data)
125- p1 = plot(t_data, y_data[1,:], label="level "); ylabel!("level")
134+ p1 = plot (t_data, y_data[1 ,:], label= " meas. " ); ylabel! (" level" )
126135 plot! (t_data, ry_data[1 ,:], label= " setpoint" , linestyle= :dash , linetype= :steppost )
127136 plot! (t_data, fill (45 ,size (t_data)), label= " min" , linestyle= :dot , linewidth= 1.5 )
128- p2 = plot(t_data, y_data[2,:], label="temp ."); ylabel!("temp.")
137+ p2 = plot (t_data, y_data[2 ,:], label= " meas ." ); ylabel! (" temp." )
129138 plot! (t_data, ry_data[2 ,:],label= " setpoint" , linestyle= :dash , linetype= :steppost )
130139 p3 = plot (t_data,u_data[1 ,:],label= " cold" , linetype= :steppost ); ylabel! (" flow rate" )
131140 plot! (t_data,u_data[2 ,:],label= " hot" , linetype= :steppost ); xlabel! (" time (s)" )
@@ -141,17 +150,20 @@ optimizer may be more efficient. To install it, run:
141150using Pkg; Pkg.add("DAQP")
142151```
143152
144- Constructing a [ ` LinMPC ` ] ( @ref ) with ` DAQP ` :
153+ Also, compared to the default setting, adding the integrating states at the model inputs may
154+ improve the closed-loop performance. Load disturbances are indeed very frequent in real-life
155+ control problems. Constructing a [ ` LinMPC ` ] ( @ref ) with ` DAQP ` and input integrators:
145156
146- ``` @example 1
157+ ``` julia
147158using JuMP, DAQP
148- daqp = Model(DAQP.Optimizer)
149- mpc2 = setconstraint!(LinMPC(model, Hp=15, Hc=2, optim=daqp), ŷmin=[45, -Inf])
159+ daqp = Model (DAQP. Optimizer)
160+ estim = SteadyKalmanFilter (model, nint_u= [1 , 1 ])
161+ mpc2 = setconstraint! (LinMPC (estim, Hp= 15 , Hc= 2 , optim= daqp), ŷmin= [45 , - Inf ])
150162```
151163
152164leads to identical results here:
153165
154- ``` @example 1
166+ ``` julia
155167setstate! (model, zeros (model. nx))
156168initstate! (mpc2, model. uop, model ())
157169u_data2, y_data2, ry_data2 = test_mpc (mpc2, model)
0 commit comments