@@ -108,16 +108,19 @@ As the motor torque is limited to -1.5 to 1.5 N m, we incorporate the input cons
108108a [ ` NonLinMPC ` ] ( @ref ) :
109109
110110``` @example 1
111- nmpc = NonLinMPC(estim, Hp=20, Hc=2, Mwt=[0.5], Nwt=[2.5], Cwt=Inf)
112- nmpc = setconstraint!(nmpc, umin=[-1.5], umax=[+1.5])
111+ Hp, Hc, Mwt, Nwt = 20, 2, [0.5], [2.5]
112+ nmpc = NonLinMPC(estim; Hp, Hc, Mwt, Nwt, Cwt=Inf)
113+ umin, umax = [-1.5], [+1.5]
114+ nmpc = setconstraint!(nmpc; umin, umax)
113115```
114116
115- The option ` Cwt=Inf ` disables the slack variable ` ϵ ` for constraint softening. We test ` mpc ` performance on ` plant ` by imposing an angular setpoint of 180° (inverted position):
117+ The option ` Cwt=Inf ` disables the slack variable ` ϵ ` for constraint softening. We test ` mpc `
118+ performance on ` plant ` by imposing an angular setpoint of 180° (inverted position):
116119
117120``` @example 1
118121using Logging; disable_logging(Warn) # hide
119122using JuMP; unset_time_limit_sec(nmpc.optim) # hide
120- res_ry = sim!(nmpc, N, [180.0], plant=plant, x0 =[0, 0], x̂0 =[0, 0, 0])
123+ res_ry = sim!(nmpc, N, [180.0], plant=plant, x_0 =[0, 0], x̂_0 =[0, 0, 0])
121124plot(res_ry)
122125savefig(ans, "plot3_NonLinMPC.svg"); nothing # hide
123126```
@@ -129,7 +132,7 @@ inverted position, the closed-loop response to a step disturbances of 10° is al
129132satisfactory:
130133
131134``` @example 1
132- res_yd = sim!(nmpc, N, [180.0], plant=plant, x0 =[π, 0], x̂0 =[π, 0, 0], y_step=[10])
135+ res_yd = sim!(nmpc, N, [180.0], plant=plant, x_0 =[π, 0], x̂_0 =[π, 0, 0], y_step=[10])
133136plot(res_yd)
134137savefig(ans, "plot4_NonLinMPC.svg"); nothing # hide
135138```
@@ -184,8 +187,8 @@ function JE(UE, ŶE, _ )
184187 τ, ω = UE[1:end-1], ŶE[2:2:end-1]
185188 return Ts*sum(τ.*ω)
186189end
187- empc = NonLinMPC(estim2, Hp=20 , Hc=2, Mwt=[0.5, 0], Nwt=[2.5 ], Cwt=Inf, Ewt=3.5e3, JE=JE)
188- empc = setconstraint!(empc, umin=[-1.5] , umax=[+1.5] )
190+ empc = NonLinMPC(estim2; Hp, Hc, Nwt, Mwt=[0.5, 0], Cwt=Inf, Ewt=3.5e3, JE=JE)
191+ empc = setconstraint!(empc; umin, umax)
189192```
190193
191194The keyword argument ` Ewt ` weights the economic costs relative to the other terms in the
@@ -196,7 +199,7 @@ setpoint is similar:
196199
197200``` @example 1
198201unset_time_limit_sec(empc.optim) # hide
199- res2_ry = sim!(empc, N, [180, 0], plant=plant2, x0 =[0, 0], x̂0 =[0, 0, 0])
202+ res2_ry = sim!(empc, N, [180, 0], plant=plant2, x_0 =[0, 0], x̂_0 =[0, 0, 0])
200203plot(res2_ry)
201204savefig(ans, "plot5_NonLinMPC.svg"); nothing # hide
202205```
@@ -216,7 +219,7 @@ Dict(:W_nmpc => calcW(res_ry), :W_empc => calcW(res2_ry))
216219Also, for a 10° step disturbance:
217220
218221``` @example 1
219- res2_yd = sim!(empc, N, [180; 0]; plant=plant2, x0 =[π, 0], x̂0 =[π, 0, 0], y_step=[10, 0])
222+ res2_yd = sim!(empc, N, [180; 0]; plant=plant2, x_0 =[π, 0], x̂_0 =[π, 0, 0], y_step=[10, 0])
220223plot(res2_yd)
221224savefig(ans, "plot6_NonLinMPC.svg"); nothing # hide
222225```
@@ -232,7 +235,7 @@ Dict(:W_nmpc => calcW(res_yd), :W_empc => calcW(res2_yd))
232235Of course, this gain is only exploitable if the motor electronic includes some kind of
233236regenerative circuitry.
234237
235- ## Linearizing the Model
238+ ## Model Linearization
236239
237240Nonlinear MPC is more computationally expensive than [ ` LinMPC ` ] ( @ref ) . Solving the problem
238241should always be faster than the sampling time `` T_s = 0.1 `` s for real-time operation. This
@@ -248,15 +251,15 @@ linmodel = linearize(model, x=[π, 0], u=[0])
248251A [ ` SteadyKalmanFilter ` ] ( @ref ) and a [ ` LinMPC ` ] ( @ref ) are designed from ` linmodel ` :
249252
250253``` @example 1
251- kf = SteadyKalmanFilter(linmodel; σQ, σR, nint_u, σQint_u)
252- mpc = LinMPC(kf, Hp=20 , Hc=2 , Mwt=[0.5] , Nwt=[2.5] , Cwt=Inf)
254+ skf = SteadyKalmanFilter(linmodel; σQ, σR, nint_u, σQint_u)
255+ mpc = LinMPC(skf; Hp, Hc, Mwt, Nwt, Cwt=Inf)
253256mpc = setconstraint!(mpc, umin=[-1.5], umax=[+1.5])
254257```
255258
256259The linear controller has difficulties to reject the 10° step disturbance:
257260
258261``` @example 1
259- res_lin = sim!(mpc, N, [180.0]; plant, x0 =[π, 0], y_step=[10])
262+ res_lin = sim!(mpc, N, [180.0]; plant, x_0 =[π, 0], y_step=[10])
260263plot(res_lin)
261264savefig(ans, "plot7_NonLinMPC.svg"); nothing # hide
262265```
@@ -287,22 +290,98 @@ Constructing a [`LinMPC`](@ref) with `DAQP`:
287290``` @example 1
288291using JuMP, DAQP
289292daqp = Model(DAQP.Optimizer, add_bridges=false)
290- mpc2 = LinMPC(kf, Hp=20 , Hc=2 , Mwt=[0.5] , Nwt=[2.5] , Cwt=Inf, optim=daqp)
291- mpc2 = setconstraint!(mpc2, umin=[-1.5] , umax=[+1.5] )
293+ mpc2 = LinMPC(skf; Hp, Hc, Mwt, Nwt, Cwt=Inf, optim=daqp)
294+ mpc2 = setconstraint!(mpc2; umin, umax)
292295```
293296
294297does improve the rejection of the step disturbance:
295298
296299``` @example 1
297- res_lin2 = sim!(mpc2, N, [180.0]; plant, x0 =[π, 0], y_step=[10])
300+ res_lin2 = sim!(mpc2, N, [180.0]; plant, x_0 =[π, 0], y_step=[10])
298301plot(res_lin2)
299302savefig(ans, "plot8_NonLinMPC.svg"); nothing # hide
300303```
301304
302305![ plot8_NonLinMPC] ( plot8_NonLinMPC.svg )
303306
304307The closed-loop performance is still lower than the nonlinear controller, as expected, but
305- computations are about 2000 times faster (0.00002 s versus 0.04 s per time steps, on
306- average). Note that ` linmodel ` is only valid for angular positions near 180°. Multiple
307- linearized models and controllers are required for large deviations from this operating
308- point. This is known as gain scheduling.
308+ computations are about 210 times faster (0.000071 s versus 0.015 s per time steps, on
309+ average). However, remember that ` linmodel ` is only valid for angular positions near 180°.
310+ For example, the 180° setpoint response from 0° is unsatisfactory since the predictions are
311+ poor in the first quadrant:
312+
313+ ``` @example 1
314+ res_lin3 = sim!(mpc2, N, [180.0]; plant, x_0=[0, 0])
315+ plot(res_lin3)
316+ savefig(ans, "plot9_NonLinMPC.svg"); nothing # hide
317+ ```
318+
319+ ![ plot9_NonLinMPC] ( plot9_NonLinMPC.svg )
320+
321+ Multiple linearized model and controller objects are required for large deviations from this
322+ operating point. This is known as gain scheduling. Another approach is adapting the model of
323+ the [ ` LinMPC ` ] ( @ref ) instance based on repeated online linearization.
324+
325+ ## Adapting the Model via Successive Linearization
326+
327+ The [ ` setmodel! ` ] ( @ref ) method allows online adaptation of a linear plant model. Combined
328+ with the automatic linearization of [ ` linearize ` ] ( @ref ) , a successive linearization MPC can
329+ be designed with minimal efforts. The [ ` SteadyKalmanFilter ` ] ( @ref ) does not support
330+ [ ` setmodel! ` ] ( @ref ) , so we need to use the time-varying [ ` KalmanFilter ` ] ( @ref ) instead:
331+
332+ ``` @example 1
333+ kf = KalmanFilter(linmodel; σQ, σR, nint_u, σQint_u)
334+ mpc3 = LinMPC(kf; Hp, Hc, Mwt, Nwt, Cwt=Inf, optim=daqp)
335+ mpc3 = setconstraint!(mpc3; umin, umax)
336+ ```
337+
338+ We create a function that simulates the plant and the adaptive controller:
339+
340+ ``` @example 1
341+ function test_slmpc(nonlinmodel, mpc, ry, plant; x_0=plant.xop, y_step=0)
342+ N = 35
343+ U_data, Y_data, Ry_data = zeros(plant.nu, N), zeros(plant.ny, N), zeros(plant.ny, N)
344+ setstate!(plant, x_0)
345+ u, y = [0.0], plant()
346+ x̂ = initstate!(mpc, u, y)
347+ for i = 1:N
348+ y = plant() .+ y_step
349+ u = moveinput!(mpc, ry)
350+ linmodel = linearize(nonlinmodel; u, x=x̂[1:2])
351+ setmodel!(mpc, linmodel)
352+ U_data[:,i], Y_data[:,i], Ry_data[:,i] = u, y, ry
353+ x̂ = updatestate!(mpc, u, y) # update mpc state estimate
354+ updatestate!(plant, u) # update plant simulator
355+ end
356+ res = SimResult(mpc, U_data, Y_data; Ry_data)
357+ return res
358+ end
359+ nothing # hide
360+ ```
361+
362+ The [ ` setmodel! ` ] ( @ref ) method must be called after solving the optimization problem with
363+ [ ` moveinput! ` ] ( @ref ) , and before updating the state estimate with [ ` updatestate! ` ] ( @ref ) .
364+ The [ ` SimResult ` ] ( @ref ) object is for plotting purposes only. The adaptive [ ` LinMPC ` ] ( @ref )
365+ performances are similar to the nonlinear MPC, both for the 180° setpoint:
366+
367+ ``` @example 1
368+ res_slin = test_slmpc(model, mpc3, [180], plant, x_0=[0, 0])
369+ plot(res_slin)
370+ savefig(ans, "plot10_NonLinMPC.svg"); nothing # hide
371+ ```
372+
373+ ![ plot10_NonLinMPC] ( plot10_NonLinMPC.svg )
374+
375+ and the 10° step disturbance:
376+
377+ ``` @example 1
378+ res_slin = test_slmpc(model, mpc3, [180], plant, x_0=[π, 0], y_step=[10])
379+ plot(res_slin)
380+ savefig(ans, "plot11_NonLinMPC.svg"); nothing # hide
381+ ```
382+
383+ ![ plot11_NonLinMPC] ( plot11_NonLinMPC.svg )
384+
385+ The computations of the successive linearization MPC are about 125 times faster than the
386+ nonlinear MPC (0.00012 s per time steps versus 0.015 s per time steps, on average), an
387+ impressive gain for similar closed-loop performances!
0 commit comments