Skip to content

Commit 37fb153

Browse files
committed
bump version
1 parent 9d25ef2 commit 37fb153

File tree

2 files changed

+82
-80
lines changed

2 files changed

+82
-80
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "ModelPredictiveControl"
22
uuid = "61f9bdb8-6ae4-484a-811f-bbf86720c31c"
33
authors = ["Francis Gagnon"]
4-
version = "0.11.2"
4+
version = "0.12.0"
55

66
[deps]
77
ControlSystemsBase = "aaaaaaaa-a6ca-5380-bf3e-84a91bcd477e"

docs/src/manual/nonlinmpc.md

Lines changed: 81 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -136,81 +136,6 @@ savefig(ans, "plot4_NonLinMPC.svg"); nothing # hide
136136

137137
![plot4_NonLinMPC](plot4_NonLinMPC.svg)
138138

139-
## Linearizing the Model
140-
141-
Nonlinear MPC are more computationally expensive than [`LinMPC`](@ref). Solving the problem
142-
should always be faster than the sampling time ``T_s = 0.1`` s for real-time operation. For
143-
electronic or mechanical systems like here, this requirement is sometimes harder to achieve
144-
because of their fast dynamics. To ease the design and comparison with [`LinMPC`](@ref), the
145-
[`linearize`](@ref) function allows automatic linearization of [`NonLinModel`](@ref) based
146-
on [`ForwardDiff.jl`](https://juliadiff.org/ForwardDiff.jl/stable/). We first linearize
147-
`model` at the operating points ``\mathbf{x} = [\begin{smallmatrix} π\\0 \end{smallmatrix}]``
148-
and ``\mathbf{u} = 0`` (inverted position):
149-
150-
```@example 1
151-
linmodel = linearize(model, x=[π, 0], u=[0])
152-
```
153-
154-
It is worth mentionning that the Euler method in `model` object is not the best choice for
155-
linearization since its accuracy is low (i.e. approximation of a bad approximation). A
156-
[`SteadyKalmanFilter`](@ref) and a [`LinMPC`](@ref) are designed from `linmodel`:
157-
158-
```@example 1
159-
kf = SteadyKalmanFilter(linmodel; σQ, σR, nint_u, σQint_u)
160-
mpc = LinMPC(kf, Hp=20, Hc=2, Mwt=[0.5], Nwt=[2.5])
161-
mpc = setconstraint!(mpc, umin=[-1.5], umax=[+1.5])
162-
```
163-
164-
The linear controller has difficulties to reject the 10° step disturbance:
165-
166-
```@example 1
167-
res_lin = sim!(mpc, N, [180.0]; plant, x0=[π, 0], y_step=[10])
168-
plot(res_lin)
169-
savefig(ans, "plot5_NonLinMPC.svg"); nothing # hide
170-
```
171-
172-
![plot5_NonLinMPC](plot5_NonLinMPC.svg)
173-
174-
Some improvements can be achieved by solving the optimization problem of `mpc` with [`DAQP`](https://darnstrom.github.io/daqp/)
175-
optimizer instead of the default `OSQP` solver. It is indeed documented that `DAQP` can
176-
perform better on small/medium dense matrices and unstable poles, which is obviously the
177-
case here (the absolute value of unstable poles are greater than one).:
178-
179-
```@example 1
180-
using LinearAlgebra; poles = eigvals(linmodel.A)
181-
```
182-
183-
To install it, run:
184-
185-
```text
186-
using Pkg; Pkg.add("DAQP")
187-
```
188-
189-
Constructing a [`LinMPC`](@ref) with `DAQP`:
190-
191-
```@example 1
192-
using JuMP, DAQP
193-
daqp = Model(DAQP.Optimizer, add_bridges=false)
194-
mpc2 = LinMPC(kf, Hp=20, Hc=2, Mwt=[0.5], Nwt=[2.5], optim=daqp)
195-
mpc2 = setconstraint!(mpc, umin=[-1.5], umax=[+1.5])
196-
```
197-
198-
does improve the rejection of the step disturbance:
199-
200-
```@example 1
201-
res_lin2 = sim!(mpc2, N, [180.0]; plant, x0=[π, 0], y_step=[10])
202-
plot(res_lin2)
203-
savefig(ans, "plot6_NonLinMPC.svg"); nothing # hide
204-
```
205-
206-
![plot6_NonLinMPC](plot6_NonLinMPC.svg)
207-
208-
The performance is still lower than the nonlinear controller, as expected, but computations
209-
are about 2000 times faster (0.00002 s versus 0.04 s per time steps on average). Note that
210-
`linmodel` is only valid for angular positions near 180°. Multiple linearized models and
211-
controllers are required for large deviations from this operating point. This is known as
212-
gain scheduling.
213-
214139
## Economic Model Predictive Controller
215140

216141
Economic MPC can achieve the same objective but with lower economical costs. For this case
@@ -273,10 +198,10 @@ setpoint is:
273198
unset_time_limit_sec(empc.optim) # hide
274199
res2_ry = sim!(empc, N, [180, 0], plant=plant2, x0=[0, 0], x̂0=[0, 0, 0])
275200
plot(res2_ry)
276-
savefig(ans, "plot7_NonLinMPC.svg"); nothing # hide
201+
savefig(ans, "plot5_NonLinMPC.svg"); nothing # hide
277202
```
278203

279-
![plot7_NonLinMPC](plot7_NonLinMPC.svg)
204+
![plot5_NonLinMPC](plot5_NonLinMPC.svg)
280205

281206
and the energy consumption is slightly lower:
282207

@@ -293,10 +218,10 @@ Also, for a 10° step disturbance:
293218
```@example 1
294219
res2_yd = sim!(empc, N, [180; 0]; plant=plant2, x0=[π, 0], x̂0=[π, 0, 0], y_step=[10, 0])
295220
plot(res2_yd)
296-
savefig(ans, "plot8_NonLinMPC.svg"); nothing # hide
221+
savefig(ans, "plot6_NonLinMPC.svg"); nothing # hide
297222
```
298223

299-
![plot8_NonLinMPC](plot8_NonLinMPC.svg)
224+
![plot6_NonLinMPC](plot6_NonLinMPC.svg)
300225

301226
the new controller is able to recuperate more energy from the pendulum (i.e. negative work):
302227

@@ -306,3 +231,80 @@ Dict(:W_nmpc => calcW(res_yd), :W_empc => calcW(res2_yd))
306231

307232
Of course, this gain is only exploitable if the motor electronic includes some kind of
308233
regenerative circuitry.
234+
235+
## Linearizing the Model
236+
237+
Nonlinear MPC are more computationally expensive than [`LinMPC`](@ref). Solving the problem
238+
should always be faster than the sampling time ``T_s = 0.1`` s for real-time operation. This
239+
requirement is sometimes hard to meet on electronics or mechanical systems because of fast
240+
dynamics. To ease the design and comparison with [`LinMPC`](@ref), the [`linearize`](@ref)
241+
function allows automatic linearization of [`NonLinModel`](@ref) based on [`ForwardDiff.jl`](https://juliadiff.org/ForwardDiff.jl/stable/).
242+
We first linearize `model` at the point ``θ = π`` rad and ``ω = τ = 0`` (inverted position):
243+
244+
```@example 1
245+
linmodel = linearize(model, x=[π, 0], u=[0])
246+
```
247+
248+
It is worth mentioning that the Euler method in `model` object is not the best choice for
249+
linearization since its accuracy is low (approximation of a poor approximation). A
250+
[`SteadyKalmanFilter`](@ref) and a [`LinMPC`](@ref) are designed from `linmodel`:
251+
252+
```@example 1
253+
kf = SteadyKalmanFilter(linmodel; σQ, σR, nint_u, σQint_u)
254+
mpc = LinMPC(kf, Hp=20, Hc=2, Mwt=[0.5], Nwt=[2.5])
255+
mpc = setconstraint!(mpc, umin=[-1.5], umax=[+1.5])
256+
```
257+
258+
The linear controller has difficulties to reject the 10° step disturbance:
259+
260+
```@example 1
261+
res_lin = sim!(mpc, N, [180.0]; plant, x0=[π, 0], y_step=[10])
262+
plot(res_lin)
263+
savefig(ans, "plot7_NonLinMPC.svg"); nothing # hide
264+
```
265+
266+
![plot7_NonLinMPC](plot7_NonLinMPC.svg)
267+
268+
Solving the optimization problem of `mpc` with [`DAQP`](https://darnstrom.github.io/daqp/)
269+
optimizer instead of the default `OSQP` solver can help here. It is indeed documented that
270+
`DAQP` can perform better on small/medium dense matrices and unstable poles[^1], which is
271+
obviously the case here (absolute value of unstable poles are greater than one):
272+
273+
[^1]: Arnström, D., Bemporad, A., and Axehill, D. (2022). A dual active-set solver for
274+
embedded quadratic programming using recursive LDLᵀ updates. IEEE Trans. Autom. Contr.,
275+
67(8). https://doi.org/doi:10.1109/TAC.2022.3176430.
276+
277+
```@example 1
278+
using LinearAlgebra; poles = eigvals(linmodel.A)
279+
```
280+
281+
To install the solver, run:
282+
283+
```text
284+
using Pkg; Pkg.add("DAQP")
285+
```
286+
287+
Constructing a [`LinMPC`](@ref) with `DAQP`:
288+
289+
```@example 1
290+
using JuMP, DAQP
291+
daqp = Model(DAQP.Optimizer, add_bridges=false)
292+
mpc2 = LinMPC(kf, Hp=20, Hc=2, Mwt=[0.5], Nwt=[2.5], optim=daqp)
293+
mpc2 = setconstraint!(mpc2, umin=[-1.5], umax=[+1.5])
294+
```
295+
296+
does improve the rejection of the step disturbance:
297+
298+
```@example 1
299+
res_lin2 = sim!(mpc2, N, [180.0]; plant, x0=[π, 0], y_step=[10])
300+
plot(res_lin2)
301+
savefig(ans, "plot8_NonLinMPC.svg"); nothing # hide
302+
```
303+
304+
![plot8_NonLinMPC](plot8_NonLinMPC.svg)
305+
306+
The closed-loop performance is still lower than the nonlinear controller, as expected, but
307+
computations are about 2000 times faster (0.00002 s versus 0.04 s per time steps, on
308+
average). Note that `linmodel` is only valid for angular positions near 180°. Multiple
309+
linearized models and controllers are required for large deviations from this operating
310+
point. This is known as gain scheduling.

0 commit comments

Comments
 (0)