Skip to content

Commit e0b3c1a

Browse files
committed
add NonLinMPC example in Manual
1 parent 1428073 commit e0b3c1a

File tree

7 files changed

+136
-6
lines changed

7 files changed

+136
-6
lines changed

docs/src/manual.md

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,3 +136,93 @@ p3 = plot(t_data,u_data[1,:],label="cold", linetype=:steppost); ylabel!("flow ra
136136
plot!(t_data,u_data[2,:],label="hot", linetype=:steppost); xlabel!("time (s)")
137137
p = plot(p1, p2, p3, layout=(3,1), fmt=:svg)
138138
```
139+
140+
### Nonlinear Model
141+
142+
In this example, the goal is to control the angular position ``θ`` of a pendulum
143+
attached to a motor. If the manipulated input is the motor torque ``τ``, the vectors
144+
are:
145+
146+
```math
147+
\begin{aligned}
148+
\mathbf{u} &= \begin{bmatrix} τ \end{bmatrix} \\
149+
\mathbf{y} &= \begin{bmatrix} θ \end{bmatrix}
150+
\end{aligned}
151+
```
152+
153+
The plant model is nonlinear:
154+
155+
```math
156+
\begin{aligned}
157+
\dot{θ}(t) &= ω(t) \\
158+
\dot{ω}(t) &= -\frac{g}{L}\sin\big( θ(t) \big) - \frac{k}{m} ω(t) + \frac{m}{L^2}τ(t)
159+
\end{aligned}
160+
```
161+
162+
in which ``g`` is the gravitational acceleration, ``L``, the pendulum length, ``k``, the
163+
friction coefficient at the pivot point, and ``m``, the mass attached at the end of the
164+
pendulum. Here, the explicit Euler method discretizes the system to construct a
165+
[`NonLinModel`](@ref):
166+
167+
```@example 2
168+
using ModelPredictiveControl
169+
function pendulum(par, x, u)
170+
g, L, k, m = par # [m/s], [m], [kg/s], [kg]
171+
θ, ω = x[1], x[2] # [rad], [rad/s]
172+
τ = u[1] # [N m]
173+
dθ = ω
174+
dω = -g/L*sin(θ) - k/m*ω + τ/m/L^2
175+
return [dθ, dω]
176+
end
177+
Ts = 0.1 # [s]
178+
par = (9.8, 0.4, 1.2, 0.3)
179+
f(x, u, _ ) = x + Ts*pendulum(par, x, u) # Euler method
180+
h(x, _ ) = [180/π*x[1]] # [°]
181+
nu, nx, ny = 1, 2, 1
182+
model = NonLinModel(f, h, Ts, nu, nx, ny)
183+
```
184+
185+
The output function ``\mathbf{h}`` converts the angular position ``θ`` to degrees. It
186+
is good practice to first simulate `model` using [`sim!`](@ref) as a quick sanity check:
187+
188+
```@example 2
189+
using Plots
190+
u = [0.5] # τ = 0.5 N m
191+
plot(sim!(model, 60, u), plotu=false)
192+
```
193+
194+
An [`UnscentedKalmanFilter`](@ref) estimates the plant state :
195+
196+
```@example 2
197+
estim = UnscentedKalmanFilter(model, σQ=[0.5, 2.5], σQ_int=[0.5])
198+
```
199+
200+
The standard deviation of the angular velocity ``ω`` is higher here (second value of `σQ`)
201+
since ``\dot{ω}(t)`` equation includes the friction coefficient ``k``, an uncertain
202+
parameter. The estimator tuning is tested on a simulated plant with a different ``k`` value:
203+
204+
```@example 2
205+
par_plant = (par[1], par[2], par[3] + 0.25, par[4])
206+
f_plant(x, u, _) = x + Ts*pendulum(par_plant, x, u)
207+
plant = NonLinModel(f_plant, h, Ts, nu, nx, ny)
208+
res = sim!(estim, 30, [0.5], plant=plant, y_noise=[0.5]) # τ = 0.5 N m
209+
p2 = plot(res, plotu=false, plotx=true, plotx̂=true)
210+
```
211+
212+
The Kalman filter performance seems sufficient for control applications. As the motor torque
213+
is limited to -1.5 to 1.5 N m, we incorporate the manipulated input constraints in a
214+
[`NonLinMPC`](@ref):
215+
216+
```@example 2
217+
mpc = NonLinMPC(estim, Hp=20, Hc=2, Mwt=[0.1], Nwt=[1.0], Cwt=Inf)
218+
mpc = setconstraint!(mpc, umin=[-1.5], umax=[+1.5])
219+
```
220+
221+
We test `mpc` performance on `plant` by imposing an angular setpoint of 180° (inverted position):
222+
223+
```@example 2
224+
res = sim!(mpc, 30, [180.0], x̂0=zeros(mpc.estim.nx̂), plant=plant, x0=zeros(plant.nx))
225+
plot(res, plotŷ=true)
226+
```
227+
228+
The controller here seems robust enough to variations on ``k`` coefficients.

docs/src/test.jl

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
using ModelPredictiveControl
2+
3+
function pendulum(par, x, u)
4+
g, L, K, m = par # [m/s], [m], [kg/s], [kg]
5+
θ, ω = x[1], x[2] # [rad], [rad/s]
6+
τ = u[1] # [N m]
7+
= ω
8+
= -g/L*sin(θ) - K/m*ω + τ/m/L^2
9+
return [dθ, dω]
10+
end
11+
12+
Ts = 0.1 # [s]
13+
par = (9.8, 0.4, 1.2, 0.3)
14+
f(x, u, _) = x + Ts*pendulum(par, x, u)
15+
h(x, _ ) = [180/π*x[1]] # [rad] to [°]
16+
nu, nx, ny = 1, 2, 1
17+
model = NonLinModel(f, h, Ts, nu, nx, ny)
18+
19+
using Plots
20+
p1 = plot(sim!(model, 50, [0.5])) # τ = 0.5 N m
21+
display(p1)
22+
23+
par_plant = (par[1], par[2], par[3] + 0.25, par[4])
24+
f_plant(x, u, _) = x + Ts*pendulum(par_plant, x, u)
25+
plant = NonLinModel(f_plant, h, Ts, nu, nx, ny)
26+
27+
estim = UnscentedKalmanFilter(model, σQ=[0.5, 2.5], σQ_int=[0.5])
28+
29+
30+
res = sim!(estim, 30, [0.5], plant=plant, y_noise=[0.5]) # τ = 0.5 N m
31+
p2 = plot(res, plotu=false, plotx=true, plotx̂=true)
32+
display(p2)
33+
34+
35+
mpc = NonLinMPC(estim, Hp=20, Hc=2, Mwt=[0.1], Nwt=[1.0], Cwt=Inf)
36+
mpc = setconstraint!(mpc, umin=[-1.5], umax=[+1.5])
37+
38+
res = sim!(mpc, 30, [180.0], x̂=zeros(mpc.estim.nx̂), plant=plant)
39+
plot(res, plotŷ=true)

src/controller/nonlinmpc.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ function init_optimization!(mpc::NonLinMPC)
230230
# --- nonlinear optimization init ---
231231
model = mpc.estim.model
232232
ny, nu, Hp, Hc = model.ny, model.nu, mpc.Hp, mpc.Hc
233-
nC = (2*Hc*nu + 2*Hc*nu + 2*Hp*ny + 2) - length(mpc.con.b)
233+
nC = (2*Hc*nu + 2*nvar + 2*Hp*ny) - length(mpc.con.b)
234234
# inspired from https://jump.dev/JuMP.jl/stable/tutorials/nonlinear/tips_and_tricks/#User-defined-functions-with-vector-outputs
235235
Jfunc, Cfunc = let mpc=mpc, model=model, nC=nC, nvar=nvar , nŶ=Hp*ny
236236
last_ΔŨtup_float, last_ΔŨtup_dual = nothing, nothing

src/estimator/internal_model.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ struct InternalModel{M<:SimModel} <: StateEstimator
3939
Âs, B̂s = init_internalmodel(As, Bs, Cs, Ds)
4040
i_ym = collect(i_ym)
4141
lastu0 = zeros(nu)
42-
x̂d == copy(model.x) # x̂ and x̂d are same object (updating x̂d will update x̂)
42+
x̂d == zeros(model.nx) # x̂ and x̂d are same object (updating x̂d will update x̂)
4343
x̂s = zeros(nxs)
4444
return new(
4545
model,

src/estimator/kalman.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ struct SteadyKalmanFilter <: StateEstimator
4040
end
4141
i_ym = collect(i_ym)
4242
lastu0 = zeros(nu)
43-
= [copy(model.x); zeros(nxs)]
43+
= [zeros(model.nx); zeros(nxs)]
4444
= Hermitian(Q̂, :L)
4545
= Hermitian(R̂, :L)
4646
return new(
@@ -202,7 +202,7 @@ struct KalmanFilter <: StateEstimator
202202
Ĉm, D̂dm = Ĉ[i_ym, :], D̂d[i_ym, :] # measured outputs ym only
203203
i_ym = collect(i_ym)
204204
lastu0 = zeros(nu)
205-
= [copy(model.x); zeros(nxs)]
205+
= [zeros(model.nx); zeros(nxs)]
206206
P̂0 = Hermitian(P̂0, :L)
207207
= Hermitian(Q̂, :L)
208208
= Hermitian(R̂, :L)
@@ -352,7 +352,7 @@ struct UnscentedKalmanFilter{M<:SimModel} <: StateEstimator
352352
nσ, γ, m̂, Ŝ = init_ukf(nx̂, α, β, κ)
353353
i_ym = collect(i_ym)
354354
lastu0 = zeros(nu)
355-
= [copy(model.x); zeros(nxs)]
355+
= [zeros(model.nx); zeros(nxs)]
356356
P̂0 = Hermitian(P̂0, :L)
357357
= Hermitian(Q̂, :L)
358358
= Hermitian(R̂, :L)

src/estimator/luenberger.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ struct Luenberger <: StateEstimator
3333
end
3434
i_ym = collect(i_ym)
3535
lastu0 = zeros(nu)
36-
= [copy(model.x); zeros(nxs)]
36+
= [zeros(model.nx); zeros(nxs)]
3737
return new(
3838
model,
3939
lastu0, x̂,

src/plot_sim.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ function sim_closedloop!(
186186
lastd, lasty = d, evaloutput(plant, d)
187187
if isnothing(x̂0)
188188
initstate!(est_mpc, lastu, lasty[estim.i_ym], lastd)
189+
189190
else
190191
setstate!(est_mpc, x̂0)
191192
end

0 commit comments

Comments
 (0)