Skip to content

Commit 234cddf

Browse files
authored
Merge pull request #303 from JuliaDynamics/hw/mtk10take2
bump MTK to version 10, take 2
2 parents 158fa82 + 0531375 commit 234cddf

File tree

14 files changed

+306
-77
lines changed

14 files changed

+306
-77
lines changed

.github/workflows/tests.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,6 @@ jobs:
161161
with:
162162
version: ${{ matrix.version }}
163163
arch: ${{ matrix.arch }}
164-
- uses: julia-actions/julia-buildpkg@v1
165164
- name: Load Julia packages from cache
166165
id: julia-cache
167166
uses: julia-actions/cache@v2
@@ -202,6 +201,9 @@ jobs:
202201
using Pkg
203202
using Test
204203
pkg"dev ."
204+
# pkg"update"
205+
pkg"instantiate"
206+
pkg"build"
205207
@testset "PowerDynamics Downstream Tests" begin
206208
@testset "Normal Package tests" begin
207209
Pkg.test("PowerDynamics"; coverage=false)

NEWS.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
11
# NetworkDynamics Release Notes
22

3+
## v0.10.4 Changelog
4+
- [#303](https://github.com/JuliaDynamics/NetworkDynamics.jl/pull/303) update for ModelingToolkit.jl v10 compatibility:
5+
- rename all `ODESystem` -> `System` (follows MTK v10 API)
6+
- MTK extension now uses `mtkcompile` instead of `structural_simplify` internally
7+
- Add new `implicit_output` function to handle fully implicit output variables in MTK models
8+
- Add documentation for handling fully implicit outputs in MTK integration
9+
- Update minimum ModelingToolkit.jl requirement from v9.67 to v10
10+
311
## v0.10.3 Changelog
412
- [#301](https://github.com/JuliaDynamics/NetworkDynamics.jl/pull/301) improve callback system performance and flexibility:
5-
- Add callback batching for better DiscreteComponentCallback performance
13+
- Add callback batching for better DiscreteComponentCallback performance
614
- Allow `EIndex(1=>2)` as standalone edge index with relaxed type constraints
715
- Optimize CallbackSet construction to prevent performance bottlenecks
816
- Add important documentation warning about parameter array copying in callbacks

Project.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "NetworkDynamics"
22
uuid = "22e9dc34-2a0d-11e9-0de0-8588d035468b"
33
authors = ["Frank Hellmann <[email protected]>, Michael Lindner <[email protected]>, Hans Würfel <[email protected]"]
4-
version = "0.10.3"
4+
version = "0.10.4"
55

66
[deps]
77
ArgCheck = "dce04be8-c92d-5529-be00-80e4d2c0e197"
@@ -67,7 +67,7 @@ KernelAbstractions = "0.9.18"
6767
LinearAlgebra = "1"
6868
MacroTools = "0.5.15"
6969
Mixers = "0.1.2"
70-
ModelingToolkit = "9.82"
70+
ModelingToolkit = "10"
7171
NNlib = "0.9.13"
7272
NonlinearSolve = "4"
7373
OrderedCollections = "1.8.0"

docs/examples/gas_network.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ As a workaround we had to explicitly define `LinearInterpolations` as unitless,
233233
234234
To build the network, we first need to define the components. This is a two-step process:
235235
236-
- first create the symbolic `ODESystem` using ModelingToolkit
236+
- first create the symbolic `System` using ModelingToolkit
237237
- secondly build a NetworkDynamics component model ([`VertexModel`](@ref)/[`EdgeModel`](@ref)) based on the symbolic system.
238238
239239
In the first step we can use the keyword arguments to pass "default" values for our parameters and states.

docs/examples/init_tutorial.jl

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,15 +61,19 @@ end
6161
nothing #hide
6262

6363
#=
64-
**B) A static prosumer node which forces a certain flow (pressure is fully implicit)**
64+
**B) A static prosumer node which forces a certain flow**
65+
66+
!!! note "Fully Implicit Output"
67+
We need to use `implicit_output(p)` to handle the fully implicit pressure
68+
output. See [fully implicit outputs](@ref Fully-Implicit-Outputs) for details.
6569
=#
6670
@mtkmodel StaticProsumerNode begin
6771
@extend GasNode()
6872
@parameters begin
6973
q̃_prosumer, [description="flow injected by prosumer"]
7074
end
7175
@equations begin
72-
-q̃_nw ~ q̃_prosumer
76+
-q̃_nw ~ q̃_prosumer + implicit_output(p)
7377
end
7478
end
7579
nothing #hide

