Skip to content

Commit 5b57398

Browse files
start attempting to write composition page
1 parent e4b255f commit 5b57398

File tree

2 files changed

+173
-24
lines changed

2 files changed

+173
-24
lines changed

docs/make.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ makedocs(
1919
"Basics" => Any[
2020
"basics/AbstractSystem.md",
2121
"basics/ContextualVariables.md",
22+
"basics/Composition.md"
2223
"basics/Validation.md",
2324
"basics/DependencyGraphs.md"
2425
],

docs/src/basics/Composition.md

Lines changed: 172 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,73 @@ easily build large models. The composition is lazy and only instantiated
55
at the time of conversion to numerical models, allowing a more performant
66
way in terms of computation time and memory.
77

8+
## Simple Model Composition Example
9+
10+
The following is an example of building a model in a library with
11+
an optional forcing function, and allowing the user to specify the
12+
forcing later. Here, the library author defines a component named
13+
`decay`. The user then builds two `decay` components and connects them,
14+
saying the forcing term of `decay1` is a constant while the forcing term
15+
of `decay2` is the value of the state variable `x`.
16+
17+
```julia
18+
using ModelingToolkit
19+
20+
function decay(;name)
21+
@parameters t a
22+
@variables x(t) f(t)
23+
D = Differential(t)
24+
ODESystem([
25+
D(x) ~ -a*x + f
26+
];
27+
name=name)
28+
end
29+
30+
@named decay1 = decay()
31+
@named decay2 = decay()
32+
33+
@parameters t
34+
D = Differential(t)
35+
@named connected = ODESystem([
36+
decay2.f ~ decay1.x
37+
D(decay1.f) ~ 0
38+
], t, systems=[decay1, decay2])
39+
40+
equations(connected)
41+
42+
#4-element Vector{Equation}:
43+
# Differential(t)(decay1₊f(t)) ~ 0
44+
# decay2₊f(t) ~ decay1₊x(t)
45+
# Differential(t)(decay1₊x(t)) ~ decay1₊f(t) - (decay1₊a*(decay1₊x(t)))
46+
# Differential(t)(decay2₊x(t)) ~ decay2₊f(t) - (decay2₊a*(decay2₊x(t)))
47+
48+
equations(alias_elimination(connected))
49+
50+
# 4-element Vector{Equation}:
51+
# Differential(t)(decay1₊f(t)) ~ 0
52+
# 0 ~ decay1₊x(t) - (decay2₊f(t))
53+
# Differential(t)(decay1₊x(t)) ~ decay1₊f(t) - (decay1₊a*(decay1₊x(t)))
54+
# Differential(t)(decay2₊x(t)) ~ decay2₊f(t) - (decay2₊a*(decay2₊x(t)))
55+
```
56+
57+
Now we can solve the system:
58+
59+
```julia
60+
x0 = [
61+
decay1.x => 1.0
62+
decay1.f => 0.0
63+
decay2.x => 1.0
64+
]
65+
p = [
66+
decay1.a => 0.1
67+
decay2.a => 0.2
68+
]
69+
70+
using DifferentialEquations
71+
prob = ODEProblem(reduced_system, x0, (0.0, 100.0), p)
72+
sol = solve(prob, Tsit5())
73+
```
74+
875
## Basics of Model Composition
976

1077
Every `AbstractSystem` has a `system` keyword argument for specifying
@@ -29,40 +96,121 @@ by specifying their equality: `x ~ subsys.x` in the `eqs` for `sys`.
2996
This algebraic relationship can then be simplified by transformations
3097
like `alias_elimination` or `tearing` which will be described later.
3198

32-
### Simple Model Composition Example
99+
### Numerics with Composed Models
33100

34-
The following is an example of building a model in a library with
35-
an optional forcing function, and allowing the user to specify the
36-
forcing later. Here, the library author defines a component named
37-
`decay`. The user then builds two `decay` components and connects them,
38-
saying the forcing term of `decay1` is a constant while the forcing term
39-
of `decay2` is the value of the state variable `x`.
101+
These composed models can then be directly transformed into their
102+
associated `SciMLProblem` type using the standard constructors. When
103+
this is done, the initial conditions and parameters must be specified
104+
in their namespaced form. For example:
40105

41106
```julia
42-
function decay(;name)
43-
@parameters t a
44-
@variables x(t) f(t)
45-
D = Differential(t)
46-
ODESystem([
47-
D(x) ~ -a*x + f
48-
];
49-
name=name)
50-
end
107+
u0 = [
108+
x => 2.0
109+
subsys.x => 2.0
110+
]
111+
```
51112

52-
@named decay1 = decay()
53-
@named decay2 = decay()
113+
Note that any default values within the given subcomponent will be
114+
used if no override is provided at construction time. If any values for
115+
initial conditions or parameters are unspecified an error will be thrown.
116+
117+
When the model is numerically solved, the solution can be accessed via
118+
its symbolic values. For example, if `sol` is the `ODESolution`, one
119+
can use `sol[x]` and `sol[subsys.x]` to access the respective timeseries
120+
in the solution. All other indexing rules stay the same, so `sol[x,1:5]`
121+
accesses the first through fifth values of `x`. Note that this can be
122+
done even if the variable `x` is eliminated from the system from
123+
transformations like `alias_elimination` or `tearing`: the variable
124+
will be lazily reconstructed on demand.
125+
126+
## Alias Elimination
127+
128+
In many cases, the nicest way to build a model may leave a lot of
129+
unnecessary variables. Thus one may want to remove these equations
130+
before numerically solving. The `alias_elimination` function removes
131+
these relationships and trivial singularity equations, i.e. equations
132+
which result in `0~0` expressions in over-specified systems.
133+
134+
## Inheritance and Combine (TODO)
135+
136+
Model inheritance can be done in two ways: explicitly or implicitly.
137+
The explicit way is to shadow variables with equality expressions.
138+
For example, let's assume we have three separate systems which we
139+
want to compose to a single one. This is how one could explicitly
140+
forward all states and parameters to the higher level system:
141+
142+
```julia
143+
using ModelingToolkit, OrdinaryDiffEq, Plots
144+
145+
## Library code
54146

55147
@parameters t
56148
D = Differential(t)
57-
connected = ODESystem([
58-
decay2.f ~ decay1.x
59-
D(decay1.f) ~ 0
60-
], t, systems=[decay1, decay2])
149+
150+
@variables S(t), I(t), R(t)
151+
N = S + I + R
152+
@parameters β,γ
153+
154+
@named seqn = ODESystem([D(S) ~ -β*S*I/N])
155+
@named ieqn = ODESystem([D(I) ~ β*S*I/N-γ*I])
156+
@named reqn = ODESystem([D(R) ~ γ*I])
157+
158+
@named sir = ODESystem([
159+
S ~ ieqn.S,
160+
I ~ seqn.I,
161+
R ~ ieqn.R,
162+
ieqn.S ~ seqn.S,
163+
seqn.I ~ ieqn.I,
164+
seqn.R ~ reqn.R,
165+
ieqn.R ~ reqn.R,
166+
reqn.I ~ ieqn.I], t, [S,I,R], [β,γ], name=:sir,
167+
systems=[seqn,ieqn,reqn],
168+
default_p = [
169+
seqn.β => β
170+
ieqn.β => β
171+
ieqn.γ => γ
172+
reqn.γ => γ
173+
])
61174
```
62175

63-
## Combine
176+
Note that the states are forwarded by an equality relationship, while
177+
the parameters are forwarded through a relationship in their default
178+
values. The user of this model can then solve this model simply by
179+
specifying the values at the highest level:
64180

65-
## Alias Elimination
181+
```julia
182+
sireqn_simple = alias_elimination(sir)
183+
184+
## User Code
185+
186+
u0 = [sir.S => 990.0,
187+
sir.I => 10.0,
188+
sir.R => 0.0]
189+
190+
p = [
191+
sir.β => 0.5
192+
sir.γ => 0.25
193+
]
194+
195+
# Time span
196+
tspan = (0.0,40.0)
197+
198+
# Define problem
199+
prob = ODEProblem(sireqn_simple,u0,tspan,p,jac=true)
200+
201+
# Solve
202+
sol = solve(prob,Tsit5())
203+
204+
sol[R]
205+
```
206+
207+
However, one can similarly simplify this process of inheritance by
208+
using `combine` which concatenates all of the vectors within the
209+
systems. For example, we could equivalently have done:
210+
211+
```julia
212+
@named sir = combine([seqn,ieqn,reqn])
213+
```
66214

67215
## The Tearing Transformation
68216

0 commit comments

Comments
 (0)