44Pages = ["nonlinmpc.md"]
55```
66
7- ``` @setup 1
8- using Logging; errlogger = ConsoleLogger(stderr, Error);
9- old_logger = global_logger(); global_logger(errlogger);
10- ```
11-
127## Nonlinear Model
138
149In this example, the goal is to control the angular position `` θ `` of a pendulum
@@ -45,7 +40,7 @@ the end of the pendulum in kg, all bundled in the parameter vector ``\mathbf{p}
4540constructor assumes by default that the state function ` f ` is continuous in time, that is,
4641an ordinary differential equation system (like here):
4742
48- ``` @example 1
43+ ``` @example man_nonlin
4944using ModelPredictiveControl
5045function f(x, u, _ , p)
5146 g, L, K, m = p # [m/s²], [m], [kg/s], [kg]
@@ -69,7 +64,7 @@ vector of you want to modify it later. A 4th order [`RungeKutta`](@ref) method s
6964differential equations by default. It is good practice to first simulate ` model ` using
7065[ ` sim! ` ] ( @ref ) as a quick sanity check:
7166
72- ``` @example 1
67+ ``` @example man_nonlin
7368using Plots
7469u = [0.5]
7570N = 35
@@ -88,7 +83,7 @@ method.
8883
8984An [ ` UnscentedKalmanFilter ` ] ( @ref ) estimates the plant state :
9085
91- ``` @example 1
86+ ``` @example man_nonlin
9287α=0.01; σQ=[0.1, 1.0]; σR=[5.0]; nint_u=[1]; σQint_u=[0.1]
9388estim = UnscentedKalmanFilter(model; α, σQ, σR, nint_u, σQint_u)
9489```
@@ -100,7 +95,7 @@ Also, the argument `nint_u` explicitly adds one integrating state at the model i
10095motor torque `` τ `` , with an associated standard deviation ` σQint_u ` of 0.1 N m. The
10196estimator tuning is tested on a plant with a 25 % larger friction coefficient `` K `` :
10297
103- ``` @example 1
98+ ``` @example man_nonlin
10499p_plant = copy(p_model)
105100p_plant[3] = 1.25*p_model[3]
106101plant = setname!(NonLinModel(f, h, Ts, nu, nx, ny; p=p_plant); u=vu, x=vx, y=vy)
@@ -117,7 +112,7 @@ static errors. The Kalman filter performance seems sufficient for control.
117112As the motor torque is limited to -1.5 to 1.5 N m, we incorporate the input constraints in
118113a [ ` NonLinMPC ` ] ( @ref ) :
119114
120- ``` @example 1
115+ ``` @example man_nonlin
121116Hp, Hc, Mwt, Nwt = 20, 2, [0.5], [2.5]
122117nmpc = NonLinMPC(estim; Hp, Hc, Mwt, Nwt, Cwt=Inf)
123118umin, umax = [-1.5], [+1.5]
@@ -127,7 +122,7 @@ nmpc = setconstraint!(nmpc; umin, umax)
127122The option ` Cwt=Inf ` disables the slack variable ` ϵ ` for constraint softening. We test ` mpc `
128123performance on ` plant ` by imposing an angular setpoint of 180° (inverted position):
129124
130- ``` @example 1
125+ ``` @example man_nonlin
131126using JuMP; unset_time_limit_sec(nmpc.optim) # hide
132127res_ry = sim!(nmpc, N, [180.0], plant=plant, x_0=[0, 0], x̂_0=[0, 0, 0])
133128plot(res_ry)
@@ -140,7 +135,7 @@ The controller seems robust enough to variations on ``K`` coefficient. Starting
140135inverted position, the closed-loop response to a step disturbances of 10° is also
141136satisfactory:
142137
143- ``` @example 1
138+ ``` @example man_nonlin
144139res_yd = sim!(nmpc, N, [180.0], plant=plant, x_0=[π, 0], x̂_0=[π, 0, 0], y_step=[10])
145140plot(res_yd)
146141savefig("plot4_NonLinMPC.svg"); nothing # hide
@@ -181,7 +176,7 @@ angle ``θ`` is measured here). As the arguments of [`NonLinMPC`](@ref) economic
181176` JE ` do not include the states, the speed is now defined as an unmeasured output to design a
182177Kalman Filter similar to the previous one (`` \mathbf{y^m} = θ `` and `` \mathbf{y^u} = ω `` ):
183178
184- ``` @example 1
179+ ``` @example man_nonlin
185180h2(x, _ , _ ) = [180/π*x[1], x[2]]
186181nu, nx, ny = 1, 2, 2
187182model2 = setname!(NonLinModel(f, h2, Ts, nu, nx, ny; p=p_model), u=vu, x=vx, y=[vy; vx[2]])
@@ -193,7 +188,7 @@ The `plant2` object based on `h2` is also required since [`sim!`](@ref) expects
193188output vector of ` plant ` argument corresponds to the model output vector in ` mpc ` argument.
194189We can now define the `` J_E `` function and the ` empc ` controller:
195190
196- ``` @example 1
191+ ``` @example man_nonlin
197192function JE(Ue, Ŷe, _ , p)
198193 Ts = p
199194 τ, ω = Ue[1:end-1], Ŷe[2:2:end-1]
@@ -209,7 +204,7 @@ lead to a static error on the angle setpoint. The second element of `Mwt` is zer
209204speed `` ω `` is not requested to track a setpoint. The closed-loop response to a 180°
210205setpoint is similar:
211206
212- ``` @example 1
207+ ``` @example man_nonlin
213208unset_time_limit_sec(empc.optim) # hide
214209res2_ry = sim!(empc, N, [180, 0], plant=plant2, x_0=[0, 0], x̂_0=[0, 0, 0])
215210plot(res2_ry, ploty=[1])
@@ -220,7 +215,7 @@ savefig("plot5_NonLinMPC.svg"); nothing # hide
220215
221216and the energy consumption is slightly lower:
222217
223- ``` @example 1
218+ ``` @example man_nonlin
224219function calcW(res)
225220 τ, ω = res.U_data[1, 1:end-1], res.X_data[2, 1:end-1]
226221 return Ts*sum(τ.*ω)
@@ -230,7 +225,7 @@ Dict(:W_nmpc => calcW(res_ry), :W_empc => calcW(res2_ry))
230225
231226Also, for a 10° step disturbance:
232227
233- ``` @example 1
228+ ``` @example man_nonlin
234229res2_yd = sim!(empc, N, [180; 0]; plant=plant2, x_0=[π, 0], x̂_0=[π, 0, 0], y_step=[10, 0])
235230plot(res2_yd, ploty=[1])
236231savefig("plot6_NonLinMPC.svg"); nothing # hide
@@ -241,13 +236,93 @@ savefig("plot6_NonLinMPC.svg"); nothing # hide
241236the new controller is able to recuperate a little more energy from the pendulum (i.e.
242237negative work):
243238
244- ``` @example 1
239+ ``` @example man_nonlin
245240Dict(:W_nmpc => calcW(res_yd), :W_empc => calcW(res2_yd))
246241```
247242
248243Of course, this gain is only exploitable if the motor electronic includes some kind of
249244regenerative circuitry.
250245
246+ ## Custom Nonlinear Inequality Constraints
247+
248+ Instead of limits on the torque, suppose that the motor can deliver a maximum of 3 watt:
249+
250+ ``` math
251+ P(t) = τ(t) ω(t) ≤ P_\mathrm{max}
252+ ```
253+
254+ with `` P_\mathrm{max} = 3 `` W. This inequality describes nonlinear constraints that can
255+ be implemented using the custom `` \mathbf{g_c} `` function of [ ` NonLinMPC ` ] ( @ref ) . The
256+ constructor expects a function in this form:
257+
258+ ``` math
259+ \mathbf{g_c}(\mathbf{U_e}, \mathbf{Ŷ_e}, \mathbf{D̂_e}, \mathbf{p}, ϵ) ≤ \mathbf{0}
260+ ```
261+
262+ in which `` ϵ `` is the slack variable (scalar), an necessary feature to ensure feasibility
263+ when the nonlinear inequality constraints are active. There is also an additional ` LHS `
264+ argument ("left-hand side" of the inequality above) for the in-place version:
265+
266+ ``` @example man_nonlin
267+ function gc!(LHS, Ue, Ŷe, _, p, ϵ)
268+ Pmax, Hp = p
269+ i_τ, i_ω = 1, 2
270+ for i in eachindex(LHS)
271+ τ, ω = Ue[i_τ], Ŷe[i_ω]
272+ P = τ*ω
273+ LHS[i] = P - Pmax - ϵ
274+ i_τ += 1
275+ i_ω += 2
276+ end
277+ return nothing
278+ end
279+ nothing # hide
280+ ```
281+
282+ Here we implemented the custom nonlinear constraint function in the mutating form to reduce
283+ the computational burden (see [ ` NonLinMPC ` ] ( @ref ) Extended Help for details). Note that
284+ similar mutating forms are also possible for the ` f ` and ` h ` functions of [ ` NonLinModel ` ] ( @ref ) .
285+ We construct the controller by enabling relaxation with the ` Cwt ` argument, and also by
286+ specifying the number of custom inequality constraints ` nc ` :
287+
288+ ``` @example man_nonlin
289+ Cwt, Pmax, nc = 1e5, 3, Hp+1
290+ p_nmpc2 = [Pmax, Hp]
291+ nmpc2 = NonLinMPC(estim2; Hp, Hc, Nwt=Nwt, Mwt=[0.5, 0], Cwt, gc!, nc, p=p_nmpc2)
292+ using JuMP; unset_time_limit_sec(nmpc2.optim) # hide
293+ nmpc2 # hide
294+ ```
295+
296+ In addition to the 180° setpoint response:
297+
298+ ``` @example man_nonlin
299+ using Logging # hide
300+ res3_ry = with_logger(ConsoleLogger(stderr, Error)) do # hide
301+ res3_ry = sim!(nmpc2, N, [180; 0]; plant=plant2, x_0=[0, 0], x̂_0=[0, 0, 0]);
302+ end # hide
303+ nothing # hide
304+ ```
305+
306+ a plot for the power `` P(t) `` is included below:
307+
308+ ``` @example man_nonlin
309+ function plotWithPower(res, Pmax)
310+ t, τ, ω = res.T_data, res.U_data[1, :], res.X_data[2, :]
311+ P, Pmax = τ.*ω, fill(Pmax, size(t))
312+ plt1 = plot(res, ploty=[1])
313+ plt2 = plot(t, P, label=raw"$P$", ylabel=raw"$P$ (W)", xlabel="Time (s)", legend=:right)
314+ plot!(plt2, t, Pmax, label=raw"$P_\mathrm{max}$", linestyle=:dot, linewidth=1.5)
315+ return plot(plt1, plt2, layout=(2,1))
316+ end
317+ plotWithPower(res3_ry, Pmax)
318+ savefig("plot7_NonLinMPC.svg"); nothing # hide
319+ ```
320+
321+ ![ plot7_NonLinMPC] ( plot7_NonLinMPC.svg )
322+
323+ The slight constraint violation is caused here by the modeling error on the friction
324+ coefficient `` K `` .
325+
251326## Model Linearization
252327
253328Nonlinear MPC is more computationally expensive than [ ` LinMPC ` ] ( @ref ) . Solving the problem
@@ -257,13 +332,13 @@ fast dynamics. To ease the design and comparison with [`LinMPC`](@ref), the [`li
257332function allows automatic linearization of [ ` NonLinModel ` ] ( @ref ) based on [ ` ForwardDiff.jl ` ] ( https://juliadiff.org/ForwardDiff.jl/stable/ ) .
258333We first linearize ` model ` at the point `` θ = π `` rad and `` ω = τ = 0 `` (inverted position):
259334
260- ``` @example 1
335+ ``` @example man_nonlin
261336linmodel = linearize(model, x=[π, 0], u=[0])
262337```
263338
264339A [ ` SteadyKalmanFilter ` ] ( @ref ) and a [ ` LinMPC ` ] ( @ref ) are designed from ` linmodel ` :
265340
266- ``` @example 1
341+ ``` @example man_nonlin
267342
268343skf = SteadyKalmanFilter(linmodel; σQ, σR, nint_u, σQint_u)
269344mpc = LinMPC(skf; Hp, Hc, Mwt, Nwt, Cwt=Inf)
@@ -272,13 +347,13 @@ mpc = setconstraint!(mpc, umin=[-1.5], umax=[+1.5])
272347
273348The linear controller satisfactorily rejects the 10° step disturbance:
274349
275- ``` @example 1
350+ ``` @example man_nonlin
276351res_lin = sim!(mpc, N, [180.0]; plant, x_0=[π, 0], y_step=[10])
277352plot(res_lin)
278- savefig("plot7_NonLinMPC .svg"); nothing # hide
353+ savefig("plot9_NonLinMPC .svg"); nothing # hide
279354```
280355
281- ![ plot7_NonLinMPC ] ( plot7_NonLinMPC .svg)
356+ ![ plot9_NonLinMPC ] ( plot9_NonLinMPC .svg)
282357
283358Solving the optimization problem of ` mpc ` with [ ` DAQP ` ] ( https://darnstrom.github.io/daqp/ )
284359optimizer instead of the default ` OSQP ` solver can improve the performance here. It is
@@ -290,7 +365,7 @@ than one):
290365 embedded quadratic programming using recursive LDLᵀ updates. IEEE Trans. Autom. Contr.,
291366 67(8). < https://doi.org/doi:10.1109/TAC.2022.3176430 > .
292367
293- ``` @example 1
368+ ``` @example man_nonlin
294369using LinearAlgebra; poles = eigvals(linmodel.A)
295370```
296371
@@ -302,7 +377,7 @@ using Pkg; Pkg.add("DAQP")
302377
303378Constructing a [ ` LinMPC ` ] ( @ref ) with ` DAQP ` :
304379
305- ``` @example 1
380+ ``` @example man_nonlin
306381using JuMP, DAQP
307382daqp = Model(DAQP.Optimizer, add_bridges=false)
308383mpc2 = LinMPC(skf; Hp, Hc, Mwt, Nwt, Cwt=Inf, optim=daqp)
@@ -311,27 +386,27 @@ mpc2 = setconstraint!(mpc2; umin, umax)
311386
312387does slightly improve the rejection of the step disturbance:
313388
314- ``` @example 1
389+ ``` @example man_nonlin
315390res_lin2 = sim!(mpc2, N, [180.0]; plant, x_0=[π, 0], y_step=[10])
316391plot(res_lin2)
317- savefig("plot8_NonLinMPC .svg"); nothing # hide
392+ savefig("plot10_NonLinMPC .svg"); nothing # hide
318393```
319394
320- ![ plot8_NonLinMPC ] ( plot8_NonLinMPC .svg)
395+ ![ plot10_NonLinMPC ] ( plot10_NonLinMPC .svg)
321396
322397The closed-loop performance is still lower than the nonlinear controller, as expected, but
323398computations are about 210 times faster (0.000071 s versus 0.015 s per time steps, on
324399average). However, remember that ` linmodel ` is only valid for angular positions near 180°.
325400For example, the 180° setpoint response from 0° is unsatisfactory since the predictions are
326401poor in the first quadrant:
327402
328- ``` @example 1
403+ ``` @example man_nonlin
329404res_lin3 = sim!(mpc2, N, [180.0]; plant, x_0=[0, 0])
330405plot(res_lin3)
331- savefig("plot9_NonLinMPC .svg"); nothing # hide
406+ savefig("plot11_NonLinMPC .svg"); nothing # hide
332407```
333408
334- ![ plot9_NonLinMPC ] ( plot9_NonLinMPC .svg)
409+ ![ plot11_NonLinMPC ] ( plot11_NonLinMPC .svg)
335410
336411Multiple linearized model and controller objects are required for large deviations from this
337412operating point. This is known as gain scheduling. Another approach is adapting the model of
@@ -345,7 +420,7 @@ be designed with minimal efforts. The [`SteadyKalmanFilter`](@ref) does not supp
345420[ ` setmodel! ` ] ( @ref ) so we need to use the time-varying [ ` KalmanFilter ` ] ( @ref ) , and we
346421initialize it with a linearization at `` θ = ω = τ = 0 `` :
347422
348- ``` @example 1
423+ ``` @example man_nonlin
349424linmodel = linearize(model, x=[0, 0], u=[0])
350425kf = KalmanFilter(linmodel; σQ, σR, nint_u, σQint_u)
351426mpc3 = LinMPC(kf; Hp, Hc, Mwt, Nwt, Cwt=Inf, optim=daqp)
@@ -354,7 +429,7 @@ mpc3 = setconstraint!(mpc3; umin, umax)
354429
355430We create a function that simulates the plant and the adaptive controller:
356431
357- ``` @example 1
432+ ``` @example man_nonlin
358433function sim_adapt!(mpc, nonlinmodel, N, ry, plant, x_0, x̂_0, y_step=[0])
359434 U_data, Y_data, Ry_data = zeros(plant.nu, N), zeros(plant.ny, N), zeros(plant.ny, N)
360435 setstate!(plant, x_0)
@@ -382,29 +457,25 @@ that is, when both ``\mathbf{u}(k)`` and ``\mathbf{x̂}_{k}(k)`` are available a
382457operating point. The [ ` SimResult ` ] ( @ref ) object is for plotting purposes only. The adaptive
383458[ ` LinMPC ` ] ( @ref ) performances are similar to the nonlinear MPC, both for the 180° setpoint:
384459
385- ``` @example 1
460+ ``` @example man_nonlin
386461x_0 = [0, 0]; x̂_0 = [0, 0, 0]; ry = [180]
387462res_slin = sim_adapt!(mpc3, model, N, ry, plant, x_0, x̂_0)
388463plot(res_slin)
389- savefig("plot10_NonLinMPC .svg"); nothing # hide
464+ savefig("plot12_NonLinMPC .svg"); nothing # hide
390465```
391466
392- ![ plot10_NonLinMPC ] ( plot10_NonLinMPC .svg)
467+ ![ plot12_NonLinMPC ] ( plot12_NonLinMPC .svg)
393468
394469and the 10° step disturbance:
395470
396- ``` @example 1
471+ ``` @example man_nonlin
397472x_0 = [π, 0]; x̂_0 = [π, 0, 0]; y_step = [10]
398473res_slin = sim_adapt!(mpc3, model, N, ry, plant, x_0, x̂_0, y_step)
399474plot(res_slin)
400- savefig("plot11_NonLinMPC .svg"); nothing # hide
475+ savefig("plot13_NonLinMPC .svg"); nothing # hide
401476```
402477
403- ![ plot11_NonLinMPC ] ( plot11_NonLinMPC .svg)
478+ ![ plot13_NonLinMPC ] ( plot13_NonLinMPC .svg)
404479
405480The computations of the successive linearization MPC are about 75 times faster than the
406- nonlinear MPC on average, an impressive gain for similar closed-loop performances!
407-
408- ``` @setup 1
409- global_logger(old_logger);
410- ```
481+ nonlinear MPC on average, an impressive gain for similar closed-loop performances!
0 commit comments