Skip to content

Commit 18d2362

Browse files
Merge pull request #3412 from AayushSabharwal/as/linfun-docs
fix: allow passing guesses to `linearize`, improve linearization docs
2 parents 585301a + 027fe71 commit 18d2362

File tree

4 files changed

+96
-11
lines changed

4 files changed

+96
-11
lines changed

docs/src/basics/Linearization.md

Lines changed: 72 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ The `linearize` function expects the user to specify the inputs ``u`` and the ou
2222
```@example LINEARIZE
2323
using ModelingToolkit
2424
using ModelingToolkit: t_nounits as t, D_nounits as D
25-
@variables x(t)=0 y(t)=0 u(t)=0 r(t)=0
25+
@variables x(t)=0 y(t) u(t) r(t)=0
2626
@parameters kp = 1
2727
2828
eqs = [u ~ kp * (r - y) # P controller
@@ -43,7 +43,7 @@ using ModelingToolkit: inputs, outputs
4343

4444
!!! note "Inputs must be unconnected"
4545

46-
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/ModelingToolkitStandardLibrary/stable/API/linear_analysis/) for utilities that make linearization of completed models easier.
46+
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

4848
!!! note "Un-simplified system"
4949

@@ -57,6 +57,74 @@ The operating point to linearize around can be specified with the keyword argume
5757

5858
If linearization is to be performed around multiple operating points, the simplification of the system has to be carried out a single time only. To facilitate this, the lower-level function [`ModelingToolkit.linearization_function`](@ref) is available. This function further allows you to obtain separate Jacobians for the differential and algebraic parts of the model. For ODE models without algebraic equations, the statespace representation above is available from the output of `linearization_function` as `A, B, C, D = f_x, f_u, h_x, h_u`.
5959

60+
All variables that will be fixed by an operating point _must_ be provided in the operating point to `linearization_function`. For example, if the operating points fix the value of
61+
`x`, `y` and `z` then an operating point with constant values for these variables (e.g. `Dict(x => 1.0, y => 1.0, z => 1.0)`) must be provided. The constant values themselves
62+
do not matter and can be changed by subsequent operating points.
63+
64+
One approach to batch linearization would be to call `linearize` in a loop, providing a different operating point each time. For example:
65+
66+
```@example LINEARIZE
67+
using ModelingToolkitStandardLibrary
68+
using ModelingToolkitStandardLibrary.Blocks
69+
70+
@parameters k=10 k3=2 c=1
71+
@variables x(t)=0 [bounds = (-0.5, 1.5)]
72+
@variables v(t) = 0
73+
74+
@named y = Blocks.RealOutput()
75+
@named u = Blocks.RealInput()
76+
77+
eqs = [D(x) ~ v
78+
D(v) ~ -k * x - k3 * x^3 - c * v + 10u.u
79+
y.u ~ x]
80+
81+
@named duffing = ODESystem(eqs, t, systems = [y, u], defaults = [u.u => 0])
82+
83+
# 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));
85+
86+
println(linearize(simplified_sys, linfun; op = Dict(x => 1.0)))
87+
println(linearize(simplified_sys, linfun; op = Dict(x => 0.0)))
88+
89+
@time linearize(simplified_sys, linfun; op = Dict(x => 0.0))
90+
91+
nothing # hide
92+
```
93+
94+
However, this route is still expensive since it has to repeatedly process the symbolic map provided to `op`. `linearize` is simply a wrapper for creating and solving a
95+
[`ModelingToolkit.LinearizationProblem`](@ref). This object is symbolically indexable, and can thus integrate with SymbolicIndexingInterface.jl for fast updates.
96+
97+
```@example LINEARIZE
98+
using SymbolicIndexingInterface
99+
100+
# The second argument is the value of the independent variable `t`.
101+
linprob = LinearizationProblem(linfun, 1.0)
102+
# It can be mutated
103+
linprob.t = 0.0
104+
# create a setter function to update `x` efficiently
105+
setter! = setu(linprob, x)
106+
107+
function fast_linearize!(problem, setter!, value)
108+
setter!(problem, value)
109+
solve(problem)
110+
end
111+
112+
println(fast_linearize!(linprob, setter!, 1.0))
113+
println(fast_linearize!(linprob, setter!, 0.0))
114+
115+
@time fast_linearize!(linprob, setter!, 1.0)
116+
117+
nothing # hide
118+
```
119+
120+
Note that `linprob` above can be interacted with similar to a normal `ODEProblem`.
121+
122+
```@repl LINEARIZE
123+
prob[x]
124+
prob[x] = 1.5
125+
prob[x]
126+
```
127+
60128
## Symbolic linearization
61129

62130
The function [`ModelingToolkit.linearize_symbolic`](@ref) works similar to [`ModelingToolkit.linearize`](@ref) but returns symbolic rather than numeric Jacobians. Symbolic linearization have several limitations and no all systems that can be linearized numerically can be linearized symbolically.
@@ -75,7 +143,7 @@ If the modeled system is actually proper (but MTK failed to find a proper realiz
75143

76144
## Tools for linear analysis
77145

78-
[ModelingToolkitStandardLibrary](https://docs.sciml.ai/ModelingToolkitStandardLibrary/stable/) contains a set of [tools for more advanced linear analysis](https://docs.sciml.ai/ModelingToolkitStandardLibrary/stable/API/linear_analysis/). These can be used to make it easier to work with and analyze causal models, such as control and signal-processing systems.
146+
ModelingToolkit contains a set of [tools for more advanced linear analysis](https://docs.sciml.ai/ModelingToolkit/stable/tutorials/linear_analysis/). These can be used to make it easier to work with and analyze causal models, such as control and signal-processing systems.
79147

80148
Also see [ControlSystemsMTK.jl](https://juliacontrol.github.io/ControlSystemsMTK.jl/dev/) for an interface to [ControlSystems.jl](https://github.com/JuliaControl/ControlSystems.jl) that contains tools for linear analysis and frequency-domain analysis.
81149

@@ -89,4 +157,5 @@ Pages = ["Linearization.md"]
89157
linearize
90158
ModelingToolkit.linearize_symbolic
91159
ModelingToolkit.linearization_function
160+
ModelingToolkit.LinearizationProblem
92161
```

