Skip to content

Commit f2f5b2e

Browse files
authored
Merge pull request #243 from JuliaControl/tc_foh
added: piecewise linear manipulated inputs with `TrapezoidalCollocation(1)`
2 parents 035fe98 + 02bcdb5 commit f2f5b2e

File tree

2 files changed

+65
-20
lines changed

2 files changed

+65
-20
lines changed

src/controller/transcription.jl

Lines changed: 60 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -64,14 +64,16 @@ for this transcription method.
6464
struct MultipleShooting <: ShootingMethod end
6565

6666
@doc raw"""
67-
TrapezoidalCollocation()
67+
TrapezoidalCollocation(h::Int=0)
6868
69-
Construct an implicit trapezoidal [`TranscriptionMethod`](@ref).
69+
Construct an implicit trapezoidal [`TranscriptionMethod`](@ref) with `h`th order hold.
7070
7171
This is the simplest collocation method. It supports continuous-time [`NonLinModel`](@ref)s
7272
only. The decision variables are the same as for [`MultipleShooting`](@ref), hence similar
73-
computational costs. It currently assumes piecewise constant manipulated inputs (or zero-
74-
order hold) between the samples, but linear interpolation will be added soon.
73+
computational costs. The `h` argument is `0` or `1`, for piecewise constant or linear
74+
manipulated inputs ``\mathbf{u}`` (`h=1` is slightly less expensive). Note that the various
75+
[`DiffSolver`](@ref) here assume zero-order hold, so `h=1` will induce a plant-model
76+
mismatch if the plant is simulated with these solvers.
7577
7678
This transcription computes the predictions by calling the continuous-time model in the
7779
equality constraint function and by using the implicit trapezoidal rule. It can handle
@@ -92,10 +94,14 @@ transcription method.
9294
for more details.
9395
"""
9496
struct TrapezoidalCollocation <: CollocationMethod
97+
h::Int
9598
nc::Int
96-
function TrapezoidalCollocation()
99+
function TrapezoidalCollocation(h::Int=0)
100+
if !(h == 0 || h == 1)
101+
throw(ArgumentError("h argument must be 0 or 1 for TrapezoidalCollocation."))
102+
end
97103
nc = 2 # 2 collocation points per interval for trapezoidal rule
98-
return new(nc)
104+
return new(h, nc)
99105
end
100106
end
101107

