Skip to content

Commit 9b676bd

Browse files
committed
refactor: require simplify system for linearization
1 parent da60fbf commit 9b676bd

File tree

3 files changed

+35
-52
lines changed

3 files changed

+35
-52
lines changed

docs/src/basics/Linearization.md

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ y &= Cx + Du
1515
\end{aligned}
1616
```
1717

18-
The `linearize` function expects the user to specify the inputs ``u`` and the outputs ``y`` using the syntax shown in the example below. The system model is *not* supposed to be simplified before calling `linearize`:
18+
The `linearize` function expects the user to specify the inputs ``u`` and the outputs ``y`` using the syntax shown in the example below.
1919

2020
## Example
2121

@@ -29,7 +29,7 @@ eqs = [u ~ kp * (r - y) # P controller
2929
D(x) ~ -x + u # First-order plant
3030
y ~ x] # Output equation
3131
32-
@named sys = ODESystem(eqs, t) # Do not call @mtkbuild when linearizing
32+
@mtkbuild sys = ODESystem(eqs, t) # Do not call @mtkbuild when linearizing
3333
matrices, simplified_sys = linearize(sys, [r], [y]) # Linearize from r to y
3434
matrices
3535
```
@@ -45,10 +45,6 @@ using ModelingToolkit: inputs, outputs
4545

