Skip to content

Commit d7b3bfb

Browse files
authored
Merge pull request #2121 from SciML/fb/symlin2
Extend symbolic linearization to DAEs
2 parents 562ba6a + 3bdb008 commit d7b3bfb

File tree

2 files changed

+76
-10
lines changed

2 files changed

+76
-10
lines changed

src/systems/abstractsystem.jl

Lines changed: 62 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1225,7 +1225,7 @@ y &= h(x, z, u)
12251225
\\end{aligned}
12261226
```
12271227
1228-
where `x` are differential states, `z` algebraic states, `u` inputs and `y` outputs. To obtain a linear statespace representation, see [`linearize`](@ref). The input argument `variables` is a vector defining the operating point, corresponding to `states(simplified_sys)` and `p` is a vector corresponding to the parameters of `simplified_sys`. Note: all variables in `inputs` have been converted to parameters in `simplified_sys`.
1228+
where `x` are differential state variables, `z` algebraic variables, `u` inputs and `y` outputs. To obtain a linear statespace representation, see [`linearize`](@ref). The input argument `variables` is a vector defining the operating point, corresponding to `states(simplified_sys)` and `p` is a vector corresponding to the parameters of `simplified_sys`. Note: all variables in `inputs` have been converted to parameters in `simplified_sys`.
12291229
12301230
The `simplified_sys` has undergone [`structural_simplify`](@ref) and had any occurring input or output variables replaced with the variables provided in arguments `inputs` and `outputs`. The states of this system also indicate the order of the states that holds for the linearized matrices.
12311231
@@ -1288,14 +1288,25 @@ function linearization_function(sys::AbstractSystem, inputs,
12881288
end
12891289

12901290
"""
1291-
(; A, B, C, D), simplified_sys = linearize_symbolic(sys::AbstractSystem, inputs, outputs; simplify = false, kwargs)
1291+
(; A, B, C, D), simplified_sys = linearize_symbolic(sys::AbstractSystem, inputs, outputs; simplify = false, allow_input_derivatives = false, kwargs...)
12921292
12931293
Similar to [`linearize`](@ref), but returns symbolic matrices `A,B,C,D` rather than numeric. While `linearize` uses ForwardDiff to perform the linearization, this function uses `Symbolics.jacobian`.
12941294
12951295
See [`linearize`](@ref) for a description of the arguments.
1296+
1297+
# Extended help
1298+
The named tuple returned as the first argument additionally contains the jacobians `f_x, f_z, g_x, g_z, f_u, g_u, h_x, h_z, h_u` of
1299+
```math
1300+
\\begin{aligned}
1301+
ẋ &= f(x, z, u) \\\\
1302+
0 &= g(x, z, u) \\\\
1303+
y &= h(x, z, u)
1304+
\\end{aligned}
1305+
```
1306+
where `x` are differential state variables, `z` algebraic variables, `u` inputs and `y` outputs.
12961307
"""
12971308
function linearize_symbolic(sys::AbstractSystem, inputs,
1298-
outputs; simplify = false,
1309+
outputs; simplify = false, allow_input_derivatives = false,
12991310
kwargs...)
13001311
sys, diff_idxs, alge_idxs, input_idxs = io_preprocessing(sys, inputs, outputs; simplify,
13011312
kwargs...)
@@ -1306,14 +1317,56 @@ function linearize_symbolic(sys::AbstractSystem, inputs,
13061317
fun = generate_function(sys, sts, p; expression = Val{false})[1]
13071318
dx = fun(sts, p, t)
13081319

1309-
A = Symbolics.jacobian(dx, sts)
1310-
B = Symbolics.jacobian(dx, inputs)
1311-
13121320
h = build_explicit_observed_function(sys, outputs)
13131321
y = h(sts, p, t)
1314-
C = Symbolics.jacobian(y, sts)
1315-
D = Symbolics.jacobian(y, inputs)
1316-
(; A, B, C, D), sys
1322+
1323+
fg_xz = Symbolics.jacobian(dx, sts)
1324+
fg_u = Symbolics.jacobian(dx, inputs)
1325+
h_xz = Symbolics.jacobian(y, sts)
1326+
h_u = Symbolics.jacobian(y, inputs)
1327+
f_x = fg_xz[diff_idxs, diff_idxs]
1328+
f_z = fg_xz[diff_idxs, alge_idxs]
1329+
g_x = fg_xz[alge_idxs, diff_idxs]
1330+
g_z = fg_xz[alge_idxs, alge_idxs]
1331+
f_u = fg_u[diff_idxs, :]
1332+
g_u = fg_u[alge_idxs, :]
1333+
h_x = h_xz[:, diff_idxs]
1334+
h_z = h_xz[:, alge_idxs]
1335+
1336+
nx, nu = size(f_u)
1337+
nz = size(f_z, 2)
1338+
ny = size(h_x, 1)
1339+
1340+
D = h_u
1341+
1342+
if isempty(g_z) # ODE
1343+
A = f_x
1344+
B = f_u
1345+
C = h_x
1346+
else
1347+
gz = lu(g_z; check = false)
1348+
issuccess(gz) ||
1349+
error("g_z not invertible, this indicates that the DAE is of index > 1.")
1350+
gzgx = -(gz \ g_x)
1351+
A = [f_x f_z
1352+
gzgx*f_x gzgx*f_z]
1353+
B = [f_u
1354+
gzgx * f_u] # The cited paper has zeros in the bottom block, see derivation in https://github.com/SciML/ModelingToolkit.jl/pull/1691 for the correct formula
1355+
1356+
C = [h_x h_z]
1357+
Bs = -(gz \ g_u) # This equation differ from the cited paper, the paper is likely wrong since their equaiton leads to a dimension mismatch.
1358+
if !iszero(Bs)
1359+
if !allow_input_derivatives
1360+
der_inds = findall(vec(any(!iszero, Bs, dims = 1)))
1361+
@show typeof(der_inds)
1362+
error("Input derivatives appeared in expressions (-g_z\\g_u != 0), the following inputs appeared differentiated: $(ModelingToolkit.inputs(sys)[der_inds]). Call `linear_statespace` with keyword argument `allow_input_derivatives = true` to allow this and have the returned `B` matrix be of double width ($(2nu)), where the last $nu inputs are the derivatives of the first $nu inputs.")
1363+
end
1364+
B = [B [zeros(nx, nu); Bs]]
1365+
D = [D zeros(ny, nu)]
1366+
end
1367+
end
1368+
1369+
(; A, B, C, D, f_x, f_z, g_x, g_z, f_u, g_u, h_x, h_z, h_u), sys
13171370
end
13181371

13191372
function markio!(state, orig_inputs, inputs, outputs; check = true)

test/linearize.jl

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ if VERSION >= v"1.8"
205205
connect(cart.flange, force.flange)
206206
connect(link1.TY1, fixed.flange)]
207207

208-
@show @named model = ODESystem(eqs, t, [], []; systems = [link1, cart, force, fixed])
208+
@named model = ODESystem(eqs, t, [], []; systems = [link1, cart, force, fixed])
209209
def = ModelingToolkit.defaults(model)
210210
def[link1.y1] = 0
211211
def[link1.x1] = 10
@@ -227,4 +227,17 @@ if VERSION >= v"1.8"
227227

228228
@test minimum(abs, ps) < 1e-6
229229
@test minimum(abs, complex(0, 1.3777260367206716) .- ps) < 1e-10
230+
231+
lsys, syss = linearize(model, lin_inputs, lin_outputs, allow_symbolic = true, op = def,
232+
allow_input_derivatives = true, zero_dummy_der = true)
233+
lsyss, sysss = ModelingToolkit.linearize_symbolic(model, lin_inputs, lin_outputs;
234+
allow_input_derivatives = true)
235+
236+
dummyder = setdiff(states(sysss), states(model))
237+
def = merge(def, Dict(x => 0.0 for x in dummyder))
238+
239+
@test substitute(lsyss.A, def) lsys.A
240+
@test substitute(lsyss.B, def) lsys.B
241+
@test substitute(lsyss.C, def) lsys.C
242+
@test substitute(lsyss.D, def) lsys.D
230243
end

0 commit comments

Comments
 (0)