Skip to content

Commit 7f9a153

Browse files
authored
Merge pull request #63 from SciML/smc/update
Update to MTK@v10 and simplify connectors
2 parents a14c987 + 8421403 commit 7f9a153

File tree

11 files changed

+129
-127
lines changed

11 files changed

+129
-127
lines changed

.github/workflows/Downgrade.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,14 @@ jobs:
1616
test:
1717
runs-on: ${{ matrix.os }}
1818
env:
19-
GROUP: ${{ matrix.group }}
19+
GROUP: ${{ matrix.group }}
2020
strategy:
2121
fail-fast: false
2222
matrix:
2323
group:
2424
- Core
2525
version:
26-
- '1'
26+
- '1.10'
2727
os:
2828
- ubuntu-latest
2929
- macos-latest
@@ -40,4 +40,4 @@ jobs:
4040
with:
4141
token: ${{ secrets.GITHUB_TOKEN }}
4242
- uses: julia-actions/julia-buildpkg@v1
43-
- uses: julia-actions/julia-runtest@v1
43+
- uses: julia-actions/julia-runtest@v1

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# ModelingToolkitNeuralNets v2
2+
3+
## Breaking changes
4+
5+
- The `NeuralNetworkBlock` no longer uses `RealInputArray` & `RealOutputArray`,
6+
the ports are now `inputs` and `outputs` and they are normal vector variables.

Project.toml

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,41 @@
11
name = "ModelingToolkitNeuralNets"
22
uuid = "f162e290-f571-43a6-83d9-22ecc16da15f"
33
authors = ["Sebastian Micluța-Câmpeanu <[email protected]> and contributors"]
4-
version = "1.7.0"
4+
version = "2.0.0"
55

66
[deps]
77
ComponentArrays = "b0b7db55-cfe3-40fc-9ded-d10e2dbeff66"
88
IntervalSets = "8197267c-284f-5f27-9208-e0e47529a953"
99
Lux = "b2108857-7c20-44ae-9111-449ecde12c47"
1010
LuxCore = "bb33d45b-7691-41d6-9220-0943567d0623"
1111
ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78"
12-
ModelingToolkitStandardLibrary = "16a59e39-deab-5bd0-87e4-056b12336739"
1312
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
1413
Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7"
1514

1615
[compat]
1716
Aqua = "0.8"
18-
ComponentArrays = "0.15.11"
19-
DifferentiationInterface = "0.6"
20-
ForwardDiff = "0.10.36"
17+
ComponentArrays = "0.15.28"
18+
DifferentiationInterface = "0.6, 0.7"
19+
ForwardDiff = "0.10.36, 1"
2120
IntervalSets = "0.7.10"
22-
JET = "0.8, 0.9"
23-
Lux = "1"
24-
LuxCore = "1"
25-
ModelingToolkit = "9.64"
26-
ModelingToolkitStandardLibrary = "2.7"
27-
Optimization = "3.24, 4"
28-
OptimizationOptimisers = "0.2.1, 0.3"
29-
OrdinaryDiffEq = "6.74"
21+
JET = "0.8, 0.9, 0.10"
22+
Lux = "1.14"
23+
LuxCore = "1.2"
24+
ModelingToolkit = "10"
25+
ModelingToolkitStandardLibrary = "2.24"
26+
Optimization = "4"
27+
OptimizationOptimisers = "0.3"
28+
OrdinaryDiffEqVerner = "1.2"
3029
Random = "1.10"
3130
SafeTestsets = "0.1"
3231
SciMLSensitivity = "7.72"
3332
SciMLStructures = "1.1.0"
3433
StableRNGs = "1"
35-
SymbolicIndexingInterface = "0.3.15"
36-
Symbolics = "6.36"
34+
Statistics = "1.10"
35+
SymbolicIndexingInterface = "0.3.41"
36+
Symbolics = "6.43"
3737
Test = "1.10"
38-
Zygote = "0.6.73"
38+
Zygote = "0.6.73, 0.7"
3939
julia = "1.10"
4040

4141
[extras]
@@ -45,14 +45,19 @@ ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
4545
JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b"
4646
Optimization = "7f7a1694-90dd-40f0-9382-eb1efda571ba"
4747
OptimizationOptimisers = "42dfb2eb-d2b4-4451-abcd-913932933ac1"
48-
OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed"
48+
OrdinaryDiffEqVerner = "79d7bb75-1356-48c1-b8c0-6832512096c2"
49+
ModelingToolkitStandardLibrary = "16a59e39-deab-5bd0-87e4-056b12336739"
4950
SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f"
5051
SciMLSensitivity = "1ed8b502-d754-442c-8d5d-10ac956f44a1"
5152
SciMLStructures = "53ae85a6-f571-4167-b2af-e1d143709226"
5253
StableRNGs = "860ef19b-820b-49d6-a774-d7a799459cd3"
54+
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
5355
SymbolicIndexingInterface = "2efcf032-c050-4f8e-a9bb-153293bab1f5"
5456
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
5557
Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f"
5658