docs/src/API.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ EdgeModel()
1818

1919
## Component Models with MTK
2020
```@docs
21-
VertexModel(::ModelingToolkit.ODESystem, ::Any, ::Any)
22-
EdgeModel(::ModelingToolkit.ODESystem, ::Any, ::Any, ::Any, ::Any)
23-
EdgeModel(::ModelingToolkit.ODESystem, ::Any, ::Any, ::Any)
21+
VertexModel(::ModelingToolkit.System, ::Any, ::Any)
22+
EdgeModel(::ModelingToolkit.System, ::Any, ::Any, ::Any, ::Any)
23+
EdgeModel(::ModelingToolkit.System, ::Any, ::Any, ::Any)
2424
```
2525

2626
### Output Function Helpers/Wrappers
@@ -245,6 +245,7 @@ save_parameters!
245245
ff_to_constraint
246246
Base.copy(::NetworkDynamics.ComponentModel)
247247
extract_nw
248+
implicit_output
248249
```
249250

250251
## NetworkDynamicsInspector API

docs/src/metadata.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ Special cases for symbol metadata are:
3333

3434
For those, there are special functions `has_*`, `get_*`, `set_*!`, `delete_*!` and `strip_*!`. The `strip_*!` functions remove all metadata of a specific type from all symbols in a component. See [Per Symbol Metadata API](@ref).
3535

36-
These are closely aligned with the [metadata use in ModelingToolkit](@extref ModelingToolkit symbolic_metadata). They are automatically copied from the `ODESystem` if you use MTK models to create NetworkDynamics models.
36+
These are closely aligned with the [metadata use in ModelingToolkit](@extref ModelingToolkit symbolic_metadata). They are automatically copied from the `System` if you use MTK models to create NetworkDynamics models.
3737

3838
## Metadata Utils
3939
Accessing metadata (especially defaults) of states and parameters is a very

docs/src/mtk_integration.md

Lines changed: 71 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@ which are then connected on network scale using NetworkDynamics.
66

77
The main entry point for this interop are the constructors
88
```julia
9-
VertexModel(::ODESystem, inputs, outputs)
10-
EdgeModel(::ODESystem, srcin, dstin, [srscout], dstout)
9+
VertexModel(::System, inputs, outputs)
10+
EdgeModel(::System, srcin, dstin, [srscout], dstout)
1111
```
1212
whose docstrings can be found in the [Component Models with MTK](@ref) section in the API.
1313

1414
These constructors will:
15-
- transform the states marked as input to parameters and `structural_simplify`ing the system,
15+
- transform the states marked as input to parameters and `mtkcompile`ing the system,
1616
- generate the `f` and `g` functions,
1717
- generate code for observables,
1818
- port all supported [Metadata](@ref) from MTK symbols to component symbols and
@@ -43,7 +43,7 @@ The system to model is 2 node, 1 edge network. The node output states are the vo
4343
4444
ideal v source Resistor Capacitor
4545
v1 o─←────MMM────→─o v2
46-
│ ┴
46+
│ ┴
4747
(↗) ┬
4848
│ │
4949
⏚ ⏚
@@ -76,7 +76,7 @@ An ideal voltage source is just a model which pins its output voltage to a fixed
7676
The source ejects whatever current is necessary. We introduce another variable `i(t)`
7777
to "capture" this current. This variable will be removed during structural simplify, but will
7878
be available for plotting through the [Observables](@ref) mechanism.
79-
The `VertexModel` can be generated from an `ODESystem` by providing names of the input and output states:
79+
The `VertexModel` can be generated from an `System` by providing names of the input and output states:
8080

8181
```@example mtk
8282
@mtkmodel VoltageSource begin
@@ -140,7 +140,7 @@ resistor_edge = EdgeModel(resistor, [:src₊v], [:dst₊v], [:src₊i], [:dst₊
140140

141141
Having all those components defined, we can build the network. We don't need to provide a graph
142142
object here because we specified the placement in the graph on a per component basis.
143-
143+
144144
```@example mtk
145145
nw = Network([vs_vertex, cap_vertex], [resistor_edge])
146146
```
@@ -149,7 +149,7 @@ We can see, that NetworkDynamics internally is able to reduce all of the "output
149149

150150
Now we can simulate the system. For that we generate the `u0` object. Since the metadata (such as default values) was automatically transferred, we can straight away construct the `ODEProblem`
151151
and solve the system.
152-
152+
153153
```@example mtk
154154
u0 = NWState(nw) # generate state based on default values
155155
prob = ODEProblem(nw, uflat(u0), (0, 10.0), pflat(u0))
@@ -164,3 +164,67 @@ axislegend(ax2)
164164
fig # hide
165165
```
166166

167+
## Fully Implicit Outputs
168+
When working with MTK systems in NetworkDynamics, you may encounter situations where
169+
your desired output variables don't explicitly appear in the equations. This creates **fully
170+
implicit outputs** - variables that are determined by the system's constraints but aren't
171+
directly computed.
172+
173+
!!! tip "tl;dr"
174+
Introduce "fake" dependencies to your input-forcing equations `0 ~ in + implicit_output(y)`.
175+
Which is mathematically equivalent to `0 ~ in` but helps MTK to reason about dependencies.
176+
177+
Consider a system with a fully implicit output:
178+
```
179+
u┌───────┐y
180+
─→┤ 0 ~ u ├→─
181+
└───────┘
182+
```
183+
Here, $y$ does not appear in the equations at all. In general, that doesn't make too much sense.
184+
During simplification, MTK will potentially get rid of the equation as it does not contribute to the system's state.
185+
186+
However, in NetworkDynamics, we're always dealing with **open loop models** on the equation level, which is not exactly what MTK was made for.
187+
If you build a closed loop between a subsystem A which **has input forcing** and a subsystem
188+
B which has **input feed forward**, the resulting system can be solved:
189+
```
190+
(system with input forcing)
191+
ua┌─────────┐ya
192+
┌──→┤ 0 ~ ua ├→──┐
193+
│ └─────────┘ │
194+
│ yb┌─────────┐ub │
195+
└──←┤ yb ~ ub ├←──┘
196+
└─────────┘
197+
(system with input feed forward)
198+
```
199+
200+
Since MTK does not know about the closed loop (which is only introduced on the NetworkDynamics level once we leave the equation based domain) we need to help MTK to figure out those dependencies.
201+
We can do so by introducing "fake" dependencies using [`implicit_output`](@ref).
202+
This function is defined as
203+
```julia
204+
implicit_output(x) = 0
205+
ModelingToolkit.@register_symbolic implicit_output(x)
206+
```
207+
which makes it numerically equivalent to zero (no effect on the simulation) but is
208+
opaque to the Symbolic Simplification.
209+
210+
### Example
211+
212+
Consider a "Kirchhoff Node" between multiple resistors:
213+
- the currents through the resistors directly depend on the voltage output of the node (input feed forward) and
214+
- the Kirchhoff node requires the sum of all inflowing currents to be zero (input forcing).
215+
216+
We can model this type of node like this:
217+
```@example mtk
218+
@mtkmodel KirchhoffNode begin
219+
@variables begin
220+
v(t), [description="Node voltage", output=true]
221+
i_sum(t), [description="Sum of incoming currents", input=true]
222+
end
223+
@equations begin
224+
0 ~ i_sum + implicit_output(v) # Kirchhoff's current law
225+
end
226+
end
227+
@named kirchhoff = KirchhoffNode()
228+
VertexModel(kirchhoff, [:i_sum], [:v])
229+
```
230+
where we "trick" MTK into believing that the input forcing equation depends on the output too.

ext/MTKExt_utils.jl

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ function warn_missing_features(sys)
152152
cev = ModelingToolkit.get_continuous_events(sys)
153153
dev = ModelingToolkit.get_discrete_events(sys)
154154
if !isempty(cev) || !isempty(dev)
155-
@warn "Model has attached events, which is not supportet."
155+
@warn "Model has attached events, which is not supported."
156156
end
157157

158158
if !isempty(ModelingToolkit.initialization_equations(sys))
@@ -215,3 +215,16 @@ function fix_metadata!(invalid_eqs, sys)
215215
end
216216
invalid_eqs .= fixedeqs
217217
end
218+
219+
function remove_implicit_output_fn!(eqs)
220+
r = SymbolicUtils.@rule implicit_output(~~x) => 0
221+
chain = SymbolicUtils.Chain([r])
222+
rewriter = SymbolicUtils.Prewalk(chain)
223+
224+
for i in eachindex(eqs)
225+
eq = eqs[i]
226+
eqs[i] = rewriter(eq.lhs) ~ rewriter(eq.rhs)
227+
end
228+
229+
eqs
230+
end

0 commit comments

Comments
 (0)