Skip to content

Commit 734fe76

Browse files
committed
Merge remote-tracking branch 'origin/main'
2 parents ff3359e + 920cedf commit 734fe76

File tree

14 files changed

+173
-38
lines changed

14 files changed

+173
-38
lines changed

.github/workflows/CI.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,5 @@ jobs:
3535
- uses: julia-actions/cache@v1
3636
- uses: julia-actions/julia-buildpkg@v1
3737
- uses: julia-actions/julia-runtest@v1
38+
- uses: julia-actions/julia-processcoverage@v1
39+
- uses: codecov/codecov-action@v3

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.5.3"
4+
version = "0.5.5"
55

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

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# ModelPredictiveControl.jl
22

33
[![Build Status](https://github.com/franckgaga/ModelPredictiveControl.jl/actions/workflows/CI.yml/badge.svg?branch=main)](https://github.com/franckgaga/ModelPredictiveControl.jl/actions/workflows/CI.yml?query=branch%3Amain)
4+
[![codecov](https://codecov.io/gh/franckgaga/ModelPredictiveControl.jl/branch/main/graph/badge.svg?token=K4V0L113M4)](https://codecov.io/gh/franckgaga/ModelPredictiveControl.jl)
45
[![doc-stable](https://img.shields.io/badge/docs-stable-blue.svg)](https://franckgaga.github.io/ModelPredictiveControl.jl/stable)
56
[![coc-dev](https://img.shields.io/badge/docs-dev-blue.svg)](https://franckgaga.github.io/ModelPredictiveControl.jl/dev)
67

@@ -20,8 +21,8 @@ using Pkg; Pkg.add("ModelPredictiveControl")
2021

2122
## Getting Started
2223

23-
To construct model predictive controllers, we must first specify a plant model that is
24-
typically extracted from input-output data using [system identification](https://github.com/baggepinnen/ControlSystemIdentification.jl).
24+
To construct model predictive controllers (MPCs), we must first specify a plant model that
25+
is typically extracted from input-output data using [system identification](https://github.com/baggepinnen/ControlSystemIdentification.jl).
2526
The model here is linear with one input, two outputs and a large time delay in the first
2627
channel:
2728

@@ -64,8 +65,8 @@ plot(res, plotry=true, plotŷmax=true)
6465

6566
![StepChangeResponse](/docs/src/assets/readme_result.svg)
6667

67-
See the [manual](https://franckgaga.github.io/ModelPredictiveControl.jl/stable/manual/) for
68-
more detailed examples.
68+
See the [manual](https://franckgaga.github.io/ModelPredictiveControl.jl/stable/manual/linmpc/)
69+
for more detailed examples.
6970

7071
## Features
7172

docs/make.jl

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,12 @@ makedocs(
2222
),
2323
pages = [
2424
"Home" => "index.md",
25-
"Manual" => "manual.md",
25+
"Manual" => [
26+
"Examples" => [
27+
"Linear Design" => "manual/linmpc.md",
28+
"Nonlinear Design" => "manual/nonlinmpc.md",
29+
],
30+
],
2631
"Functions" => [
2732
"Public" => [
2833
"Plant Models" => "public/sim_model.md",

docs/src/index.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ for the linear systems and [`JuMP.jl`](https://github.com/jump-dev/JuMP.jl) for
1111
```@contents
1212
Pages = [
1313
"index.md",
14-
"manual.md",
14+
"manual/linmpc.md",
15+
"manual/nonlinmpc.md",
1516
"public/sim_model.md",
1617
"public/state_estim.md",
1718
"public/predictive_control.md",
Lines changed: 17 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,9 @@
1-
# Manual
1+
# [Manual : Linear Design](@id my_manual)
22

3-
## Installation
3+
## Linear Model
44

5-
To install the `ModelPredictiveControl` package, run this command in the Julia REPL:
6-
7-
```text
8-
using Pkg; Pkg.add("ModelPredictiveControl")
9-
```
10-
11-
## Predictive Controller Design
12-
13-
### Linear Model
14-
15-
The considered plant is well-stirred tank with a cold and hot water inlet. The water
16-
flows out of an opening at the bottom of the tank. The manipulated inputs are the cold
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
177
``u_c`` and hot ``u_h`` water flow rate, and the measured outputs are the liquid level
188
``y_L`` and temperature ``y_T``:
199

@@ -48,14 +38,6 @@ the following linear model accurately describes the plant dynamics:
4838
\end{bmatrix}
4939
```
5040

51-
We want to design a predictive feedback that controls both the water level ``y_L`` and
52-
temperature ``y_T`` in the tank, at a sampling time of 4 s. The tank level should also never
53-
fall below 45:
54-
55-
```math
56-
y_L ≥ 45
57-
```
58-
5941
We first need to construct a [`LinModel`](@ref) objet with [`setop!`](@ref) to handle the
6042
operating points:
6143

@@ -68,8 +50,19 @@ model = setop!(LinModel(sys, Ts), uop=[10, 10], yop=[50, 30])
6850
```
6951

7052
The `model` object will be used for two purposes : to construct our controller, and as a
71-
plant simulator to test the design. We design our [`LinMPC`](@ref) controllers by including
72-
the level constraint with [`setconstraint!`](@ref):
53+
plant simulator to test the design.
54+
55+
## Linear Predictive Controller
56+
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:
59+
60+
```math
61+
y_L ≥ 45
62+
```
63+
64+
We design our [`LinMPC`](@ref) controllers by including the level constraint with
65+
[`setconstraint!`](@ref):
7366

7467
```@example 1
7568
mpc = setconstraint!(LinMPC(model, Hp=15, Hc=2), ŷmin=[45, -Inf])

docs/src/manual/nonlinmpc.md

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# Manual : Nonlinear Design
2+
3+
## Nonlinear Model
4+
5+
In this example, the goal is to control the angular position ``θ`` of a pendulum
6+
attached to a motor. If the manipulated input is the motor torque ``τ``, the vectors
7+
are:
8+
9+
```math
10+
\begin{aligned}
11+
\mathbf{u} &= \begin{bmatrix} τ \end{bmatrix} \\
12+
\mathbf{y} &= \begin{bmatrix} θ \end{bmatrix}
13+
\end{aligned}
14+
```
15+
16+
The plant model is nonlinear:
17+
18+
```math
19+
\begin{aligned}
20+
\dot{θ}(t) &= ω(t) \\
21+
\dot{ω}(t) &= -\frac{g}{L}\sin\big( θ(t) \big) - \frac{K}{m} ω(t) + \frac{1}{m L^2} τ(t)
22+
\end{aligned}
23+
```
24+
25+
in which ``g`` is the gravitational acceleration, ``L``, the pendulum length, ``K``, the
26+
friction coefficient at the pivot point, and ``m``, the mass attached at the end of the
27+
pendulum. Here, the explicit Euler method discretizes the system to construct a
28+
[`NonLinModel`](@ref):
29+
30+
```@example 1
31+
using ModelPredictiveControl
32+
function pendulum(par, x, u)
33+
g, L, K, m = par # [m/s], [m], [kg/s], [kg]
34+
θ, ω = x[1], x[2] # [rad], [rad/s]
35+
τ = u[1] # [N m]
36+
dθ = ω
37+
dω = -g/L*sin(θ) - K/m*ω + τ/m/L^2
38+
return [dθ, dω]
39+
end
40+
Ts = 0.1 # [s]
41+
par = (9.8, 0.4, 1.2, 0.3)
42+
f(x, u, _ ) = x + Ts*pendulum(par, x, u) # Euler method
43+
h(x, _ ) = [180/π*x[1]] # [°]
44+
nu, nx, ny = 1, 2, 1
45+
model = NonLinModel(f, h, Ts, nu, nx, ny)
46+
```
47+
48+
The output function ``\mathbf{h}`` converts the angular position ``θ`` to degrees. It
49+
is good practice to first simulate `model` using [`sim!`](@ref) as a quick sanity check:
50+
51+
```@example 1
52+
using Plots
53+
u = [0.5] # τ = 0.5 N m
54+
plot(sim!(model, 60, u), plotu=false)
55+
```
56+
57+
## Nonlinear Predictive Controller
58+
59+
An [`UnscentedKalmanFilter`](@ref) estimates the plant state :
60+
61+
```@example 1
62+
estim = UnscentedKalmanFilter(model, σQ=[0.5, 2.5], σQ_int=[0.5])
63+
```
64+
65+
The standard deviation of the angular velocity ``ω`` is higher here (`σQ` second value)
66+
since ``\dot{ω}(t)`` equation includes an uncertain parameter: the friction coefficient
67+
``K``. The estimator tuning is tested on a plant simulated with a different ``K``:
68+
69+
```@example 1
70+
par_plant = (par[1], par[2], par[3] + 0.25, par[4])
71+
f_plant(x, u, _) = x + Ts*pendulum(par_plant, x, u)
72+
plant = NonLinModel(f_plant, h, Ts, nu, nx, ny)
73+
res = sim!(estim, 30, [0.5], plant=plant, y_noise=[0.5]) # τ = 0.5 N m
74+
plot(res, plotu=false, plotx=true, plotx̂=true)
75+
```
76+
77+
The Kalman filter performance seems sufficient for control. As the motor torque is limited
78+
to -1.5 to 1.5 N m, we incorporate the input constraints in a [`NonLinMPC`](@ref):
79+
80+
```@example 1
81+
mpc = NonLinMPC(estim, Hp=20, Hc=2, Mwt=[0.1], Nwt=[1.0], Cwt=Inf)
82+
mpc = setconstraint!(mpc, umin=[-1.5], umax=[+1.5])
83+
```
84+
85+
We test `mpc` performance on `plant` by imposing an angular setpoint of 180° (inverted
86+
position):
87+
88+
```@example 1
89+
res = sim!(mpc, 30, [180.0], x̂0=zeros(mpc.estim.nx̂), plant=plant, x0=zeros(plant.nx))
90+
plot(res, plotŷ=true)
91+
```
92+
93+
The controller seems robust enough to variations on ``K`` coefficient.

docs/src/public/state_estim.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ the controller computations, without introducing any additional delays. Moreover
2626
[`moveinput!`](@ref) method of the predictive controllers does not automatically update the
2727
estimates with [`updatestate!`](@ref). This allows applying the calculated inputs on the
2828
real plant before starting the potentially expensive estimator computations (see
29-
[Manual](@ref) for examples).
29+
[Manual](@ref my_manual) for examples).
3030

3131
!!! info
3232
All the estimators support measured ``\mathbf{y^m}`` and unmeasured ``\mathbf{y^u}``

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

0 commit comments

Comments
 (0)