Skip to content

Commit 5edbcb1

Browse files
Merge branch 'master' into document-fully-determined
2 parents 6fb9889 + 6cca2ff commit 5edbcb1

File tree

108 files changed

+5817
-1647
lines changed

Some content is hidden

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

108 files changed

+5817
-1647
lines changed

.github/workflows/Downstream.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ jobs:
3232
- {user: SciML, repo: NeuralPDE.jl, group: NNPDE}
3333
- {user: SciML, repo: DataDrivenDiffEq.jl, group: Downstream}
3434
- {user: SciML, repo: StructuralIdentifiability.jl, group: All}
35-
- {user: SciML, repo: ModelingToolkitStandardLibrary.jl}
35+
- {user: SciML, repo: ModelingToolkitStandardLibrary.jl, group: Core}
3636
- {user: SciML, repo: ModelOrderReduction.jl, group: All}
3737
- {user: SciML, repo: MethodOfLines.jl, group: Interface}
3838
- {user: SciML, repo: MethodOfLines.jl, group: 2D_Diffusion}

Project.toml

Lines changed: 15 additions & 7 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 = "9.8.0"
4+
version = "9.25.0"
55

66
[deps]
77
AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c"
@@ -32,7 +32,8 @@ Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb"
3232
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
3333
MLStyle = "d8e11817-5142-5d16-987a-aa16d5891078"
3434
NaNMath = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3"
35-
OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed"
35+
NonlinearSolve = "8913a72c-1f9b-4ce2-8d82-65094dcecaec"
36+
OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
3637
PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a"
3738
RecursiveArrayTools = "731186ca-8d62-57ce-b412-fbd966d074cd"
3839
Reexport = "189a3867-3050-52da-a836-e630ba90ab69"
@@ -63,10 +64,12 @@ MTKDeepDiffsExt = "DeepDiffs"
6364
[compat]
6465
AbstractTrees = "0.3, 0.4"
6566
ArrayInterface = "6, 7"
67+
BifurcationKit = "0.3"
6668
Combinatorics = "1"
6769
Compat = "3.42, 4"
6870
ConstructionBase = "1"
6971
DataStructures = "0.17, 0.18"
72+
DeepDiffs = "1"
7073
DiffEqBase = "6.103.0"
7174
DiffEqCallbacks = "2.16, 3"
7275
DiffRules = "0.1, 1.0"
@@ -89,7 +92,9 @@ Libdl = "1"
8992
LinearAlgebra = "1"
9093
MLStyle = "0.4.17"
9194
NaNMath = "0.3, 1"
92-
OrdinaryDiffEq = "6.73.0"
95+
NonlinearSolve = "3.12"
96+
OrderedCollections = "1"
97+
OrdinaryDiffEq = "6.82.0"
9398
PrecompileTools = "1"
9499
RecursiveArrayTools = "2.3, 3"
95100
Reexport = "0.2, 1"
@@ -102,9 +107,9 @@ SimpleNonlinearSolve = "0.1.0, 1"
102107
SparseArrays = "1"
103108
SpecialFunctions = "0.7, 0.8, 0.9, 0.10, 1.0, 2"
104109
StaticArrays = "0.10, 0.11, 0.12, 1.0"
105-
SymbolicIndexingInterface = "0.3.11"
106-
SymbolicUtils = "1.0"
107-
Symbolics = "5.26"
110+
SymbolicIndexingInterface = "0.3.12"
111+
SymbolicUtils = "2.1"
112+
Symbolics = "5.32"
108113
URIs = "1"
109114
UnPack = "0.1, 1.0"
110115
Unitful = "1.1"
@@ -115,14 +120,17 @@ AmplNLWriter = "7c4d4715-977e-5154-bfe0-e096adeac482"
115120
BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
116121
ControlSystemsBase = "aaaaaaaa-a6ca-5380-bf3e-84a91bcd477e"
117122
DeepDiffs = "ab62b9b5-e342-54a8-a765-a90f495de1a6"
123+
DelayDiffEq = "bcd4f6db-9728-5f36-b5f7-82caef46ccdb"
118124
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
119125
Ipopt = "b6b21f68-93f8-5de0-b562-5493be1d77c9"
120126
Ipopt_jll = "9cc047cb-c261-5740-88fc-0cf96f7bdcc7"
127+
JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b"
121128
ModelingToolkitStandardLibrary = "16a59e39-deab-5bd0-87e4-056b12336739"
122129
NonlinearSolve = "8913a72c-1f9b-4ce2-8d82-65094dcecaec"
123130
Optimization = "7f7a1694-90dd-40f0-9382-eb1efda571ba"
124131
OptimizationMOI = "fd9f6733-72f4-499f-8506-86b2bdd0dea1"
125132
OptimizationOptimJL = "36348300-93cb-4f02-beb5-3c3902f8871e"
133+
OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed"
126134
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
127135
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
128136
ReferenceTests = "324d217c-45ce-50fc-942e-d289b448e8cf"
@@ -136,4 +144,4 @@ Sundials = "c3572dad-4567-51f8-b174-8c6c989267f4"
136144
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
137145