src/ModelingToolkit.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,7 @@ export independent_variable, equations, controls, observed, full_equations
262262
export initialization_equations, guesses, defaults, parameter_dependencies, hierarchy
263263
export structural_simplify, expand_connections, linearize, linearization_function,
264264
LinearizationProblem
265+
export solve
265266

266267
export calculate_jacobian, generate_jacobian, generate_function, generate_custom_function
267268
export calculate_control_jacobian, generate_control_jacobian

src/linearization.jl

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ function linearization_function(sys::AbstractSystem, inputs,
4141
initialization_solver_alg = TrustRegion(),
4242
eval_expression = false, eval_module = @__MODULE__,
4343
warn_initialize_determined = true,
44+
guesses = Dict(),
4445
kwargs...)
4546
op = Dict(op)
4647
inputs isa AbstractVector || (inputs = [inputs])
@@ -66,11 +67,10 @@ function linearization_function(sys::AbstractSystem, inputs,
6667
initializealg = initialize ? OverrideInit() : NoInit()
6768
end
6869

69-
fun, u0, p = process_SciMLProblem(
70-
ODEFunction{true, SciMLBase.FullSpecialize}, sys, op, p;
71-
t = 0.0, build_initializeprob = initializealg isa OverrideInit,
72-
allow_incomplete = true, algebraic_only = true)
73-
prob = ODEProblem(fun, u0, (nothing, nothing), p)
70+
prob = ODEProblem{true, SciMLBase.FullSpecialize}(
71+
sys, op, (nothing, nothing), p; allow_incomplete = true,
72+
algebraic_only = true, guesses)
73+
u0 = state_values(prob)
7474

7575
ps = parameters(sys)
7676
h = build_explicit_observed_function(sys, outputs; eval_expression, eval_module)
@@ -148,6 +148,12 @@ function SymbolicIndexingInterface.parameter_values(f::LinearizationFunction)
148148
end
149149
SymbolicIndexingInterface.current_time(f::LinearizationFunction) = current_time(f.prob)
150150

151+
function Base.show(io::IO, mime::MIME"text/plain", lf::LinearizationFunction)
152+
printstyled(io, "LinearizationFunction"; bold = true, color = :blue)
153+
println(io, " which wraps:")
154+
show(io, mime, lf.prob)
155+
end
156+
151157
"""
152158
$(TYPEDSIGNATURES)
153159
@@ -265,6 +271,12 @@ mutable struct LinearizationProblem{F <: LinearizationFunction, T}
265271
t::T
266272
end
267273

274+
function Base.show(io::IO, mime::MIME"text/plain", prob::LinearizationProblem)
275+
printstyled(io, "LinearizationProblem"; bold = true, color = :blue)
276+
println(io, " at time ", prob.t, " which wraps:")
277+
show(io, mime, prob.f.prob)
278+
end
279+
268280
"""
269281
$(TYPEDSIGNATURES)
270282

test/downstream/linearize.jl

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -317,12 +317,15 @@ matrices = linfun([1.0], Dict(p => 3.0), 1.0)
317317
@test matrices.f_u[] == 3.0
318318
end
319319

320-
@testset "Issue #2941" begin
321-
@variables x(t) y(t) [guess = 1.0]
320+
@testset "Issue #2941 and #3400" begin
321+
@variables x(t) y(t)
322322
@parameters p
323323
eqs = [0 ~ x * log(y) - p]
324324
@named sys = ODESystem(eqs, t; defaults = [p => 1.0])
325325
sys = complete(sys)
326-
@test_nowarn linearize(
326+
@test_throws ModelingToolkit.MissingVariablesError linearize(
327327
sys, [x], []; op = Dict(x => 1.0), allow_input_derivatives = true)
328+
@test_nowarn linearize(
329+
sys, [x], []; op = Dict(x => 1.0), guesses = Dict(y => 1.0),
330+
allow_input_derivatives = true)
328331
end

0 commit comments

Comments
 (0)