@@ -1376,11 +1382,11 @@ extracted from the decision variables `Z̃`. The ``\mathbf{k}`` coefficients are
13761382
evaluated from the continuous-time function `model.f!` and:
13771383
```math
13781384
\begin{aligned}
1379-
\mathbf{k}_1 &= \mathbf{f}\Big(\mathbf{x_0}(k), \mathbf{û_0}(k), \mathbf{d_0}(k) \Big) \\
1380-
\mathbf{k}_2 &= \mathbf{f}\Big(\mathbf{x_0}(k+1), \mathbf{û_0}(k), \mathbf{d_0}(k+1)\Big)
1385+
\mathbf{k}_1 &= \mathbf{f}\Big(\mathbf{x_0}(k), \mathbf{û_0}(k), \mathbf{d_0}(k) \Big) \\
1386+
\mathbf{k}_2 &= \mathbf{f}\Big(\mathbf{x_0}(k+1), \mathbf{û_0}(k+h), \mathbf{d_0}(k+1)\Big)
13811387
\end{aligned}
13821388
```
1383-
and the input of the augmented model is:
1389+
in which ``h`` is the hold order `transcription.h` and the disturbed input is:
13841390
```math
13851391
\mathbf{û_0}(k) = \mathbf{u_0}(k) + \mathbf{C_{s_u} x_s}(k)
13861392
```
@@ -1391,7 +1397,7 @@ function con_nonlinprogeq!(
13911397
mpc::PredictiveController, model::NonLinModel, transcription::TrapezoidalCollocation,
13921398
U0, Z̃
13931399
)
1394-
nu, nx̂, nd, nx = model.nu, mpc.estim.nx̂, model.nd, model.nx
1400+
nu, nx̂, nd, nx, h = model.nu, mpc.estim.nx̂, model.nd, model.nx, transcription.h
13951401
Hp, Hc = mpc.Hp, mpc.Hc
13961402
nΔU, nX̂ = nu*Hc, nx̂*Hp
13971403
Ts, p = model.Ts, model.p
@@ -1401,30 +1407,64 @@ function con_nonlinprogeq!(
14011407
X̂0_Z̃ = @views Z̃[(nΔU+1):(nΔU+nX̂)]
14021408
x̂0 = @views mpc.estim.x̂0[1:nx̂]
14031409
d0 = @views mpc.d0[1:nd]
1410+
if !iszero(h)
1411+
k1, u0, û0 = @views K0[1:nx], U0[1:nu], Û0[1:nu]
1412+
x0, xs = @views x̂0[1:nx], x̂0[nx+1:end]
1413+
mul!(û0, Cs_u, xs)
1414+
û0 .+= u0
1415+
model.f!(k1, x0, û0, d0, p)
1416+
lastk2 = k1
1417+
end
14041418
#TODO: allow parallel for loop or threads?
14051419
for j=1:Hp
1406-
u0 = @views U0[(1 + nu*(j-1)):(nu*j)]
1407-
û0 = @views Û0[(1 + nu*(j-1)):(nu*j)]
14081420
k0 = @views K0[(1 + nk*(j-1)):(nk*j)]
14091421
d0next = @views D̂0[(1 + nd*(j-1)):(nd*j)]
14101422
x̂0next = @views X̂0[(1 + nx̂*(j-1)):(nx̂*j)]
14111423
x̂0next_Z̃ = @views X̂0_Z̃[(1 + nx̂*(j-1)):(nx̂*j)]
14121424
ŝnext = @views geq[(1 + nx̂*(j-1)):(nx̂*j)]
1413-
xd, xs = @views x̂0[1:nx], x̂0[nx+1:end]
1414-
xdnext_Z̃, xsnext_Z̃ = @views x̂0next_Z̃[1:nx], x̂0next_Z̃[nx+1:end]
1425+
x0, xs = @views x̂0[1:nx], x̂0[nx+1:end]
1426+
x0next_Z̃, xsnext_Z̃ = @views x̂0next_Z̃[1:nx], x̂0next_Z̃[nx+1:end]
14151427
sdnext, ssnext = @views ŝnext[1:nx], ŝnext[nx+1:end]
1416-
mul!(û0, Cs_u, xs) # ys_u = Cs_u*xs
1417-
û0 .+= u0 # û0 = u0 + ys_u
1418-
k1, k2 = @views k0[1:nx], k0[nx+1:2*nx]
1419-
model.f!(k1, xd, û0, d0, p)
1420-
model.f!(k2, xdnext_Z̃, û0, d0next, p) # assuming ZOH on manipulated inputs u
1428+
k1, k2 = @views k0[1:nx], k0[nx+1:2*nx]
1429+
# ----------------- stochastic defects -----------------------------------------
14211430
xsnext = @views x̂0next[nx+1:end]
14221431
mul!(xsnext, As, xs)
1423-
sdnext .= @. xd - xdnext_Z̃ + (Ts/2)*(k1 + k2)
14241432
ssnext .= @. xsnext - xsnext_Z̃
1433+
# ----------------- deterministic defects --------------------------------------
1434+
if iszero(h) # piecewise constant manipulated inputs u:
1435+
u0 = @views U0[(1 + nu*(j-1)):(nu*j)]
1436+
û0 = @views Û0[(1 + nu*(j-1)):(nu*j)]
1437+
mul!(û0, Cs_u, xs) # ys_u(k) = Cs_u*xs(k)
1438+
û0 .+= u0 # û0(k) = u0(k) + ys_u(k)
1439+
model.f!(k1, x0, û0, d0, p)
1440+
model.f!(k2, x0next_Z̃, û0, d0next, p)
1441+
else # piecewise linear manipulated inputs u:
1442+
k1 .= lastk2
1443+
j == Hp && break # special case, treated after the loop
1444+
u0next = @views U0[(1 + nu*j):(nu*(j+1))]
1445+
û0next = @views Û0[(1 + nu*j):(nu*(j+1))]
1446+
mul!(û0next, Cs_u, xsnext_Z̃) # ys_u(k+1) = Cs_u*xs(k+1)
1447+
û0next .+= u0next # û0(k+1) = u0(k+1) + ys_u(k+1)
1448+
model.f!(k2, x0next_Z̃, û0next, d0next, p)
1449+
lastk2 = k2
1450+
end
1451+
sdnext .= @. x0 - x0next_Z̃ + 0.5*Ts*(k1 + k2)
14251452
x̂0 = x̂0next_Z̃ # using states in Z̃ for next iteration (allow parallel for)
14261453
d0 = d0next
14271454
end
1455+
if !iszero(h)
1456+
# j = Hp special case: u(k+Hp-1) = u(k+Hp) since Hc ≤ Hp implies Δu(k+Hp)=0
1457+
x̂0, x̂0next_Z̃ = @views X̂0_Z̃[end-2nx̂+1:end-nx̂], X̂0_Z̃[end-nx̂+1:end]
1458+
k1, k2 = @views K0[end-2nx+1:end-nx], K0[end-nx+1:end] # k1 already filled
1459+
d0next = @views D̂0[end-nd+1:end]
1460+
û0next, u0next = @views Û0[end-nu+1:end], U0[end-nu+1:end] # correspond to u(k+Hp-1)
1461+
x0, x0next_Z̃, xsnext_Z̃ = @views x̂0[1:nx], x̂0next_Z̃[1:nx], x̂0next_Z̃[nx+1:end]
1462+
sdnext = @views geq[end-nx̂+1:end-nx̂+nx] # ssnext already filled
1463+
mul!(û0next, Cs_u, xsnext_Z̃)
1464+
û0next .+= u0next
1465+
model.f!(k2, x0next_Z̃, û0next, d0next, p)
1466+
sdnext .= @. x0 - x0next_Z̃ + (Ts/2)*(k1 + k2)
1467+
end
14281468
return geq
14291469
end
14301470

test/3_test_predictive_control.jl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -728,6 +728,7 @@ end
728728
@test_throws ErrorException NonLinMPC(nonlinmodel, Hp=15, gc = (_,_,_,_)->[0.0], nc=1)
729729
@test_throws ErrorException NonLinMPC(nonlinmodel, Hp=15, gc! = (_,_,_,_)->[0.0], nc=1)
730730
@test_throws ArgumentError NonLinMPC(nonlinmodel, transcription=TrapezoidalCollocation())
731+
@test_throws ArgumentError NonLinMPC(nonlinmodel, transcription=TrapezoidalCollocation(2))
731732

732733
@test_logs (:warn, Regex(".*")) NonLinMPC(nonlinmodel, Hp=15, JE=(Ue,_,_,_)->Ue)
733734
@test_logs (:warn, Regex(".*")) NonLinMPC(nonlinmodel, Hp=15, gc=(Ue,_,_,_,_)->Ue, nc=0)
@@ -806,6 +807,10 @@ end
806807
preparestate!(nmpc5, [0.0])
807808
u = moveinput!(nmpc5, [1/0.001])
808809
@test u [1.0] atol=5e-2
810+
nmpc5_1 = NonLinMPC(nonlinmodel_c, Nwt=[0], Hp=100, Hc=1, transcription=TrapezoidalCollocation(1))
811+
preparestate!(nmpc5_1, [0.0])
812+
u = moveinput!(nmpc5_1, [1/0.001])
813+
@test u [1.0] atol=5e-2
809814
nmpc6 = NonLinMPC(linmodel3, Hp=10)
810815
preparestate!(nmpc6, [0])
811816
@test moveinput!(nmpc6, [0]) [0.0] atol=5e-2

0 commit comments

Comments
 (0)