Skip to content

Commit 2b1abf0

Browse files
authored
Merge pull request #160 from JuliaControl/type_stability_nonlinmodel
debug: revert allocation-free `linearize!` to avoid `NonLinModel` type-instability
2 parents 0ca6f85 + b022769 commit 2b1abf0

File tree

4 files changed

+35
-58
lines changed

4 files changed

+35
-58
lines changed

Project.toml

+1-1
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 = "1.3.2"
4+
version = "1.3.3"
55

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

src/model/linearization.jl

+17-38
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,3 @@
1-
2-
function jacobianA!(A, jb::JacobianBuffer, model::SimModel, x, u, d)
3-
jb.x .= x; jb.u .= u; jb.d .= d
4-
return ForwardDiff.jacobian!(A, jb.f_x!, jb.xnext, jb.x, jb.f_x_cfg)
5-
end
6-
jacobianA!( _ , _ , model::LinModel, _ , _ , _ ) = model.A
7-
function jacobianBu!(Bu, jb::JacobianBuffer, model::SimModel, x, u, d)
8-
jb.x .= x; jb.u .= u; jb.d .= d
9-
return ForwardDiff.jacobian!(Bu, jb.f_u!, jb.xnext, jb.u, jb.f_u_cfg)
10-
end
11-
jacobianBu!( _ , _ , model::LinModel, _ , _ , _ ) = model.Bu
12-
function jacobianBd!(Bd, jb::JacobianBuffer, model::SimModel, x, u, d)
13-
jb.x .= x; jb.u .= u; jb.d .= d
14-
return ForwardDiff.jacobian!(Bd, jb.f_d!, jb.xnext, jb.d, jb.f_d_cfg)
15-
end
16-
jacobianBd!( _ , _ , model::LinModel, _ , _ , _ ) = model.Bd
17-
function jacobianC!(C, jb::JacobianBuffer, model::SimModel, x, d)
18-
jb.x .= x; jb.d .= d
19-
return ForwardDiff.jacobian!(C, jb.h_x!, jb.y, jb.x, jb.h_x_cfg)
20-
end
21-
jacobianC!( _ , _ , model::LinModel, _ , _ ) = model.C
22-
function jacobianDd!(Dd, jb::JacobianBuffer, model::SimModel, x, d)
23-
jb.x .= x; jb.d .= d
24-
return ForwardDiff.jacobian!(Dd, jb.h_d!, jb.y, jb.d, jb.h_d_cfg)
25-
end
26-
jacobianDd!( _ , _ , model::LinModel, _ , _ ) = model.Dd
27-
28-
291
"""
302
LinModel(model::NonLinModel; x=model.x0+model.xop, u=model.uop, d=model.dop)
313
@@ -120,8 +92,8 @@ end
12092
12193
Linearize `model` and store the result in `linmodel` (in-place).
12294
123-
The keyword arguments are identical to [`linearize`](@ref). The code is allocation-free if
124-
`model` simulations does not allocate.
95+
The keyword arguments are identical to [`linearize`](@ref). The code allocates a small
96+
amount of memory to compute the Jacobians.
12597
12698
# Examples
12799
```jldoctest
@@ -137,8 +109,8 @@ julia> linearize!(linmodel, model, x=[20.0], u=[0.0]); linmodel.A
137109
```
138110
"""
139111
function linearize!(
140-
linmodel::LinModel, model::SimModel; x=model.x0+model.xop, u=model.uop, d=model.dop
141-
)
112+
linmodel::LinModel{NT}, model::SimModel; x=model.x0+model.xop, u=model.uop, d=model.dop
113+
) where NT<:Real
142114
nonlinmodel = model
143115
buffer = nonlinmodel.buffer
144116
# --- remove the operating points of the nonlinear model (typically zeros) ---
@@ -147,12 +119,19 @@ function linearize!(
147119
d0 .= d .- nonlinmodel.dop
148120
x0 .= x .- nonlinmodel.xop
149121
# --- compute the Jacobians at linearization points ---
150-
jb = nonlinmodel.buffer.jacobian
151-
jacobianA!(linmodel.A, jb, nonlinmodel, x0, u0, d0)
152-
jacobianBu!(linmodel.Bu, jb, nonlinmodel, x0, u0, d0)
153-
jacobianBd!(linmodel.Bd, jb, nonlinmodel, x0, u0, d0)
154-
jacobianC!(linmodel.C, jb, nonlinmodel, x0, d0)
155-
jacobianDd!(linmodel.Dd, jb, nonlinmodel, x0, d0)
122+
A::Matrix{NT}, Bu::Matrix{NT}, Bd::Matrix{NT} = linmodel.A, linmodel.Bu, linmodel.Bd
123+
C::Matrix{NT}, Dd::Matrix{NT} = linmodel.C, linmodel.Dd
124+
xnext0::Vector{NT}, y0::Vector{NT} = linmodel.buffer.x, linmodel.buffer.y
125+
myf_x0!(xnext0, x0) = f!(xnext0, nonlinmodel, x0, u0, d0, model.p)
126+
myf_u0!(xnext0, u0) = f!(xnext0, nonlinmodel, x0, u0, d0, model.p)
127+
myf_d0!(xnext0, d0) = f!(xnext0, nonlinmodel, x0, u0, d0, model.p)
128+
myh_x0!(y0, x0) = h!(y0, nonlinmodel, x0, d0, model.p)
129+
myh_d0!(y0, d0) = h!(y0, nonlinmodel, x0, d0, model.p)
130+
ForwardDiff.jacobian!(A, myf_x0!, xnext0, x0)
131+
ForwardDiff.jacobian!(Bu, myf_u0!, xnext0, u0)
132+
ForwardDiff.jacobian!(Bd, myf_d0!, xnext0, d0)
133+
ForwardDiff.jacobian!(C, myh_x0!, y0, x0)
134+
ForwardDiff.jacobian!(Dd, myh_d0!, y0, d0)
156135
# --- compute the nonlinear model output at operating points ---
157136
xnext0, y0 = linmodel.buffer.x, linmodel.buffer.y
158137
h!(y0, nonlinmodel, x0, d0, model.p)

src/model/nonlinmodel.jl

+7-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
struct NonLinModel{
2-
NT<:Real, F<:Function, H<:Function, P<:Any, DS<:DiffSolver, SMB<:SimModelBuffer
2+
NT<:Real, F<:Function, H<:Function, P<:Any, DS<:DiffSolver
33
} <: SimModel{NT}
44
x0::Vector{NT}
55
f!::F
@@ -21,10 +21,10 @@ struct NonLinModel{
2121
yname::Vector{String}
2222
dname::Vector{String}
2323
xname::Vector{String}
24-
buffer::SMB
24+
buffer::SimModelBuffer{NT}
2525
function NonLinModel{NT}(
26-
f!::F, h!::H, Ts, nu, nx, ny, nd, p::P, solver::DS, buffer::SMB
27-
) where {NT<:Real, F<:Function, H<:Function, P<:Any, DS<:DiffSolver, SMB<:SimModelBuffer}
26+
f!::F, h!::H, Ts, nu, nx, ny, nd, p::P, solver::DS
27+
) where {NT<:Real, F<:Function, H<:Function, P<:Any, DS<:DiffSolver}
2828
Ts > 0 || error("Sampling time Ts must be positive")
2929
uop = zeros(NT, nu)
3030
yop = zeros(NT, ny)
@@ -37,7 +37,8 @@ struct NonLinModel{
3737
xname = ["\$x_{$i}\$" for i in 1:nx]
3838
x0 = zeros(NT, nx)
3939
t = zeros(NT, 1)
40-
return new{NT, F, H, P, DS, SMB}(
40+
buffer = SimModelBuffer{NT}(nu, nx, ny, nd)
41+
return new{NT, F, H, P, DS}(
4142
x0,
4243
f!, h!,
4344
p,
@@ -144,9 +145,7 @@ function NonLinModel{NT}(
144145
isnothing(solver) && (solver=EmptySolver())
145146
f!, h! = get_mutating_functions(NT, f, h)
146147
f!, h! = get_solver_functions(NT, solver, f!, h!, Ts, nu, nx, ny, nd)
147-
jacobian = JacobianBuffer{NT}(f!, h!, nu, nx, ny, nd, p)
148-
buffer = SimModelBuffer{NT}(nu, nx, ny, nd, jacobian)
149-
return NonLinModel{NT}(f!, h!, Ts, nu, nx, ny, nd, p, solver, buffer)
148+
return NonLinModel{NT}(f!, h!, Ts, nu, nx, ny, nd, p, solver)
150149
end
151150

152151
function NonLinModel(

src/sim_model.jl

+10-11
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,9 @@ struct JacobianBuffer{
3434
CHX<:ForwardDiff.JacobianConfig
3535
}
3636
xnext::Vector{NT}
37-
u::Vector{NT}
3837
x::Vector{NT}
3938
y::Vector{NT}
39+
u::Vector{NT}
4040
d::Vector{NT}
4141
f_x!::FX
4242
f_u!::FU
@@ -48,34 +48,33 @@ struct JacobianBuffer{
4848
f_d_cfg::CFD
4949
h_x_cfg::CHX
5050
h_d_cfg::CHU
51-
5251
end
5352

5453
"""
55-
JacobianBuffer(NT::DataType, f!::Function, h!::Function, nu, nx, ny, nd)
54+
JacobianBuffer(NT::DataType, f!::Function, h!::Function, nx, nu, ny, nd)
5655
5756
Buffer object for calling `ForwardDiff.jacobian!` on `SimModel` without any allocation.
5857
"""
5958
function JacobianBuffer{NT}(
60-
f!::Function, h!::Function, nu::Int, nx::Int, ny::Int, nd::Int, p
59+
f!::Function, h!::Function, nx::Int, nu::Int, ny::Int, nd::Int
6160
) where NT <: Real
6261
xnext = Vector{NT}(undef, nx)
63-
u = Vector{NT}(undef, nu)
6462
x = Vector{NT}(undef, nx)
6563
y = Vector{NT}(undef, ny)
64+
u = Vector{NT}(undef, nu)
6665
d = Vector{NT}(undef, nd)
67-
f_x!(xnext, x) = f!(xnext, x, u, d, p)
68-
f_u!(xnext, u) = f!(xnext, x, u, d, p)
69-
f_d!(xnext, d) = f!(xnext, x, u, d, p)
70-
h_x!(y, x) = h!(y, x, d, p)
71-
h_d!(y, d) = h!(y, x, d, p)
66+
f_x!(y, x) = f!(y, x, u, d)
67+
f_u!(y, u) = f!(y, x, u, d)
68+
f_d!(y, d) = f!(y, x, u, d)
69+
h_x!(y, x) = h!(y, x, d)
70+
h_d!(y, d) = h!(y, x, d)
7271
f_x_cfg = ForwardDiff.JacobianConfig(f_x!, xnext, x)
7372
f_u_cfg = ForwardDiff.JacobianConfig(f_u!, xnext, u)
7473
f_d_cfg = ForwardDiff.JacobianConfig(f_d!, xnext, d)
7574
h_x_cfg = ForwardDiff.JacobianConfig(h_x!, y, x)
7675
h_d_cfg = ForwardDiff.JacobianConfig(h_d!, y, d)
7776
return JacobianBuffer(
78-
xnext, u, x, y, d,
77+
xnext, x, y, u, d,
7978
f_x!, f_u!, f_d!, h_x!, h_d!,
8079
f_x_cfg, f_u_cfg, f_d_cfg, h_x_cfg, h_d_cfg
8180
)

0 commit comments

Comments
 (0)