Skip to content

Commit af89d7b

Browse files
authored
Merge branch 'master' into constants2
2 parents 7a2a275 + 0f0868e commit af89d7b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+2256
-825
lines changed

.github/workflows/Downstream.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ jobs:
2525
- {user: SciML, repo: DataDrivenDiffEq.jl, group: Standard}
2626
- {user: SciML, repo: StructuralIdentifiability.jl, group: All}
2727
- {user: SciML, repo: ModelingToolkitStandardLibrary.jl}
28+
- {user: SciML, repo: ModelOrderReduction.jl, group: All}
2829
steps:
2930
- uses: actions/checkout@v2
3031
- uses: julia-actions/setup-julia@v1

Project.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "ModelingToolkit"
22
uuid = "961ee093-0014-501f-94e3-6117800e7a78"
33
authors = ["Yingbo Ma <[email protected]>", "Chris Rackauckas <[email protected]> and contributors"]
4-
version = "8.21.0"
4+
version = "8.29.1"
55

66
[deps]
77
AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c"
@@ -70,7 +70,7 @@ NonlinearSolve = "0.3.8"
7070
RecursiveArrayTools = "2.3"
7171
Reexport = "0.2, 1"
7272
RuntimeGeneratedFunctions = "0.4.3, 0.5"
73-
SciMLBase = "1.54"
73+
SciMLBase = "1.58.0"
7474
Setfield = "0.7, 0.8, 1"
7575
SpecialFunctions = "0.7, 0.8, 0.9, 0.10, 1.0, 2"
7676
StaticArrays = "0.10, 0.11, 0.12, 1.0"
@@ -93,9 +93,9 @@ OptimizationOptimJL = "36348300-93cb-4f02-beb5-3c3902f8871e"
9393
OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed"
9494
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
9595
ReferenceTests = "324d217c-45ce-50fc-942e-d289b448e8cf"
96+
SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f"
9697
StableRNGs = "860ef19b-820b-49d6-a774-d7a799459cd3"
9798
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
98-
SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f"
9999
SteadyStateDiffEq = "9672c7b4-1e72-59bd-8a11-6ac3964bc41f"
100100
StochasticDiffEq = "789caeaf-c7a9-5a7d-9973-96adeb23e2a0"
101101
Sundials = "c3572dad-4567-51f8-b174-8c6c989267f4"

docs/pages.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ pages = [
1717
"basics/Variable_metadata.md",
1818
"basics/Composition.md",
1919
"basics/Events.md",
20+
"basics/Linearization.md",
2021
"basics/Validation.md",
2122
"basics/DependencyGraphs.md",
2223
"basics/FAQ.md"],