5759
[targets]
58-
test = ["Aqua", "JET", "Test", "OrdinaryDiffEq", "DifferentiationInterface", "SciMLSensitivity", "Zygote", "ForwardDiff", "Optimization", "OptimizationOptimisers", "SafeTestsets", "SciMLStructures", "StableRNGs", "SymbolicIndexingInterface"]
60+
test = ["Aqua", "JET", "Test", "OrdinaryDiffEqVerner", "DifferentiationInterface",
61+
"SciMLSensitivity", "Zygote", "ForwardDiff", "ModelingToolkitStandardLibrary",
62+
"Optimization", "OptimizationOptimisers", "SafeTestsets", "SciMLStructures",
63+
"StableRNGs", "Statistics", "SymbolicIndexingInterface"]

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,11 @@ ModelingToolkitNeuralNets.jl is a package to create neural network blocks define
1212
## Tutorials and Documentation
1313

1414
For information on using the package, [see the stable documentation](https://docs.sciml.ai/ModelingToolkitNeuralNets/stable/). Use the [in-development documentation](https://docs.sciml.ai/ModelingToolkitNeuralNets/dev/) for the version of the documentation, which contains the unreleased features.
15+
16+
## Breaking changes in v2
17+
18+
The `NeuralNetworkBlock` no longer uses `RealInputArray` & `RealOutputArray`,
19+
the ports are now `inputs` and `outputs` and they are normal vector variables.
20+
This simplifies the usage a bit and removes the need for the ModelingToolkitStandardLibrary dependency.
21+
22+
This version also moves to ModelingToolkit@v10.

docs/Project.toml

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,17 @@ OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed"
1010
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
1111
SciMLStructures = "53ae85a6-f571-4167-b2af-e1d143709226"
1212
StableRNGs = "860ef19b-820b-49d6-a774-d7a799459cd3"
13+
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
1314
SymbolicIndexingInterface = "2efcf032-c050-4f8e-a9bb-153293bab1f5"
1415

16+
[sources]
17+
ModelingToolkitNeuralNets = {path = ".."}
18+
1519
[compat]
1620
Documenter = "1.3"
1721
Lux = "1"
18-
ModelingToolkit = "9.9"
19-
ModelingToolkitNeuralNets = "1"
22+
ModelingToolkit = "10"
23+
ModelingToolkitNeuralNets = "2"
2024
ModelingToolkitStandardLibrary = "2.7"
2125
Optimization = "3.24, 4.0"
2226
OptimizationOptimisers = "0.2.1, 0.3"
@@ -25,6 +29,3 @@ Plots = "1"
2529
SciMLStructures = "1.1.0"
2630
StableRNGs = "1"
2731
SymbolicIndexingInterface = "0.3.15"
28-
29-
[sources]
30-
ModelingToolkitNeuralNets = { path = ".." }

docs/src/api.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@
22

33
```@docs
44
NeuralNetworkBlock
5+
SymbolicNeuralNetwork
56
```

docs/src/friction.md

Lines changed: 30 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ using OptimizationOptimisers: Adam
1818
using SciMLStructures
1919
using SciMLStructures: Tunable
2020
using SymbolicIndexingInterface
21+
using Statistics
2122
using StableRNGs
2223
using Lux
2324
using Plots
@@ -49,16 +50,16 @@ function friction_true()
4950
eqs = [
5051
Dt(y) ~ Fu - friction(y)
5152
]
52-
return ODESystem(eqs, t, name = :friction_true)
53+
return System(eqs, t, name = :friction_true)
5354
end
5455
```
5556

5657
Now that we have defined the model, we will simulate it from 0 to 0.1 seconds.
5758

5859
```@example friction
59-
model_true = structural_simplify(friction_true())
60-
prob_true = ODEProblem(model_true, [], (0, 0.1), [])
61-
sol_ref = solve(prob_true, Rodas4(); saveat = 0.001)
60+
model_true = mtkcompile(friction_true())
61+
prob_true = ODEProblem(model_true, [], (0, 0.1))
62+
sol_ref = solve(prob_true, Vern7(); saveat = 0.001)
6263
```
6364

6465
Let's plot it.
@@ -81,28 +82,23 @@ Now, we will try to learn the same friction model using a neural network. We wil
8182
function friction_ude(Fu)
8283
@variables y(t) = 0.0
8384
@constants Fu = Fu
84-
@named nn_in = RealInputArray(nin = 1)
85-
@named nn_out = RealOutputArray(nout = 1)
86-
eqs = [Dt(y) ~ Fu - nn_in.u[1]
87-
y ~ nn_out.u[1]]
88-
return ODESystem(eqs, t, name = :friction, systems = [nn_in, nn_out])
89-
end
9085
91-
Fu = 120.0
92-
model = friction_ude(Fu)
86+
chain = Lux.Chain(
87+
Lux.Dense(1 => 10, Lux.mish, use_bias = false),
88+
Lux.Dense(10 => 10, Lux.mish, use_bias = false),
89+
Lux.Dense(10 => 1, use_bias = false)
90+
)
91+
@named nn = NeuralNetworkBlock(1, 1; chain = chain, rng = StableRNG(1111))
9392
94-
chain = Lux.Chain(
95-
Lux.Dense(1 => 10, Lux.mish, use_bias = false),
96-
Lux.Dense(10 => 10, Lux.mish, use_bias = false),
97-
Lux.Dense(10 => 1, use_bias = false)
98-
)
99-
@named nn = NeuralNetworkBlock(1, 1; chain = chain, rng = StableRNG(1111))
93+
eqs = [Dt(y) ~ Fu - nn.outputs[1]
94+
y ~ nn.inputs[1]]
95+
return System(eqs, t, name = :friction, systems = [nn])
96+
end
10097
101-
eqs = [connect(model.nn_in, nn.output)
102-
connect(model.nn_out, nn.input)]
98+
Fu = 120.0
10399
104-
ude_sys = complete(ODESystem(eqs, t, systems = [model, nn], name = :ude_sys))
105-
sys = structural_simplify(ude_sys)
100+
ude_sys = friction_ude(Fu)
101+
sys = mtkcompile(ude_sys)
106102
```
107103

108104
## Optimization Setup
@@ -114,22 +110,19 @@ function loss(x, (prob, sol_ref, get_vars, get_refs, set_x))
114110
new_p = set_x(prob, x)
115111
new_prob = remake(prob, p = new_p, u0 = eltype(x).(prob.u0))
116112
ts = sol_ref.t
117-
new_sol = solve(new_prob, Rodas4(), saveat = ts, abstol = 1e-8, reltol = 1e-8)
118-
loss = zero(eltype(x))
119-
for i in eachindex(new_sol.u)
120-
loss += sum(abs2.(get_vars(new_sol, i) .- get_refs(sol_ref, i)))
121-
end
113+
new_sol = solve(new_prob, Vern7(), saveat = ts, abstol = 1e-8, reltol = 1e-8)
114+
122115
if SciMLBase.successful_retcode(new_sol)
123-
loss
116+
mean(abs2.(reduce(hcat, get_vars(new_sol)) .- reduce(hcat, get_refs(sol_ref))))
124117
else
125118
Inf
126119
end
127120
end
128121
129-
of = OptimizationFunction{true}(loss, AutoForwardDiff())
122+
of = OptimizationFunction(loss, AutoForwardDiff())
130123
131-
prob = ODEProblem(sys, [], (0, 0.1), [])
132-
get_vars = getu(sys, [sys.friction.y])
124+
prob = ODEProblem(sys, [], (0, 0.1))
125+
get_vars = getu(sys, [sys.y])
133126
get_refs = getu(model_true, [model_true.y])
134127
set_x = setp_oop(sys, sys.nn.p)
135128
x0 = default_values(sys)[sys.nn.p]
@@ -150,31 +143,31 @@ We now have a trained neural network! We can check whether running the simulatio
150143
```@example friction
151144
res_p = set_x(prob, res.u)
152145
res_prob = remake(prob, p = res_p)
153-
res_sol = solve(res_prob, Rodas4(), saveat = sol_ref.t)
146+
res_sol = solve(res_prob, Vern7(), saveat = sol_ref.t)
154147
@test first.(sol_ref.u)≈first.(res_sol.u) rtol=1e-3 #hide
155-
@test friction.(first.(sol_ref.u))≈(getindex.(res_sol[sys.nn.output.u], 1)) rtol=1e-1 #hide
148+
@test friction.(first.(sol_ref.u))≈(getindex.(res_sol[sys.nn.outputs], 1)) rtol=1e-1 #hide
156149
nothing #hide
157150
```
158151

159152
Also, it would be interesting to check the simulation before the training to get an idea of the starting point of the network.
160153

161154
```@example friction
162-
initial_sol = solve(prob, Rodas4(), saveat = sol_ref.t)
155+
initial_sol = solve(prob, Vern7(), saveat = sol_ref.t)
163156
```
164157

165158
Now we plot it.
166159

167160
```@example friction
168161
scatter(sol_ref, idxs = [model_true.y], label = "ground truth velocity")
169-
plot!(res_sol, idxs = [sys.friction.y], label = "velocity after training")
170-
plot!(initial_sol, idxs = [sys.friction.y], label = "velocity before training")
162+
plot!(res_sol, idxs = [sys.y], label = "velocity after training")
163+
plot!(initial_sol, idxs = [sys.y], label = "velocity before training")
171164
```
172165

173166
It matches the data well! Let's also check the predictions for the friction force and whether the network learnt the friction model or not.
174167

175168
```@example friction
176169
scatter(sol_ref.t, friction.(first.(sol_ref.u)), label = "ground truth friction")
177-
plot!(res_sol.t, getindex.(res_sol[sys.nn.output.u], 1),
170+
plot!(res_sol.t, getindex.(res_sol[sys.nn.outputs], 1),
178171
label = "friction from neural network")
179172
```
180173

docs/src/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ Pkg.add("ModelingToolkitNeuralNets")
2323

2424
- See the [SciML Style Guide](https://github.com/SciML/SciMLStyle) for common coding practices and other style decisions.
2525
- There are a few community forums:
26-
26+
2727
+ The #diffeq-bridged and #sciml-bridged channels in the
2828
[Julia Slack](https://julialang.org/slack/)
2929
+ The #diffeq-bridged and #sciml-bridged channels in the

src/ModelingToolkitNeuralNets.jl

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
module ModelingToolkitNeuralNets
22

3-
using ModelingToolkit: @parameters, @named, ODESystem, t_nounits
3+
using ModelingToolkit: @parameters, @named, @variables, System, t_nounits
44
using IntervalSets: var".."
5-
using ModelingToolkitStandardLibrary.Blocks: RealInputArray, RealOutputArray
65
using Symbolics: Symbolics, @register_array_symbolic, @wrapped
7-
using LuxCore: stateless_apply
6+
using LuxCore: stateless_apply, outputsize
87
using Lux: Lux
98
using Random: Xoshiro
109
using ComponentArrays: ComponentArray
@@ -21,7 +20,7 @@ include("utils.jl")
2120
eltype = Float64,
2221
name)
2322
24-
Create an `ODESystem` with a neural network inside.
23+
Create a component neural network as a `System`.
2524
"""
2625
function NeuralNetworkBlock(; n_input = 1, n_output = 1,
2726
chain = multi_layer_feed_forward(n_input, n_output),
@@ -31,19 +30,21 @@ function NeuralNetworkBlock(; n_input = 1, n_output = 1,
3130
name)
3231
ca = ComponentArray{eltype}(init_params)
3332

34-
@parameters p[1:length(ca)] = Vector(ca)
33+
@parameters p[1:length(ca)]=Vector(ca) [tunable = true]
3534
@parameters T::typeof(typeof(ca))=typeof(ca) [tunable = false]
36-
@parameters lux_model::typeof(chain) = chain
35+
@parameters lux_model::typeof(chain)=chain [tunable = false]
3736

38-
@named input = RealInputArray(nin = n_input)
39-
@named output = RealOutputArray(nout = n_output)
37+
@variables inputs(t_nounits)[1:n_input] [input = true]
38+
@variables outputs(t_nounits)[1:n_output] [output = true]
4039

41-
out = stateless_apply(lux_model, input.u, lazyconvert(T, p))
40+
expected_outsz = only(outputsize(chain, inputs, rng))
41+
msg = "The outputsize of the given Lux network ($expected_outsz) does not match `n_output = $n_output`"
42+
@assert n_output==expected_outsz msg
4243

43-
eqs = [output.u ~ out]
44+
eqs = [outputs ~ stateless_apply(lux_model, inputs, lazyconvert(T, p))]
4445

45-
ude_comp = ODESystem(
46-
eqs, t_nounits, [], [lux_model, p, T]; systems = [input, output], name)
46+
ude_comp = System(
47+
eqs, t_nounits, [inputs, outputs], [lux_model, p, T]; name)
4748
return ude_comp
4849
end
4950

0 commit comments

Comments
 (0)