138146
[targets]
139-
test = ["AmplNLWriter", "BenchmarkTools", "ControlSystemsBase", "NonlinearSolve", "ForwardDiff", "Ipopt", "Ipopt_jll", "ModelingToolkitStandardLibrary", "Optimization", "OptimizationOptimJL", "OptimizationMOI", "Random", "ReferenceTests", "SafeTestsets", "StableRNGs", "Statistics", "SteadyStateDiffEq", "Test", "StochasticDiffEq", "Sundials", "StochasticDelayDiffEq", "Pkg"]
147+
test = ["AmplNLWriter", "BenchmarkTools", "ControlSystemsBase", "DelayDiffEq", "NonlinearSolve", "ForwardDiff", "Ipopt", "Ipopt_jll", "ModelingToolkitStandardLibrary", "Optimization", "OptimizationOptimJL", "OptimizationMOI", "OrdinaryDiffEq", "Random", "ReferenceTests", "SafeTestsets", "StableRNGs", "Statistics", "SteadyStateDiffEq", "Test", "StochasticDiffEq", "Sundials", "StochasticDelayDiffEq", "Pkg", "JET"]

docs/Project.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Optimization = "7f7a1694-90dd-40f0-9382-eb1efda571ba"
1313
OptimizationOptimJL = "36348300-93cb-4f02-beb5-3c3902f8871e"
1414
OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed"
1515
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
16+
SciMLStructures = "53ae85a6-f571-4167-b2af-e1d143709226"
1617
StochasticDiffEq = "789caeaf-c7a9-5a7d-9973-96adeb23e2a0"
1718
SymbolicIndexingInterface = "2efcf032-c050-4f8e-a9bb-153293bab1f5"
1819
SymbolicUtils = "d1185830-fcd6-423d-90d6-eec64667417b"
@@ -27,14 +28,15 @@ Distributions = "0.25"
2728
Documenter = "1"
2829
DynamicQuantities = "^0.11.2, 0.12"
2930
ModelingToolkit = "8.33, 9"
30-
NonlinearSolve = "0.3, 1, 2, 3"
31+
NonlinearSolve = "3"
3132
Optim = "1.7"
3233
Optimization = "3.9"
3334
OptimizationOptimJL = "0.1"
3435
OrdinaryDiffEq = "6.31"
3536
Plots = "1.36"
37+
SciMLStructures = "1.1"
3638
StochasticDiffEq = "6"
3739
SymbolicIndexingInterface = "0.3.1"
38-
SymbolicUtils = "1"
40+
SymbolicUtils = "2"
3941
Symbolics = "5"
4042
Unitful = "1.12"