docs/src/basics/Linearization.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# [Linearization](@id linearization)
2+
A nonlinear dynamical system with state (differential and algebraic) ``x`` and input signals ``u``
3+
```math
4+
M \dot x = f(x, u)
5+
```
6+
can be linearized using the function [`linearize`](@ref) to produce a linear statespace system on the form
7+
```math
8+
\begin{aligned}
9+
\dot x &= Ax + Bu\\
10+
y &= Cx + Du
11+
\end{aligned}
12+
```
13+
14+
The `linearize` function expects the user to specify the inputs ``u`` and the outputs ``u`` using the syntax shown in the example below:
15+
16+
## Example
17+
```@example LINEARIZE
18+
using ModelingToolkit
19+
@variables t x(t)=0 y(t)=0 u(t)=0 r(t)=0
20+
@parameters kp = 1
21+
D = Differential(t)
22+
23+
eqs = [u ~ kp * (r - y) # P controller
24+
D(x) ~ -x + u # First-order plant
25+
y ~ x] # Output equation
26+
27+
@named sys = ODESystem(eqs, t)
28+
matrices, simplified_sys = linearize(sys, [r], [y]) # Linearize from r to y
29+
matrices
30+
```
31+
The named tuple `matrices` contains the matrices of the linear statespace representation, while `simplified_sys` is an `ODESystem` that, amongst other things, indicates the state order in the linear system through
32+
```@example LINEARIZE
33+
using ModelingToolkit: inputs, outputs
34+
[states(simplified_sys); inputs(simplified_sys); outputs(simplified_sys)]
35+
```
36+
37+
## Operating point
38+
The operating point to linearize around can be specified with the keyword argument `op` like this: `op = Dict(x => 1, r => 2)`.
39+
40+
## Batch linearization and algebraic variables
41+
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`.
42+
43+
44+
## Input derivatives
45+
Physical systems are always *proper*, i.e., they do not differentiate causal inputs. However, ModelingToolkit allows you to model non-proper systems, such as inverse models, and may sometimes fail to find a realization of a proper system on proper form. In these situations, `linearize` may throw an error mentioning
46+
```
47+
Input derivatives appeared in expressions (-g_z\g_u != 0)
48+
```
49+
This means that to simulate this system, some order of derivatives of the input is required. To allow `linearize` to proceed in this situation, one may pass the keyword argument `allow_input_derivatives = true`, in which case the resulting model will have twice as many inputs, ``2n_u``, where the last ``n_u`` inputs correspond to ``\dot u``.
50+
51+
If the modeled system is actually proper (but MTK failed to find a proper realization), further numerical simplification can be applied to the resulting statespace system to obtain a proper form. Such simplification is currently available in the experimental package [ControlSystemsMTK](https://github.com/baggepinnen/ControlSystemsMTK.jl#internals-transformation-of-non-proper-models-to-proper-statespace-form).
52+
53+
54+
## Tools for linear analysis
55+
[ModelingToolkitStandardLibrary](http://mtkstdlib.sciml.ai/dev/API/linear_analysis/) contains a set of [tools for more advanced linear analysis](http://mtkstdlib.sciml.ai/dev/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.
56+
57+
```@index
58+
Pages = ["Linearization.md"]
59+
```
60+
61+
```@docs
62+
linearize
63+
ModelingToolkit.linearization_function
64+
```

src/ModelingToolkit.jl

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ module ModelingToolkit
55
using DocStringExtensions
66
using AbstractTrees
77
using DiffEqBase, SciMLBase, ForwardDiff, Reexport
8+
using SciMLBase: StandardODEProblem, StandardNonlinearProblem, handle_varmap
89
using Distributed
910
using StaticArrays, LinearAlgebra, SparseArrays, LabelledArrays
1011
using InteractiveUtils
@@ -110,6 +111,7 @@ function parameters end
110111

111112
# this has to be included early to deal with depency issues
112113
include("structural_transformation/bareiss.jl")
114+
function complete end
113115
include("bipartite_graph.jl")
114116
using .BipartiteGraphs
115117

@@ -147,6 +149,7 @@ include("systems/dependency_graphs.jl")
147149
include("systems/systemstructure.jl")
148150
using .SystemStructures
149151

152+
include("debugging.jl")
150153
include("systems/alias_elimination.jl")
151154
include("structural_transformation/StructuralTransformations.jl")
152155

@@ -176,7 +179,8 @@ export NonlinearSystem, OptimizationSystem
176179
export alias_elimination, flatten
177180
export connect, @connector, Connection, Flow, Stream, instream
178181
export isinput, isoutput, getbounds, hasbounds, isdisturbance, istunable, getdist, hasdist,
179-
tunable_parameters, isirreducible, getdescription, hasdescription
182+
tunable_parameters, isirreducible, getdescription, hasdescription, isbinaryvar,
183+
isintegervar
180184
export ode_order_lowering, dae_order_lowering, liouville_transform
181185
export PDESystem
182186
export Differential, expand_derivatives, @derivatives
@@ -185,7 +189,7 @@ export Term, Sym
185189
export SymScope, LocalScope, ParentScope, GlobalScope
186190
export independent_variables, independent_variable, states, parameters, equations, controls,
187191
observed, structure, full_equations
188-
export structural_simplify, expand_connections, linearize, linear_statespace
192+
export structural_simplify, expand_connections, linearize, linearization_function
189193
export DiscreteSystem, DiscreteProblem
190194

191195
export calculate_jacobian, generate_jacobian, generate_function
@@ -207,7 +211,9 @@ export toexpr, get_variables
207211
export simplify, substitute
208212
export build_function
209213
export modelingtoolkitize
214+
210215
export @variables, @parameters, @constants
211-
export @named, @nonamespace, @namespace, extend, compose
216+
export @named, @nonamespace, @namespace, extend, compose, complete
217+
export debug_system
212218

213219
end # module

src/bipartite_graph.jl

Lines changed: 64 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
module BipartiteGraphs
22

3+
import ModelingToolkit: complete
4+
35
export BipartiteEdge, BipartiteGraph, DiCMOBiGraph, Unassigned, unassigned,
46
Matching, ResidualCMOGraph, InducedCondensationGraph, maximal_matching,
57
construct_augmenting_path!, MatchedCondensationGraph
68

79
export 𝑠vertices, 𝑑vertices, has_𝑠vertex, has_𝑑vertex, 𝑠neighbors, 𝑑neighbors,
810
𝑠edges, 𝑑edges, nsrcs, ndsts, SRC, DST, set_neighbors!, invview,
9-
complete, delete_srcs!, delete_dsts!
11+
delete_srcs!, delete_dsts!
1012

1113
using DocStringExtensions
1214
using UnPack
@@ -63,7 +65,17 @@ end
6365
function Base.setindex!(m::Matching{U}, v::Union{Integer, U}, i::Integer) where {U}
6466
if m.inv_match !== nothing
6567
oldv = m.match[i]
66-
isa(oldv, Int) && (m.inv_match[oldv] = unassigned)
68+
# TODO: maybe default Matching to always have an `inv_match`?
69+
70+
# To maintain the invariant that `m.inv_match[m.match[i]] == i`, we need
71+
# to unassign the matching at `m.inv_match[v]` if it exists.
72+
if v isa Int && (iv = m.inv_match[v]) isa Int
73+
m.match[iv] = unassigned
74+
end
75+
if isa(oldv, Int)
76+
@assert m.inv_match[oldv] == i
77+
m.inv_match[oldv] = unassigned
78+
end
6779
isa(v, Int) && (m.inv_match[v] = i)
6880
end
6981
return m.match[i] = v
@@ -191,14 +203,54 @@ end
191203
# Matrix whose only purpose is to pretty-print the bipartite graph
192204
struct BipartiteAdjacencyList
193205
u::Union{Vector{Int}, Nothing}
206+
highligh_u::Union{Set{Int}, Nothing}
207+
match::Union{Int, Bool, Unassigned}
208+
end
209+
function BipartiteAdjacencyList(u::Union{Vector{Int}, Nothing})
210+
BipartiteAdjacencyList(u, nothing, unassigned)
211+
end
212+
213+
struct HighlightInt
214+
i::Int
215+
highlight::Union{Symbol, Nothing}
216+
end
217+
Base.typeinfo_implicit(::Type{HighlightInt}) = true
218+
219+
function Base.show(io::IO, hi::HighlightInt)
220+
if hi.highlight !== nothing
221+
printstyled(io, hi.i, color = hi.highlight)
222+
else
223+
print(io, hi.i)
224+
end
194225
end
226+
195227
function Base.show(io::IO, l::BipartiteAdjacencyList)
228+
if l.match === true
229+
printstyled(io, "", color = :light_blue, bold = true)
230+
end
196231
if l.u === nothing
197232
printstyled(io, '', color = :light_black)
198233
elseif isempty(l.u)
199234
printstyled(io, '', color = :light_black)
200-
else
235+
elseif l.highligh_u === nothing
201236
print(io, l.u)
237+
else
238+
match = l.match
239+
isa(match, Bool) && (match = unassigned)
240+
function choose_color(i)
241+
i in l.highligh_u ? (i == match ? :light_yellow : :green) :
242+
(i == match ? :yellow : nothing)
243+
end
244+
if !isempty(setdiff(l.highligh_u, l.u))
245+
# Only for debugging, shouldn't happen in practice
246+
print(io, map(union(l.u, l.highligh_u)) do i
247+
HighlightInt(i, !(i in l.u) ? :light_red : choose_color(i))
248+
end)
249+
else
250+
print(io, map(l.u) do i
251+
HighlightInt(i, choose_color(i))
252+
end)
253+
end
202254
end
203255
end
204256

@@ -262,7 +314,8 @@ function BipartiteGraph(nsrcs::T, ndsts::T, backedge::Val{B} = Val(true);
262314
end
263315

264316
function Base.copy(bg::BipartiteGraph)
265-
BipartiteGraph(bg.ne, copy(bg.fadjlist), copy(bg.badjlist), deepcopy(bg.metadata))
317+
BipartiteGraph(bg.ne, map(copy, bg.fadjlist), map(copy, bg.badjlist),
318+
deepcopy(bg.metadata))
266319
end
267320
Base.eltype(::Type{<:BipartiteGraph{I}}) where {I} = I
268321
function Base.empty!(g::BipartiteGraph)
@@ -435,19 +488,23 @@ function set_neighbors!(g::BipartiteGraph, i::Integer, new_neighbors)
435488
for n in old_neighbors
436489
@inbounds list = g.badjlist[n]
437490
index = searchsortedfirst(list, i)
438-
deleteat!(list, index)
491+
if 1 <= index <= length(list) && list[index] == i
492+
deleteat!(list, index)
493+
end
439494
end
440495
for n in new_neighbors
441496
@inbounds list = g.badjlist[n]
442497
index = searchsortedfirst(list, i)
443-
insert!(list, index, i)
498+
if !(1 <= index <= length(list) && list[index] == i)
499+
insert!(list, index, i)
500+
end
444501
end
445502
end
446503
if iszero(new_nneighbors) # this handles Tuple as well
447504
# Warning: Aliases old_neighbors
448505
empty!(g.fadjlist[i])
449506
else
450-
g.fadjlist[i] = copy(new_neighbors)
507+
g.fadjlist[i] = unique!(sort(new_neighbors))
451508
end
452509
end
453510

src/debugging.jl

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
const LOGGED_FUN = Set([log, sqrt, (^), /, inv])
2+
is_legal(::typeof(/), a, b) = is_legal(inv, b)
3+
is_legal(::typeof(inv), a) = !iszero(a)
4+
is_legal(::Union{typeof(log), typeof(sqrt)}, a) = a isa Complex || a >= zero(a)
5+
is_legal(::typeof(^), a, b) = a isa Complex || b isa Complex || isinteger(b) || a >= zero(a)
6+
7+
struct LoggedFun{F}
8+
f::F
9+
args::Any
10+
end
11+
Base.nameof(lf::LoggedFun) = nameof(lf.f)
12+
SymbolicUtils.promote_symtype(::LoggedFun, Ts...) = Real
13+
function (lf::LoggedFun)(args...)
14+
f = lf.f
15+
symbolic_args = lf.args
16+
if is_legal(f, args...)
17+
f(args...)
18+
else
19+
args_str = join(string.(symbolic_args .=> args), ", ", ", and ")
20+
throw(DomainError(args, "$(lf.f) errors with input(s): $args_str"))
21+
end
22+
end
23+
24+
function logged_fun(f, args...)
25+
# Currently we don't really support complex numbers
26+
term(LoggedFun(f, args), args..., type = Real)
27+
end
28+
29+
debug_sub(eq::Equation) = debug_sub(eq.lhs) ~ debug_sub(eq.rhs)
30+
function debug_sub(ex)
31+
istree(ex) || return ex
32+
f = operation(ex)
33+
args = map(debug_sub, arguments(ex))
34+
f in LOGGED_FUN ? logged_fun(f, args...) :
35+
similarterm(ex, f, args, metadata = metadata(ex))
36+
end

0 commit comments

Comments
 (0)