4646
The model above has 4 variables but only three equations, there is no equation specifying the value of `r` since `r` is an input. This means that only unbalanced models can be linearized, or in other words, models that are balanced and can be simulated _cannot_ be linearized. To learn more about this, see [How to linearize a ModelingToolkit model (YouTube)](https://www.youtube.com/watch?v=-XOux-2XDGI&t=395s). Also see [ModelingToolkitStandardLibrary: Linear analysis](https://docs.sciml.ai/ModelingToolkit/stable/tutorials/linear_analysis/) for utilities that make linearization of completed models easier.
4747

48-
!!! note "Un-simplified system"
49-
50-
Linearization expects `sys` to be un-simplified, i.e., `structural_simplify` or `@mtkbuild` should not be called on the system before linearizing.
51-
5248
## Operating point
5349

5450
The operating point to linearize around can be specified with the keyword argument `op` like this: `op = Dict(x => 1, r => 2)`. The operating point may include specification of unknown variables, input variables and parameters. For variables that are not specified in `op`, the default value specified in the model will be used if available, if no value is specified, an error is thrown.
@@ -79,14 +75,15 @@ eqs = [D(x) ~ v
7975
y.u ~ x]
8076
8177
@named duffing = ODESystem(eqs, t, systems = [y, u], defaults = [u.u => 0])
78+
duffing = structural_simplify(duffing, inputs = [u.u], outputs = [y.u])
8279
8380
# pass a constant value for `x`, since it is the variable we will change in operating points
84-
linfun, simplified_sys = linearization_function(duffing, [u.u], [y.u]; op = Dict(x => NaN));
81+
linfun = linearization_function(duffing, [u.u], [y.u]; op = Dict(x => NaN));
8582
86-
println(linearize(simplified_sys, linfun; op = Dict(x => 1.0)))
87-
println(linearize(simplified_sys, linfun; op = Dict(x => 0.0)))
83+
println(linearize(duffing, linfun; op = Dict(x => 1.0)))
84+
println(linearize(duffing, linfun; op = Dict(x => 0.0)))
8885
89-
@time linearize(simplified_sys, linfun; op = Dict(x => 0.0))
86+
@time linearize(duffing, linfun; op = Dict(x => 0.0))
9087
9188
nothing # hide
9289
```

src/linearization.jl

Lines changed: 28 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""
2-
lin_fun, simplified_sys = linearization_function(sys::AbstractSystem, inputs, outputs; simplify = false, initialize = true, initialization_solver_alg = TrustRegion(), kwargs...)
2+
lin_fun, simplified_sys = linearization_function(sys::AbstractSystem, inputs, outputs; initialize = true, initialization_solver_alg = TrustRegion(), kwargs...)
33
44
Return a function that linearizes the system `sys`. The function [`linearize`](@ref) provides a higher-level and easier to use interface.
55
@@ -22,7 +22,6 @@ The `simplified_sys` has undergone [`structural_simplify`](@ref) and had any occ
2222
- `sys`: An [`ODESystem`](@ref). This function will automatically apply simplification passes on `sys` and return the resulting `simplified_sys`.
2323
- `inputs`: A vector of variables that indicate the inputs of the linearized input-output model.
2424
- `outputs`: A vector of variables that indicate the outputs of the linearized input-output model.
25-
- `simplify`: Apply simplification in tearing.
2625
- `initialize`: If true, a check is performed to ensure that the operating point is consistent (satisfies algebraic equations). If the op is not consistent, initialization is performed.
2726
- `initialization_solver_alg`: A NonlinearSolve algorithm to use for solving for a feasible set of state and algebraic variables that satisfies the specified operating point.
2827
- `autodiff`: An `ADType` supported by DifferentiationInterface.jl to use for calculating the necessary jacobians. Defaults to using `AutoForwardDiff()`
@@ -31,7 +30,7 @@ The `simplified_sys` has undergone [`structural_simplify`](@ref) and had any occ
3130
See also [`linearize`](@ref) which provides a higher-level interface.
3231
"""
3332
function linearization_function(sys::AbstractSystem, inputs,
34-
outputs; simplify = false,
33+
outputs;
3534
initialize = true,
3635
initializealg = nothing,
3736
initialization_abstol = 1e-5,
@@ -58,16 +57,7 @@ function linearization_function(sys::AbstractSystem, inputs,
5857
outputs = mapreduce(vcat, outputs; init = []) do var
5958
symbolic_type(var) == ArraySymbolic() ? collect(var) : [var]
6059
end
61-
ssys, diff_idxs, alge_idxs, input_idxs = io_preprocessing(sys, inputs, outputs;
62-
simplify,
63-
kwargs...)
64-
if zero_dummy_der
65-
dummyder = setdiff(unknowns(ssys), unknowns(sys))
66-
defs = Dict(x => 0.0 for x in dummyder)
67-
@set! ssys.defaults = merge(defs, defaults(ssys))
68-
op = merge(defs, op)
69-
end
70-
sys = ssys
60+
diff_idxs, alge_idxs = eq_idxs(sys)
7161

7262
if initializealg === nothing
7363
initializealg = initialize ? OverrideInit() : NoInit()
@@ -87,9 +77,9 @@ function linearization_function(sys::AbstractSystem, inputs,
8777

8878
p = parameter_values(prob)
8979
t0 = current_time(prob)
90-
inputvals = [p[idx] for idx in input_idxs]
80+
inputvals = [prob.ps[i] for i in inputs]
9181

92-
hp_fun = let fun = h, setter = setp_oop(sys, input_idxs)
82+
hp_fun = let fun = h, setter = setp_oop(sys, inputs)
9383
function hpf(du, input, u, p, t)
9484
p = setter(p, input)
9585
fun(du, u, p, t)
@@ -113,7 +103,7 @@ function linearization_function(sys::AbstractSystem, inputs,
113103
# observed function is a `GeneratedFunctionWrapper` with iip component
114104
h_jac = PreparedJacobian{true}(h, similar(prob.u0, size(outputs)), autodiff,
115105
prob.u0, DI.Constant(p), DI.Constant(t0))
116-
pf_fun = let fun = prob.f, setter = setp_oop(sys, input_idxs)
106+
pf_fun = let fun = prob.f, setter = setp_oop(sys, inputs)
117107
function pff(du, input, u, p, t)
118108
p = setter(p, input)
119109
SciMLBase.ParamJacobianWrapper(fun, t, u)(du, p)
@@ -127,10 +117,22 @@ function linearization_function(sys::AbstractSystem, inputs,
127117
end
128118

129119
lin_fun = LinearizationFunction(
130-
diff_idxs, alge_idxs, input_idxs, length(unknowns(sys)),
120+
diff_idxs, alge_idxs, length(unknowns(sys)),
131121
prob, h, u0 === nothing ? nothing : similar(u0), uf_jac, h_jac, pf_jac,
132122
hp_jac, initializealg, initialization_kwargs)
133-
return lin_fun, sys
123+
return lin_fun
124+
end
125+
126+
function eq_idxs(sys::AbstractSystem)
127+
eqs = equations(sys)
128+
alg_start_idx = findfirst(!isdiffeq, eqs)
129+
if alg_start_idx === nothing
130+
alg_start_idx = length(eqs) + 1
131+
end
132+
diff_idxs = 1:(alg_start_idx - 1)
133+
alge_idxs = alg_start_idx:length(eqs)
134+
135+
diff_idxs, alge_idxs
134136
end
135137

136138
"""
@@ -206,7 +208,7 @@ struct LinearizationFunction{
206208
The indexes of parameters in the linearized system which represent
207209
input variables.
208210
"""
209-
input_idxs::II
211+
inputs::II
210212
"""
211213
The number of unknowns in the linearized system.
212214
"""
@@ -281,6 +283,7 @@ function (linfun::LinearizationFunction)(u, p, t)
281283
end
282284

283285
fun = linfun.prob.f
286+
input_vals = [linfun.prob.ps[i] for i in linfun.inputs]
284287
if u !== nothing # Handle systems without unknowns
285288
linfun.num_states == length(u) ||
286289
error("Number of unknown variables ($(linfun.num_states)) does not match the number of input unknowns ($(length(u)))")
@@ -294,15 +297,15 @@ function (linfun::LinearizationFunction)(u, p, t)
294297
end
295298
fg_xz = linfun.uf_jac(u, DI.Constant(p), DI.Constant(t))
296299
h_xz = linfun.h_jac(u, DI.Constant(p), DI.Constant(t))
297-
fg_u = linfun.pf_jac([p[idx] for idx in linfun.input_idxs],
300+
fg_u = linfun.pf_jac(input_vals,
298301
DI.Constant(u), DI.Constant(p), DI.Constant(t))
299302
else
300303
linfun.num_states == 0 ||
301304
error("Number of unknown variables (0) does not match the number of input unknowns ($(length(u)))")
302305
fg_xz = zeros(0, 0)
303-
h_xz = fg_u = zeros(0, length(linfun.input_idxs))
306+
h_xz = fg_u = zeros(0, length(linfun.inputs))
304307
end
305-
h_u = linfun.hp_jac([p[idx] for idx in linfun.input_idxs],
308+
h_u = linfun.hp_jac(input_vals,
306309
DI.Constant(u), DI.Constant(p), DI.Constant(t))
307310
(f_x = fg_xz[linfun.diff_idxs, linfun.diff_idxs],
308311
f_z = fg_xz[linfun.diff_idxs, linfun.alge_idxs],
@@ -461,7 +464,7 @@ function CommonSolve.solve(prob::LinearizationProblem; allow_input_derivatives =
461464
end
462465

463466
"""
464-
(; A, B, C, D), simplified_sys = linearize_symbolic(sys::AbstractSystem, inputs, outputs; simplify = false, allow_input_derivatives = false, kwargs...)
467+
(; A, B, C, D), simplified_sys = linearize_symbolic(sys::AbstractSystem, inputs, outputs; allow_input_derivatives = false, kwargs...)
465468
466469
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`.
467470
@@ -479,12 +482,10 @@ y &= h(x, z, u)
479482
where `x` are differential unknown variables, `z` algebraic variables, `u` inputs and `y` outputs.
480483
"""
481484
function linearize_symbolic(sys::AbstractSystem, inputs,
482-
outputs; simplify = false, allow_input_derivatives = false,
485+
outputs; allow_input_derivatives = false,
483486
eval_expression = false, eval_module = @__MODULE__,
484487
kwargs...)
485-
sys, diff_idxs, alge_idxs, input_idxs = io_preprocessing(
486-
sys, inputs, outputs; simplify,
487-
kwargs...)
488+
diff_idxs, alge_idxs = eq_idxs(sys)
488489
sts = unknowns(sys)
489490
t = get_iv(sys)
490491
ps = parameters(sys; initial_parameters = true)

src/systems/abstractsystem.jl

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2484,21 +2484,6 @@ function eliminate_constants(sys::AbstractSystem)
24842484
return sys
24852485
end
24862486

2487-
function io_preprocessing(sys::AbstractSystem, inputs,
2488-
outputs; simplify = false, kwargs...)
2489-
sys, input_idxs = structural_simplify(sys, (inputs, outputs); simplify, kwargs...)
2490-
2491-
eqs = equations(sys)
2492-
alg_start_idx = findfirst(!isdiffeq, eqs)
2493-
if alg_start_idx === nothing
2494-
alg_start_idx = length(eqs) + 1
2495-
end
2496-
diff_idxs = 1:(alg_start_idx - 1)
2497-
alge_idxs = alg_start_idx:length(eqs)
2498-
2499-
sys, diff_idxs, alge_idxs, input_idxs
2500-
end
2501-
25022487
@latexrecipe function f(sys::AbstractSystem)
25032488
return latexify(equations(sys))
25042489
end

0 commit comments

Comments
 (0)