Skip to content

Commit a485499

Browse files
Merge pull request #4050 from SciML/as/compats
docs: add release notes
2 parents c3bd84a + f98ce67 commit a485499

File tree

2 files changed

+356
-0
lines changed

2 files changed

+356
-0
lines changed

NEWS.md

Lines changed: 354 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,357 @@
1+
# ModelingToolkit v11 Release Notes
2+
3+
## Symbolics@7 and SymbolicUtils@4 compatibility
4+
5+
SymbolicUtils version 4 involved a major overhaul of the core symbolic infrastructure, which
6+
propagated to Symbolics as Symbolics version 7. ModelingToolkit has now updated to these versions.
7+
This includes significant type-stability improvements, enabling precompilation of large parts
8+
of the symbolic infrastructure and faster TTFX. It is highly recommended to read the
9+
[Release Notes for SymbolicUtils@4](https://github.com/JuliaSymbolics/SymbolicUtils.jl/releases/tag/v4.0.0)
10+
and the [doc page](https://docs.sciml.ai/SymbolicUtils/dev/manual/variants/) describing the new
11+
variant structure before these release notes.
12+
13+
As part of these changes, ModelingToolkit has changed how some data is represented to allow
14+
precompilation. Notably, `variable => value` mappings (such as guesses) are stored as an
15+
`AbstractDict{SymbolicT, SymbolicT}`. Here, `SymbolicT` is a type that comes from Symbolics.jl,
16+
and is the type for all unwrapped symbolic values. This means that any non-symbolic values
17+
are stored as `SymbolicUtils.Const` variants. Mutation such as `guesses(sys)[x] = 1.0` is still
18+
possible, and values are automatically converted. However, obtaining the value back requires
19+
usage of `SymbolicUtils.unwrap_const` or `Symbolics.value`.
20+
21+
Following is a before/after comparison of the TTFX for the most common operations in ModelingToolkit.jl.
22+
Further improvements are ongoing. Note that the timings do depend on many factors such as the exact system
23+
used, types passed to constructor functions, other packages currently loaded in the session, presence of
24+
array variables/equations, whether index reduction is required, and the behavior of various passes in
25+
`mtkcompile`. However, the numbers are good representations of the kinds of performance improvements
26+
that are possible due to the new infrastructure. There will continue to be improvements as this gets
27+
more extensive testing and we are better able to identify bottlenecks in compilation.
28+
29+
### `System` constructor
30+
31+
The time to call `System`, not including the time taken for `@variables` or building the equations.
32+
33+
Before:
34+
35+
```
36+
0.243758 seconds (563.80 k allocations: 30.613 MiB, 99.48% compilation time: 3% of which was recompilation)
37+
elapsed time (ns): 2.43757958e8
38+
gc time (ns): 0
39+
bytes allocated: 32099616
40+
pool allocs: 563137
41+
non-pool GC allocs: 16
42+
malloc() calls: 651
43+
free() calls: 0
44+
minor collections: 0
45+
full collections: 0
46+
```
47+
48+
After:
49+
50+
```
51+
0.000670 seconds (217 allocations: 10.641 KiB)
52+
elapsed time (ns): 669875.0
53+
gc time (ns): 0
54+
bytes allocated: 10896
55+
pool allocs: 217
56+
non-pool GC allocs: 0
57+
minor collections: 0
58+
full collections: 0
59+
```
60+
61+
### `complete`
62+
63+
Before:
64+
65+
```
66+
1.795140 seconds (9.76 M allocations: 506.143 MiB, 2.67% gc time, 99.75% compilation time: 71% of which was recompilation)
67+
elapsed time (ns): 1.795140083e9
68+
gc time (ns): 47998414
69+
bytes allocated: 530729216
70+
pool allocs: 9747214
71+
non-pool GC allocs: 111
72+
malloc() calls: 10566
73+
free() calls: 8069
74+
minor collections: 5
75+
full collections: 1
76+
```
77+
78+
After:
79+
80+
```
81+
0.001191 seconds (1.08 k allocations: 2.554 MiB)
82+
elapsed time (ns): 1.190625e6
83+
gc time (ns): 0
84+
bytes allocated: 2678088
85+
pool allocs: 1077
86+
non-pool GC allocs: 0
87+
malloc() calls: 3
88+
free() calls: 0
89+
minor collections: 0
90+
full collections: 0
91+
```
92+
93+
### `TearingState` constructor
94+
95+
`TearingState` is an intermediary step in `mtkcompile`. It is significant enough for the impact
96+
to be worth measuring separately.
97+
98+
Before:
99+
100+
```
101+
0.374312 seconds (527.01 k allocations: 32.318 MiB, 24.13% gc time, 99.60% compilation time: 85% of which was recompilation)
102+
elapsed time (ns): 3.74312e8
103+
gc time (ns): 90318708
104+
bytes allocated: 33888248
105+
pool allocs: 526440
106+
non-pool GC allocs: 11
107+
malloc() calls: 555
108+
free() calls: 2923
109+
minor collections: 1
110+
full collections: 0
111+
```
112+
113+
After:
114+
115+
```
116+
0.002062 seconds (1.07 k allocations: 8.546 MiB, 50.24% compilation time)
117+
elapsed time (ns): 2.0618339999999998e6
118+
gc time (ns): 0
119+
bytes allocated: 8961560
120+
pool allocs: 1064
121+
non-pool GC allocs: 0
122+
malloc() calls: 6
123+
free() calls: 0
124+
minor collections: 0
125+
full collections: 0
126+
```
127+
128+
### `mtkcompile`
129+
130+
This measures the time taken by the first call to `mtkcompile`. This is run after the `TearingState`
131+
benchmark, and hence the compile time from that aspect of the process is not included (runtime is
132+
included).
133+
134+
Before:
135+
136+
```
137+
1.772756 seconds (3.81 M allocations: 206.068 MiB, 0.63% gc time, 99.71% compilation time: 71% of which was recompilation)
138+
elapsed time (ns): 1.772755875e9
139+
gc time (ns): 11162292
140+
bytes allocated: 216077752
141+
pool allocs: 3808615
142+
non-pool GC allocs: 61
143+
malloc() calls: 4877
144+
free() calls: 4844
145+
minor collections: 2
146+
full collections: 0
147+
```
148+
149+
After:
150+
151+
```
152+
0.018629 seconds (20.74 k allocations: 932.062 KiB, 89.89% compilation time)
153+
elapsed time (ns): 1.8628542e7
154+
gc time (ns): 0
155+
bytes allocated: 954432
156+
pool allocs: 20727
157+
non-pool GC allocs: 0
158+
malloc() calls: 13
159+
free() calls: 0
160+
minor collections: 0
161+
full collections: 0
162+
```
163+
164+
## Semantic separation of discretes
165+
166+
ModelingToolkit has long overloaded the meaning of `@parameters` to the point that it means
167+
"anything that isn't `@variables`." This isn't a very intuitive or clear definition. This is
168+
now improved with the introduction of `@discretes`. Any quantities that vary on a different
169+
time-scale than those in `@variables` are now `@discretes`. `@parameters` can only be used to
170+
create "time-independent parameters". For clarity, the following continues to work:
171+
172+
```julia
173+
@parameters p q[1:3] f(::Real, ::Real)
174+
```
175+
176+
However, this is now disallowed:
177+
178+
```julia
179+
@parameters value(t)
180+
```
181+
182+
Instead, it must be declared as:
183+
184+
```julia
185+
@discretes value(t)
186+
```
187+
188+
And can be passed along with the `@variables`. Essentially, for time-varying systems
189+
the constructor syntax is
190+
191+
```julia
192+
System(equations, independent_variable, time_varying_variables, constant_values, [brownians])
193+
```
194+
195+
In the subsequent release notes and in documentation, "variables" refers to either `@variables`
196+
or `@discretes` unless explicitly mentioned otherwise.
197+
198+
An important note is that while this is a difference in declaration, the semantics are defined
199+
by their usage in the system. More concretely, a variable declared via `@discretes` is only
200+
actually considered discrete if it is part of the variables updated in a callback in the system.
201+
Otherwise, it is treated identically to a variable declared via `@variables`.
202+
203+
## Changes to `defaults` and initialization semantics
204+
205+
The concept of `defaults` is a relic of earlier ModelingToolkit versions, from when initialization
206+
did not exist and they served as convenient initial conditions. The package has evolved greatly since then
207+
and `defaults` have taken on many different meanings in different contexts. This makes their usage
208+
complicated and unintuitive.
209+
210+
`defaults` have now been removed. They are replaced by two new concepts, with simple and well-defined
211+
semantics. Firstly, `initial_conditions` is a variable-value mapping aimed solely at being a convenient
212+
way to provide initial conditions to `SciMLProblem`s constructed from the system. Specifying them is
213+
identical to providing initial values to the `ODEProblem` constructor. Secondly, `bindings` is an
214+
immutable variable-value mapping representing strong constraints between variables/parameters.
215+
A binding for a variable is a function of other variables/parameters that is enforced during initialization.
216+
A binding for a parameter is a function of other parameters that exclusively defines the value of that
217+
parameter. Bound variables or parameters cannot be given initial conditions, either through the
218+
`initial_conditions` keyword or by passing them to the problem constructor. In effect, bindings
219+
serve to mark specific variables as aliases of others during initialization, and parameters as aliases
220+
of other parameters. This supersedes the previous concept of parameter bindings, and explicit parameter
221+
equations passed along with the equations of the model. Since bound parameters are computed as functions
222+
of other parameters, they are treated akin to observed variables. They are not stored in the parameter
223+
object, and instead are computed on the fly as required.
224+
225+
Sometimes, it is useful to enforce a relation between parameters while allowing them to be given initial
226+
values. For example, one might relate the radius `r` and area `A` of a pipe as `A ~ pi * r * r`. Users of
227+
the model should be able to provide a value for either `r` or `A`, and the other should be calculated
228+
automatically. This is done by providing the relation `A ~ pi * r * r` to the `initialization_eqs`
229+
keyword of the model and binding both `A` and `r` to `missing`. Similar to v10, the equation represents
230+
a constraint to be enforced. The bindings act similar to the `missing` defaults in v9 and v10, indicating
231+
that the parameters are to be solved for. They are part of bindings since a parameter to be solved for
232+
cannot be an alias for a different value. As such, the choice of parameters that can be solved for is
233+
an immutable property of the system. Note that making a parameter solvable no longer requires specifying a
234+
guess. If a guess is required to solve the initialization, ModelingToolkit will error with an informative
235+
message during problem construction. Note that since parameters can only be bound to other parameters,
236+
a parameter `x0` can be bound to the initial value of a variable `x` using the binding `x0 = Initial(x)`.
237+
238+
The formulation of the initialization system can now be summarized succinctly. The system solves for:
239+
240+
- Unknowns of the system.
241+
- Observables (observed variables) of the system.
242+
- All unknowns for which derivatives are known (differential variables, and ones for which derivative
243+
information is available due to the index reduction process).
244+
- Discrete variables (created via `@discretes`).
245+
- Parameters with a binding of `missing`.
246+
247+
It is composed of:
248+
249+
- Algebraic equations.
250+
- Observed equations.
251+
- Initialization equations.
252+
- The `initial_conditions` of the system.
253+
- Initial conditions passed to the problem constructor. These override values in `initial_conditions`
254+
for the same variable.
255+
256+
Additionally, `Initial` parameters exist for the following variables:
257+
258+
- Unknowns
259+
- Observables
260+
- First derivatives of all unknowns and observables
261+
- Discrete variables
262+
- Parameters with a binding of `missing`
263+
264+
"Defaults" specified via variable metadata are now translated into either `initial_conditions` or
265+
`bindings` depending on the value. If the value is a constant, it is part of `initial_conditions`.
266+
If it is an expression involving other variables/parameters, it is part of `bindings`. For example,
267+
the following are `initial_conditions`:
268+
269+
```julia
270+
@variables x(t) = 1 y(t)[1:3] = zeros(3)
271+
@parameters f(::Real) = sin
272+
```
273+
274+
The following are bindings:
275+
276+
```julia
277+
@variables z(t) = x w(t)[1:2] = [1.5, z]
278+
@parameters p[1:3] = f(3)
279+
```
280+
281+
Notably, arrays are considered atomic. This means that if even one element of an array default is
282+
symbolic, the entire array variable is considered bound. Partial bindings can be constructed by
283+
destructuring the array:
284+
285+
```julia
286+
@parameters par[1:3] = [par1, par2, par3]
287+
```
288+
289+
Where `par1`, `par2` and `par3` can independently have initial conditions or bindings. In a
290+
similar vein, `guesses`, `initial_conditions` and `bindings` are all stored in special
291+
`AbstractDict` types that disallow scalarized keys. For example, `par[1]` cannot be a key
292+
of these dictionaries. `par` is allowed as a key. Initial values can still be given to the
293+
problem constructor in scalarized form.
294+
295+
As mentioned previously, bindings cannot be mutated. To change the bindings of a system,
296+
the following pattern can be employed:
297+
298+
```julia
299+
binds = bindings(sys)
300+
# the `ReadOnlyDict` wrapper uses `Base.parent` to get the underlying mutable container
301+
new_binds = parent(copy(binds))
302+
303+
# mutate `new_binds`...
304+
305+
using Setfield: @set!
306+
307+
@set! sys.bindings = new_binds
308+
sys = complete(sys) # Important!
309+
```
310+
311+
Mutation of bindings without copying them is undefined behavior and can lead to unpredictable bugs.
312+
313+
## Array variables as inputs
314+
315+
Previously, ModelingToolkit allowed part of an array variable to be an input. For example, the following
316+
used to be valid:
317+
318+
```julia
319+
@variables u(t)[1:2] [input = true]
320+
@named sys = # Some system involving `u`
321+
322+
sys = mtkcompile(sys; inputs = [u[1]])
323+
```
324+
325+
This is now disallowed. `mtkcompile` will throw an informative error if part of an array is passed as an
326+
input.
327+
328+
## Deprecation of `@mtkmodel`
329+
330+
The `@mtkmodel` originated as a convenient DSL for creating models. However, it has not received the same
331+
level of support as other features due to the complexity of the parsing. It is also a major source of bugs,
332+
and is thus a tripping hazard for new and old users alike. The macro is now deprecated. It is moved to a new
333+
package, SciCompDSL.jl. Enough updates have been made to allow it to create systems in v11, but it will not
334+
receive more maintenance from the core developers. It is, however, still open to community contribution. For
335+
more details, please refer to the discussion in [this Discourse thread](https://discourse.julialang.org/t/using-mtk-when-i-import-modelingtoolkit/133681/12).
336+
337+
## Splitting into `ModelingToolkitBase` and relicensing of parts of ModelingToolkit
338+
339+
The advanced structural simplification algorithms in ModelingToolkit, such as index reduction, structural
340+
singularity removal and tearing, are now moved to the [StateSelection.jl](https://github.com/JuliaComputing/StateSelection.jl/)
341+
and ModelingToolkitTearing.jl (`lib/ModelingToolkitTearing` in the same repo) packages. These packages are
342+
AGPL licensed. ModelingToolkitBase.jl contains the `System` representation, callbacks, all of the code-generation
343+
targets (problem constructors), and initialization infrastructure. Items that depend on structural simplification
344+
and/or require highly specialized code generation (`SCCNonlinearProblem` prominent among them) have been moved
345+
to ModelingToolkit.jl, which depends on the aforementioned AGPL packages. ModelingToolkitBase still contains
346+
a simple version of `mtkcompile` suitable for most use cases. It does not perform index reduction and requires
347+
that all differential equations are explicit in the derivative. However, it does have a simpler tearing algorithm
348+
that is capable of identifying observed equations in many scenarios. In fact, it is also able to reduce many
349+
systems created using modular components and the `connect` infrastructure. Contributions to improve this
350+
are also welcome.
351+
352+
For more information on the split and surrounding changes, please follow the discussion in
353+
[this Discourse thread](https://discourse.julialang.org/t/modelingtoolkit-v11-library-split-and-licensing-community-feedback-requested/134396).
354+
1355
# ModelingToolkit v10 Release Notes
2356

3357
## Callbacks

Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ LinearSolve = "3.19.2"
8686
Logging = "1"
8787
ModelingToolkitBase = "1"
8888
ModelingToolkitStandardLibrary = "2.20"
89+
ModelingToolkitTearing = "1"
8990
Moshi = "0.3"
9091
NonlinearSolve = "4.3"
9192
OffsetArrays = "1"
@@ -106,6 +107,7 @@ Serialization = "1"
106107
Setfield = "0.7, 0.8, 1"
107108
SimpleNonlinearSolve = "0.1.0, 1, 2"
108109
SparseArrays = "1"
110+
StateSelection = "1"
109111
StaticArrays = "1.9.14"
110112
StochasticDelayDiffEq = "1.11"
111113
StochasticDiffEq = "6.82.0"

0 commit comments

Comments
 (0)