Skip to content

Commit 562ba6a

Browse files
authored
Merge pull request #2119 from SciML/fb/symlin
add symbolic linearization function
2 parents db52605 + b27da2e commit 562ba6a

File tree

2 files changed

+62
-7
lines changed

2 files changed

+62
-7
lines changed

src/systems/abstractsystem.jl

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1287,6 +1287,35 @@ function linearization_function(sys::AbstractSystem, inputs,
12871287
return lin_fun, sys
12881288
end
12891289

1290+
"""
1291+
(; A, B, C, D), simplified_sys = linearize_symbolic(sys::AbstractSystem, inputs, outputs; simplify = false, kwargs)
1292+
1293+
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`.
1294+
1295+
See [`linearize`](@ref) for a description of the arguments.
1296+
"""
1297+
function linearize_symbolic(sys::AbstractSystem, inputs,
1298+
outputs; simplify = false,
1299+
kwargs...)
1300+
sys, diff_idxs, alge_idxs, input_idxs = io_preprocessing(sys, inputs, outputs; simplify,
1301+
kwargs...)
1302+
sts = states(sys)
1303+
t = get_iv(sys)
1304+
p = parameters(sys)
1305+
1306+
fun = generate_function(sys, sts, p; expression = Val{false})[1]
1307+
dx = fun(sts, p, t)
1308+
1309+
A = Symbolics.jacobian(dx, sts)
1310+
B = Symbolics.jacobian(dx, inputs)
1311+
1312+
h = build_explicit_observed_function(sys, outputs)
1313+
y = h(sts, p, t)
1314+
C = Symbolics.jacobian(y, sts)
1315+
D = Symbolics.jacobian(y, inputs)
1316+
(; A, B, C, D), sys
1317+
end
1318+
12901319
function markio!(state, orig_inputs, inputs, outputs; check = true)
12911320
fullvars = state.fullvars
12921321
inputset = Dict{Any, Bool}(i => false for i in inputs)
@@ -1343,7 +1372,7 @@ the default values of `sys` are used.
13431372
13441373
If `allow_input_derivatives = false`, an error will be thrown if input derivatives (``u̇``) appear as inputs in the linearized equations. If input derivatives are allowed, the returned `B` matrix will be of double width, corresponding to the input `[u; u̇]`.
13451374
1346-
See also [`linearization_function`](@ref) which provides a lower-level interface, and [`ModelingToolkit.reorder_states`](@ref).
1375+
See also [`linearization_function`](@ref) which provides a lower-level interface, [`linearize_symbolic`](@ref) and [`ModelingToolkit.reorder_states`](@ref).
13471376
13481377
See extended help for an example.
13491378
@@ -1405,14 +1434,19 @@ connections = [f.y ~ c.r # filtered reference to controller reference
14051434
14061435
@named cl = ODESystem(connections, t, systems = [f, c, p])
14071436
1408-
lsys, ssys = linearize(cl, [f.u], [p.x])
1437+
lsys0, ssys = linearize(cl, [f.u], [p.x])
14091438
desired_order = [f.x, p.x]
1410-
lsys = ModelingToolkit.reorder_states(lsys, states(ssys), desired_order)
1439+
lsys = ModelingToolkit.reorder_states(lsys0, states(ssys), desired_order)
14111440
14121441
@assert lsys.A == [-2 0; 1 -2]
14131442
@assert lsys.B == [1; 0;;]
14141443
@assert lsys.C == [0 1]
14151444
@assert lsys.D[] == 0
1445+
1446+
## Symbolic linearization
1447+
lsys_sym, _ = ModelingToolkit.linearize_symbolic(cl, [f.u], [p.x])
1448+
1449+
@assert substitute(lsys_sym.A, ModelingToolkit.defaults(cl)) == lsys.A
14161450
```
14171451
"""
14181452
function linearize(sys, lin_fun; t = 0.0, op = Dict(), allow_input_derivatives = false,

test/linearize.jl

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,15 +68,22 @@ connections = [f.y ~ c.r # filtered reference to controller reference
6868

6969
@named cl = ODESystem(connections, t, systems = [f, c, p])
7070

71-
lsys, ssys = linearize(cl, [f.u], [p.x])
71+
lsys0, ssys = linearize(cl, [f.u], [p.x])
7272
desired_order = [f.x, p.x]
73-
lsys = ModelingToolkit.reorder_states(lsys, states(ssys), desired_order)
73+
lsys = ModelingToolkit.reorder_states(lsys0, states(ssys), desired_order)
7474

7575
@test lsys.A == [-2 0; 1 -2]
7676
@test lsys.B == reshape([1, 0], 2, 1)
7777
@test lsys.C == [0 1]
7878
@test lsys.D[] == 0
7979

80+
## Symbolic linearization
81+
lsyss, _ = ModelingToolkit.linearize_symbolic(cl, [f.u], [p.x])
82+
83+
@test substitute(lsyss.A, ModelingToolkit.defaults(cl)) == lsys.A
84+
@test substitute(lsyss.B, ModelingToolkit.defaults(cl)) == lsys.B
85+
@test substitute(lsyss.C, ModelingToolkit.defaults(cl)) == lsys.C
86+
@test substitute(lsyss.D, ModelingToolkit.defaults(cl)) == lsys.D
8087
##
8188
using ModelingToolkitStandardLibrary.Blocks: LimPID
8289
k = 400
@@ -86,16 +93,24 @@ Nd = 10
8693
@named pid = LimPID(; k, Ti, Td, Nd)
8794

8895
@unpack reference, measurement, ctr_output = pid
89-
lsys, ssys = linearize(pid, [reference.u, measurement.u], [ctr_output.u])
96+
lsys0, ssys = linearize(pid, [reference.u, measurement.u], [ctr_output.u])
9097
@unpack int, der = pid
9198
desired_order = [int.x, der.x]
92-
lsys = ModelingToolkit.reorder_states(lsys, states(ssys), desired_order)
99+
lsys = ModelingToolkit.reorder_states(lsys0, states(ssys), desired_order)
93100

94101
@test lsys.A == [0 0; 0 -10]
95102
@test lsys.B == [2 -2; 10 -10]
96103
@test lsys.C == [400 -4000]
97104
@test lsys.D == [4400 -4400]
98105

106+
lsyss, _ = ModelingToolkit.linearize_symbolic(pid, [reference.u, measurement.u],
107+
[ctr_output.u])
108+
109+
@test substitute(lsyss.A, ModelingToolkit.defaults(pid)) == lsys.A
110+
@test substitute(lsyss.B, ModelingToolkit.defaults(pid)) == lsys.B
111+
@test substitute(lsyss.C, ModelingToolkit.defaults(pid)) == lsys.C
112+
@test substitute(lsyss.D, ModelingToolkit.defaults(pid)) == lsys.D
113+
99114
# Test with the reverse desired state order as well to verify that similarity transform and reoreder_states really works
100115
lsys = ModelingToolkit.reorder_states(lsys, states(ssys), reverse(desired_order))
101116

@@ -151,6 +166,12 @@ lsys, ssys = linearize(sat, [u], [y])
151166
@test isempty(lsys.C)
152167
@test lsys.D[] == 1
153168

169+
@test_skip lsyss, _ = ModelingToolkit.linearize_symbolic(sat, [u], [y]) # Code gen replaces ifelse with if statements causing symbolic evaluation to fail
170+
# @test substitute(lsyss.A, ModelingToolkit.defaults(sat)) == lsys.A
171+
# @test substitute(lsyss.B, ModelingToolkit.defaults(sat)) == lsys.B
172+
# @test substitute(lsyss.C, ModelingToolkit.defaults(sat)) == lsys.C
173+
# @test substitute(lsyss.D, ModelingToolkit.defaults(sat)) == lsys.D
174+
154175
# outside the linear region the derivative is 0
155176
lsys, ssys = linearize(sat, [u], [y]; op = Dict(u => 2))
156177
@test isempty(lsys.A) # there are no differential variables in this system

0 commit comments

Comments
 (0)