Skip to content

Commit 5877270

Browse files
committed
added: linearize! now uses DI.jl
The code is wayyyyy simpler, custom linearization buffer `struct` is no longer needed.
1 parent 8758fe9 commit 5877270

File tree

4 files changed

+65
-115
lines changed

4 files changed

+65
-115
lines changed

src/general.jl

+4-25
Original file line numberDiff line numberDiff line change
@@ -6,31 +6,6 @@ const DEFAULT_LWT = 0.0
66
const DEFAULT_CWT = 1e5
77
const DEFAULT_EWT = 0.0
88

9-
"Abstract type for all differentiation buffers."
10-
abstract type DifferentiationBuffer end
11-
12-
function Base.show(io::IO, buffer::DifferentiationBuffer)
13-
return print(io, "DifferentiationBuffer with a $(typeof(buffer.config).name.name)")
14-
end
15-
16-
"Struct with both function and configuration for ForwardDiff Jacobian."
17-
struct JacobianBuffer{FT<:Function, CT<:ForwardDiff.JacobianConfig} <: DifferentiationBuffer
18-
f!::FT
19-
config::CT
20-
end
21-
22-
"Create a JacobianBuffer with in-place function `f!`, output `y` and input `x`."
23-
JacobianBuffer(f!, y, x) = JacobianBuffer(f!, ForwardDiff.JacobianConfig(f!, y, x))
24-
25-
"Compute in-place and return the Jacobian matrix of `buffer.f!` at `x`."
26-
function get_jacobian!(A, buffer::JacobianBuffer, y, x)
27-
return ForwardDiff.jacobian!(A, buffer.f!, y, x, buffer.config)
28-
end
29-
30-
"Init a differentiation result matrix as dense or sparse matrix, as required by `backend`."
31-
init_diffmat(T, backend::AbstractADType, _ , nx , ny) = Matrix{T}(undef, ny, nx)
32-
init_diffmat(T, backend::AutoSparse ,prep , _ , _ ) = similar(sparsity_pattern(prep), T)
33-
349
"Termination status that means 'no solution available'."
3510
const ERROR_STATUSES = (
3611
JuMP.INFEASIBLE, JuMP.DUAL_INFEASIBLE, JuMP.LOCALLY_INFEASIBLE,
@@ -81,6 +56,10 @@ function limit_solve_time(optim::GenericModel, Ts)
8156
end
8257
end
8358

59+
"Init a differentiation result matrix as dense or sparse matrix, as required by `backend`."
60+
init_diffmat(T, backend::AbstractADType, _ , nx , ny) = Matrix{T}(undef, ny, nx)
61+
init_diffmat(T, backend::AutoSparse ,prep , _ , _ ) = similar(sparsity_pattern(prep), T)
62+
8463
"Verify that x and y elements are different using `!==`."
8564
isdifferent(x, y) = any(xi !== yi for (xi, yi) in zip(x, y))
8665

src/model/linearization.jl

+52-79
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,49 @@
1-
"A linearization buffer for the [`linearize`](@ref) function."
2-
struct LinearizationBuffer{
3-
NT <:Real,
4-
JB_FUD <:JacobianBuffer,
5-
JB_FXD <:JacobianBuffer,
6-
JB_FXU <:JacobianBuffer,
7-
JB_HD <:JacobianBuffer,
8-
JB_HX <:JacobianBuffer
9-
}
10-
x::Vector{NT}
11-
u::Vector{NT}
12-
d::Vector{NT}
13-
buffer_f_at_u_d::JB_FUD
14-
buffer_f_at_x_d::JB_FXD
15-
buffer_f_at_x_u::JB_FXU
16-
buffer_h_at_d ::JB_HD
17-
buffer_h_at_x ::JB_HX
18-
function LinearizationBuffer(
19-
x::Vector{NT},
20-
u::Vector{NT},
21-
d::Vector{NT},
22-
buffer_f_at_u_d::JB_FUD,
23-
buffer_f_at_x_d::JB_FXD,
24-
buffer_f_at_x_u::JB_FXU,
25-
buffer_h_at_d::JB_HD,
26-
buffer_h_at_x::JB_HX
27-
) where {NT<:Real, JB_FUD, JB_FXD, JB_FXU, JB_HD, JB_HX}
28-
return new{NT, JB_FUD, JB_FXD, JB_FXU, JB_HD, JB_HX}(
29-
x, u, d,
30-
buffer_f_at_u_d,
31-
buffer_f_at_x_d,
32-
buffer_f_at_x_u,
33-
buffer_h_at_d,
34-
buffer_h_at_x
35-
)
36-
end
37-
38-
end
1+
"""
2+
get_linearization_func(NT, f!, h!, nu, nx, ny, nd, p) -> linfunc!
393
40-
Base.show(io::IO, buffer::LinearizationBuffer) = print(io, "LinearizationBuffer object")
41-
42-
function LinearizationBuffer(NT, f!, h!, nu, nx, ny, nd, p)
43-
x, u, d, f_at_u_d!, f_at_x_d!, f_at_x_u!, h_at_d!, h_at_x! = get_linearize_funcs(
44-
NT, f!, h!, nu, nx, ny, nd, p
45-
)
46-
xnext, y = Vector{NT}(undef, nx), Vector{NT}(undef, ny) # TODO: to replace ?
47-
return LinearizationBuffer(
48-
x, u, d,
49-
JacobianBuffer(f_at_u_d!, xnext, x),
50-
JacobianBuffer(f_at_x_d!, xnext, u),
51-
JacobianBuffer(f_at_x_u!, xnext, d),
52-
JacobianBuffer(h_at_d!, y, x),
53-
JacobianBuffer(h_at_x!, y, d)
54-
)
55-
end
4+
Return the `linfunc!` function that computes the Jacobians of `f!` and `h!` functions.
565
57-
"Get the linearization functions for [`NonLinModel`](@ref) `f!` and `h!` functions."
58-
function get_linearize_funcs(NT, f!, h!, nu, nx, ny, nd, p)
59-
x = Vector{NT}(undef, nx)
60-
u = Vector{NT}(undef, nu)
61-
d = Vector{NT}(undef, nd)
62-
f_at_u_d!(xnext, x) = f!(xnext, x, u, d, p)
63-
f_at_x_d!(xnext, u) = f!(xnext, x, u, d, p)
64-
f_at_x_u!(xnext, d) = f!(xnext, x, u, d, p)
65-
h_at_d!(y, x) = h!(y, x, d, p)
66-
h_at_x!(y, d) = h!(y, x, d, p)
67-
return x, u, d, f_at_u_d!, f_at_x_d!, f_at_x_u!, h_at_d!, h_at_x!
6+
The function has the following signature:
7+
```
8+
linfunc!(xnext, y, A, Bu, C, Bd, Dd, x, u, d, cst_x, cst_u, cst_d) -> nothing
9+
```
10+
and it should modifies in-place all the arguments before `x`. The `cst_x`, `cst_u`, `cst_d`
11+
and are `DifferentiationInterface.Constant` objects with the linearization points.
12+
"""
13+
function get_linearization_func(NT, f!, h!, nu, nx, ny, nd, p)
14+
f_x!(xnext, x, u, d) = f!(xnext, x, u, d, p)
15+
f_u!(xnext, u, x, d) = f!(xnext, x, u, d, p)
16+
f_d!(xnext, d, x, u) = f!(xnext, x, u, d, p)
17+
h_x!(y, x, d) = h!(y, x, d, p)
18+
h_d!(y, d, x) = h!(y, x, d, p)
19+
backend = AutoForwardDiff()
20+
strict = Val(true)
21+
xnext = zeros(NT, nx)
22+
y = zeros(NT, ny)
23+
x = zeros(NT, nx)
24+
u = zeros(NT, nu)
25+
d = zeros(NT, nd)
26+
cst_x = Constant(x)
27+
cst_u = Constant(u)
28+
cst_d = Constant(d)
29+
A_prep = prepare_jacobian(f_x!, xnext, backend, x, cst_u, cst_d; strict)
30+
Bu_prep = prepare_jacobian(f_u!, xnext, backend, u, cst_x, cst_d; strict)
31+
Bd_prep = prepare_jacobian(f_d!, xnext, backend, d, cst_x, cst_u; strict)
32+
C_prep = prepare_jacobian(h_x!, y, backend, x, cst_d ; strict)
33+
Dd_prep = prepare_jacobian(h_d!, y, backend, d, cst_x ; strict)
34+
function linfunc!(xnext, y, A, Bu, C, Bd, Dd, x, u, d, cst_x, cst_u, cst_d)
35+
# all the arguments before `x` are mutated in this function
36+
jacobian!(f_x!, xnext, A, A_prep, backend, x, cst_u, cst_d)
37+
jacobian!(f_u!, xnext, Bu, Bu_prep, backend, u, cst_x, cst_d)
38+
jacobian!(f_d!, xnext, Bd, Bd_prep, backend, d, cst_x, cst_u)
39+
jacobian!(h_x!, y, C, C_prep, backend, x, cst_d)
40+
jacobian!(h_d!, y, Dd, Dd_prep, backend, d, cst_x)
41+
return nothing
42+
end
43+
return linfunc!
6844
end
6945

46+
7047
@doc raw"""
7148
linearize(model::SimModel; x=model.x0+model.xop, u=model.uop, d=model.dop) -> linmodel
7249
@@ -181,8 +158,7 @@ function linearize!(
181158
u0 .= u .- nonlinmodel.uop
182159
d0 .= d .- nonlinmodel.dop
183160
# --- compute the Jacobians at linearization points ---
184-
xnext0::Vector{NT}, y0::Vector{NT} = linmodel.buffer.x, linmodel.buffer.y
185-
get_jacobians!(linmodel, xnext0, y0, nonlinmodel, x0, u0, d0)
161+
linearize_core!(linmodel, nonlinmodel, x0, u0, d0)
186162
# --- compute the nonlinear model output at operating points ---
187163
xnext0, y0 = linmodel.buffer.x, linmodel.buffer.y
188164
h!(y0, nonlinmodel, x0, d0, model.p)
@@ -203,22 +179,19 @@ function linearize!(
203179
return linmodel
204180
end
205181

206-
"Compute the 5 Jacobians of `model` at the linearization point and write them in `linmodel`."
207-
function get_jacobians!(linmodel::LinModel, xnext0, y0, model::SimModel, x0, u0, d0)
208-
linbuffer = model.linbuffer # a LinearizationBuffer object
209-
linbuffer.x .= x0
210-
linbuffer.u .= u0
211-
linbuffer.d .= d0
212-
get_jacobian!(linmodel.A, linbuffer.buffer_f_at_u_d, xnext0, x0)
213-
get_jacobian!(linmodel.Bu, linbuffer.buffer_f_at_x_d, xnext0, u0)
214-
get_jacobian!(linmodel.Bd, linbuffer.buffer_f_at_x_u, xnext0, d0)
215-
get_jacobian!(linmodel.C, linbuffer.buffer_h_at_d, y0, x0)
216-
get_jacobian!(linmodel.Dd, linbuffer.buffer_h_at_x, y0, d0)
182+
"Call `linfunc!` function to compute the Jacobians of `model` at the linearization point."
183+
function linearize_core!(linmodel::LinModel, model::SimModel, x0, u0, d0)
184+
xnext0, y0 = linmodel.buffer.x, linmodel.buffer.y
185+
A, Bu, C, Bd, Dd = linmodel.A, linmodel.Bu, linmodel.C, linmodel.Bd, linmodel.Dd
186+
cx = Constant(x0)
187+
cu = Constant(u0)
188+
cd = Constant(d0)
189+
model.linfunc!(xnext0, y0, A, Bu, C, Bd, Dd, x0, u0, d0, cx, cu, cd)
217190
return nothing
218191
end
219192

220193
"Copy the state-space matrices of `model` to `linmodel` if `model` is already linear."
221-
function get_jacobians!(linmodel::LinModel, _ , _ , model::LinModel, _ , _ , _)
194+
function linearize_core!(linmodel::LinModel, model::LinModel, _ , _ , _ )
222195
linmodel.A .= model.A
223196
linmodel.Bu .= model.Bu
224197
linmodel.C .= model.C

src/model/nonlinmodel.jl

+8-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
struct NonLinModel{
2-
NT<:Real, F<:Function, H<:Function, PT<:Any, DS<:DiffSolver, LB<:LinearizationBuffer
2+
NT<:Real, F<:Function, H<:Function, PT<:Any, DS<:DiffSolver, LF<:Function
33
} <: SimModel{NT}
44
x0::Vector{NT}
55
f!::F
@@ -21,11 +21,11 @@ struct NonLinModel{
2121
yname::Vector{String}
2222
dname::Vector{String}
2323
xname::Vector{String}
24-
linbuffer::LB
24+
linfunc!::LF
2525
buffer::SimModelBuffer{NT}
2626
function NonLinModel{NT}(
27-
f!::F, h!::H, Ts, nu, nx, ny, nd, p::PT, solver::DS, linbuffer::LB
28-
) where {NT<:Real, F<:Function, H<:Function, PT<:Any, DS<:DiffSolver, LB<:LinearizationBuffer}
27+
f!::F, h!::H, Ts, nu, nx, ny, nd, p::PT, solver::DS, linfunc!::LF
28+
) where {NT<:Real, F<:Function, H<:Function, PT<:Any, DS<:DiffSolver, LF<:Function}
2929
Ts > 0 || error("Sampling time Ts must be positive")
3030
uop = zeros(NT, nu)
3131
yop = zeros(NT, ny)
@@ -39,7 +39,7 @@ struct NonLinModel{
3939
x0 = zeros(NT, nx)
4040
t = zeros(NT, 1)
4141
buffer = SimModelBuffer{NT}(nu, nx, ny, nd)
42-
return new{NT, F, H, PT, DS, LB}(
42+
return new{NT, F, H, PT, DS, LF}(
4343
x0,
4444
f!, h!,
4545
p,
@@ -48,7 +48,7 @@ struct NonLinModel{
4848
nu, nx, ny, nd,
4949
uop, yop, dop, xop, fop,
5050
uname, yname, dname, xname,
51-
linbuffer,
51+
linfunc!,
5252
buffer
5353
)
5454
end
@@ -148,8 +148,8 @@ function NonLinModel{NT}(
148148
isnothing(solver) && (solver=EmptySolver())
149149
f!, h! = get_mutating_functions(NT, f, h)
150150
f!, h! = get_solver_functions(NT, solver, f!, h!, Ts, nu, nx, ny, nd)
151-
linbuffer = LinearizationBuffer(NT, f!, h!, nu, nx, ny, nd, p)
152-
return NonLinModel{NT}(f!, h!, Ts, nu, nx, ny, nd, p, solver, linbuffer)
151+
linfunc! = get_linearization_func(NT, f!, h!, nu, nx, ny, nd, p)
152+
return NonLinModel{NT}(f!, h!, Ts, nu, nx, ny, nd, p, solver, linfunc!)
153153
end
154154

155155
function NonLinModel(

test/1_test_sim_model.jl

+1-3
Original file line numberDiff line numberDiff line change
@@ -297,9 +297,7 @@ end
297297
@test linmodel1.Bu linmodel2.Bu
298298
@test linmodel1.Bd linmodel2.Bd
299299
@test linmodel1.C linmodel2.C
300-
@test linmodel1.Dd linmodel2.Dd
301-
@test repr(nonlinmodel1.linbuffer) == "LinearizationBuffer object"
302-
@test repr(nonlinmodel1.linbuffer.buffer_f_at_u_d) == "DifferentiationBuffer with a JacobianConfig"
300+
@test linmodel1.Dd linmodel2.Dd
303301

304302
f1!(ẋ, x, u, d, _) = (ẋ .= x.^5 .+ u.^4 .+ d.^3; nothing)
305303
h1!(y, x, d, _) = (y .= x.^2 .+ d; nothing)

0 commit comments

Comments
 (0)