docs/pages.jl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ pages = [
1717
"Basic Examples" => Any["examples/higher_order.md",
1818
"examples/spring_mass.md",
1919
"examples/modelingtoolkitize_index_reduction.md",
20-
"examples/parsing.md"],
20+
"examples/parsing.md",
21+
"examples/remake.md"],
2122
"Advanced Examples" => Any["examples/tearing_parallelism.md",
2223
"examples/sparse_jacobians.md",
2324
"examples/perturbation.md"]],
@@ -30,6 +31,7 @@ pages = [
3031
"basics/MTKModel_Connector.md",
3132
"basics/Validation.md",
3233
"basics/DependencyGraphs.md",
34+
"basics/Precompilation.md",
3335
"basics/FAQ.md"],
3436
"System Types" => Any["systems/ODESystem.md",
3537
"systems/SDESystem.md",

docs/src/basics/FAQ.md

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ p, replace, alias = SciMLStructures.canonicalize(Tunable(), prob.p)
186186

187187
This error can come up after running `structural_simplify` on a system that generates dummy derivatives (i.e. variables with `ˍt`). For example, here even though all the variables are defined with initial values, the `ODEProblem` generation will throw an error that defaults are missing from the variable map.
188188

189-
```
189+
```julia
190190
using ModelingToolkit
191191
using ModelingToolkit: t_nounits as t, D_nounits as D
192192

@@ -197,13 +197,13 @@ eqs = [x1 + x2 + 1 ~ 0
197197
2 * D(D(x1)) + D(D(x2)) + D(D(x3)) + D(x4) + 4 ~ 0]
198198
@named sys = ODESystem(eqs, t)
199199
sys = structural_simplify(sys)
200-
prob = ODEProblem(sys, [], (0,1))
200+
prob = ODEProblem(sys, [], (0, 1))
201201
```
202202

203203
We can solve this problem by using the `missing_variable_defaults()` function
204204

205-
```
206-
prob = ODEProblem(sys, ModelingToolkit.missing_variable_defaults(sys), (0,1))
205+
```julia
206+
prob = ODEProblem(sys, ModelingToolkit.missing_variable_defaults(sys), (0, 1))
207207
```
208208

209209
This function provides 0 for the default values, which is a safe assumption for dummy derivatives of most models. However, the 2nd argument allows for a different default value or values to be used if needed.
@@ -221,12 +221,26 @@ julia> ModelingToolkit.missing_variable_defaults(sys, [1,2,3])
221221
Use the `u0_constructor` keyword argument to map an array to the desired
222222
container type. For example:
223223

224-
```
224+
```julia
225225
using ModelingToolkit, StaticArrays
226226
using ModelingToolkit: t_nounits as t, D_nounits as D
227227

228-
sts = @variables x1(t)=0.0
228+
sts = @variables x1(t) = 0.0
229229
eqs = [D(x1) ~ 1.1 * x1]
230230
@mtkbuild sys = ODESystem(eqs, t)
231-
prob = ODEProblem{false}(sys, [], (0,1); u0_constructor = x->SVector(x...))
231+
prob = ODEProblem{false}(sys, [], (0, 1); u0_constructor = x -> SVector(x...))
232+
```
233+
234+
## Using a custom independent variable
235+
236+
When possible, we recommend `using ModelingToolkit: t_nounits as t, D_nounits as D` as the independent variable and its derivative.
237+
However, if you want to use your own, you can do so:
238+
239+
```julia
240+
using ModelingToolkit
241+
242+
@independent_variables x
243+
D = Differential(x)
244+
@variables y(x)
245+
@named sys = ODESystem([D(y) ~ x], x)
232246
```

docs/src/basics/MTKModel_Connector.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ Let's explore these in more detail with the following example:
3939

4040
```@example mtkmodel-example
4141
using ModelingToolkit
42+
using ModelingToolkit: t
4243
4344
@mtkmodel ModelA begin
4445
@parameters begin
@@ -191,6 +192,7 @@ getdefault(model_c3.model_a.k_array[2])
191192

192193
```@example mtkmodel-example
193194
using ModelingToolkit
195+
using ModelingToolkit: t
194196
195197
@mtkmodel M begin
196198
@parameters begin
@@ -262,6 +264,7 @@ A simple connector can be defined with syntax similar to following example:
262264

263265
```@example connector
264266
using ModelingToolkit
267+
using ModelingToolkit: t
265268
266269
@connector Pin begin
267270
v(t) = 0.0, [description = "Voltage"]
@@ -344,6 +347,7 @@ The if-elseif-else statements can be used inside `@equations`, `@parameters`,
344347

345348
```@example branches-in-components
346349
using ModelingToolkit
350+
using ModelingToolkit: t
347351
348352
@mtkmodel C begin end
349353
@@ -404,12 +408,13 @@ The conditional parts are reflected in the `structure`. For `BranchOutsideTheBlo
404408

405409
```julia
406410
julia> BranchOutsideTheBlock.structure
407-
Dict{Symbol, Any} with 6 entries:
411+
Dict{Symbol, Any} with 7 entries:
408412
:components => Any[(:if, :flag, Vector{Union{Expr, Symbol}}[[:sys1, :C]], Any[])]
409413
:kwargs => Dict{Symbol, Dict}(:flag=>Dict{Symbol, Bool}(:value=>1))
410414
:structural_parameters => Dict{Symbol, Dict}(:flag=>Dict{Symbol, Bool}(:value=>1))
411415
:independent_variable => t
412-
:parameters => Dict{Symbol, Dict{Symbol, Any}}(:a2 => Dict(:type => AbstractArray{Real}, :condition => (:if, :flag, Dict{Symbol, Any}(:kwargs => Dict{Any, Any}(:a1 => Dict{Symbol, Union{Nothing, DataType}}(:value => nothing, :type => Real)), :parameters => Any[Dict{Symbol, Dict{Symbol, Any}}(:a1 => Dict(:type => AbstractArray{Real}))]), Dict{Symbol, Any}(:variables => Any[Dict{Symbol, Dict{Symbol, Any}}()], :kwargs => Dict{Any, Any}(:a2 => Dict{Symbol, Union{Nothing, DataType}}(:value => nothing, :type => Real)), :parameters => Any[Dict{Symbol, Dict{Symbol, Any}}(:a2 => Dict(:type => AbstractArray{Real}))]))), :a1 => Dict(:type => AbstractArray{Real}, :condition => (:if, :flag, Dict{Symbol, Any}(:kwargs => Dict{Any, Any}(:a1 => Dict{Symbol, Union{Nothing, DataType}}(:value => nothing, :type => Real)), :parameters => Any[Dict{Symbol, Dict{Symbol, Any}}(:a1 => Dict(:type => AbstractArray{Real}))]), Dict{Symbol, Any}(:variables => Any[Dict{Symbol, Dict{Symbol, Any}}()], :kwargs => Dict{Any, Any}(:a2 => Dict{Symbol, Union{Nothing, DataType}}(:value => nothing, :type => Real)), :parameters => Any[Dict{Symbol, Dict{Symbol, Any}}(:a2 => Dict(:type => AbstractArray{Real}))]))))
416+
:parameters => Dict{Symbol, Dict{Symbol, Any}}(:a2 => Dict(:type=>AbstractArray{Real}, :condition=>(:if, :flag, Dict{Symbol, Any}(:kwargs=>Dict{Any, Any}(:a1=>Dict{Symbol, Union{Nothing, DataType}}(:value=>nothing, :type=>Real)), :parameters=>Any[Dict{Symbol, Dict{Symbol, Any}}(:a1=>Dict(:type=>AbstractArray{Real}))]), Dict{Symbol, Any}(:variables=>Any[Dict{Symbol, Dict{Symbol, Any}}()], :kwargs=>Dict{Any, Any}(:a2=>Dict{Symbol, Union{Nothing, DataType}}(:value=>nothing, :type=>Real)), :parameters=>Any[Dict{Symbol, Dict{Symbol, Any}}(:a2=>Dict(:type=>AbstractArray{Real}))]))), :a1 => Dict(:type=>AbstractArray{Real}, :condition=>(:if, :flag, Dict{Symbol, Any}(:kwargs=>Dict{Any, Any}(:a1=>Dict{Symbol, Union{Nothing, DataType}}(:value=>nothing, :type=>Real)), :parameters=>Any[Dict{Symbol, Dict{Symbol, Any}}(:a1=>Dict(:type=>AbstractArray{Real}))]), Dict{Symbol, Any}(:variables=>Any[Dict{Symbol, Dict{Symbol, Any}}()], :kwargs=>Dict{Any, Any}(:a2=>Dict{Symbol, Union{Nothing, DataType}}(:value=>nothing, :type=>Real)), :parameters=>Any[Dict{Symbol, Dict{Symbol, Any}}(:a2=>Dict(:type=>AbstractArray{Real}))]))))
417+
:defaults => Dict{Symbol, Any}(:a1=>10)
413418
:equations => Any[(:if, :flag, ["a1 ~ 0"], ["a2 ~ 0"])]
414419
```
415420

docs/src/basics/Precompilation.md

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
# Working with Precompilation and Binary Building
2+
3+
## tl;dr, I just want precompilation to work
4+
5+
The tl;dr is, if you want to make precompilation work then instead of
6+
7+
```julia
8+
ODEProblem(sys, u0, tspan, p)
9+
```
10+
11+
use:
12+
13+
```julia
14+
ODEProblem(sys, u0, tspan, p, eval_module = @__MODULE__, eval_expression = true)
15+
```
16+
17+
As a full example, here's an example of a module that would precompile effectively:
18+
19+
```julia
20+
module PrecompilationMWE
21+
using ModelingToolkit
22+
23+
@variables x(ModelingToolkit.t_nounits)
24+
@named sys = ODESystem([ModelingToolkit.D_nounits(x) ~ -x + 1], ModelingToolkit.t_nounits)
25+
prob = ODEProblem(structural_simplify(sys), [x => 30.0], (0, 100), [],
26+
eval_expression = true, eval_module = @__MODULE__)
27+
28+
end
29+
```
30+
31+
If you use that in your package's code then 99% of the time that's the right answer to get
32+
precompilation working.
33+
34+
## I'm doing something fancier and need a bit more of an explanation
35+
36+
Oh you dapper soul, time for the bigger explanation. Julia's `eval` function evaluates a
37+
function into a module at a specified world-age. If you evaluate a function within a function
38+
and try to call it from within that same function, you will hit a world-age error. This looks like:
39+
40+
```julia
41+
function worldageerror()
42+
f = eval(:((x) -> 2x))
43+
f(2)
44+
end
45+
```
46+
47+
```
48+
julia> worldageerror()
49+
ERROR: MethodError: no method matching (::var"#5#6")(::Int64)
50+
51+
Closest candidates are:
52+
(::var"#5#6")(::Any) (method too new to be called from this world context.)
53+
@ Main REPL[12]:2
54+
```
55+
56+
This is done for many reasons, in particular if the code that is called within a function could change
57+
at any time, then Julia functions could not ever properly optimize because the meaning of any function
58+
or dispatch could always change and you would lose performance by guarding against that. For a full
59+
discussion of world-age, see [this paper](https://arxiv.org/abs/2010.07516).
60+
61+
However, this would be greatly inhibiting to standard ModelingToolkit usage because then something as
62+
simple as building an ODEProblem in a function and then using it would get a world age error:
63+
64+
```julia
65+
function wouldworldage()
66+
prob = ODEProblem(sys, [], (0.0, 1.0))
67+
sol = solve(prob)
68+
end
69+
```
70+
71+
The reason is because `prob.f` would be constructed via `eval`, and thus `prob.f` could not be called
72+
in the function, which means that no solve could ever work in the same function that generated the
73+
problem. That does mean that:
74+
75+
```julia
76+
function wouldworldage()
77+
prob = ODEProblem(sys, [], (0.0, 1.0))
78+
end
79+
sol = solve(prob)
80+
```
81+
82+
is fine, or putting
83+
84+
```julia
85+
prob = ODEProblem(sys, [], (0.0, 1.0))
86+
sol = solve(prob)
87+
```
88+
89+
at the top level of a module is perfectly fine too. They just cannot happen in the same function.
90+
91+
This would be a major limitation to ModelingToolkit, and thus we developed
92+
[RuntimeGeneratedFunctions](https://github.com/SciML/RuntimeGeneratedFunctions.jl) to get around
93+
this limitation. It will not be described beyond that, it is dark art and should not be investigated.
94+
But it does the job. But that does mean that it plays... oddly with Julia's compilation.
95+
96+
There are ways to force RuntimeGeneratedFunctions to perform their evaluation and caching within
97+
a given module, but that is not recommended because it does not play nicely with Julia v1.9's
98+
introduction of package images for binary caching.
99+
100+
Thus when trying to make things work with precompilation, we recommend using `eval`. This is
101+
done by simply adding `eval_expression=true` to the problem constructor. However, this is not
102+
a silver bullet because the moment you start using eval, all potential world-age restrictions
103+
apply, and thus it is recommended this is simply used for evaluating at the top level of modules
104+
for the purpose of precompilation and ensuring binaries of your MTK functions are built correctly.
105+
106+
However, there is one caveat that `eval` in Julia works depending on the module that it is given.
107+
If you have `MyPackage` that you are precompiling into, or say you are using `juliac` or PackageCompiler
108+
or some other static ahead-of-time (AOT) Julia compiler, then you don't want to accidentally `eval`
109+
that function to live in ModelingToolkit and instead want to make sure it is `eval`'d to live in `MyPackage`
110+
(since otherwise it will not cache into the binary). ModelingToolkit cannot know that in advance, and thus
111+
you have to pass in the module you wish for the functions to "live" in. This is done via the `eval_module`
112+
argument.
113+
114+
Hence `ODEProblem(sys, u0, tspan, p, eval_module=@__MODULE__, eval_expression=true)` will work if you
115+
are running this expression in the scope of the module you wish to be precompiling. However, if you are
116+
attempting to AOT compile a different module, this means that `eval_module` needs to be appropriately
117+
chosen. And, because `eval_expression=true`, all caveats of world-age apply.

0 commit comments

Comments
 (0)