Skip to content

Commit f97204a

Browse files
Merge pull request #19 from SciML/sb/docs
Add Friction tutorial
2 parents bec8951 + b70f2cc commit f97204a

File tree

10 files changed

+230
-17
lines changed

10 files changed

+230
-17
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ JET = "0.8"
2020
Lux = "0.5.32"
2121
LuxCore = "0.1.14"
2222
ModelingToolkit = "9.9"
23-
ModelingToolkitStandardLibrary = "2.6"
23+
ModelingToolkitStandardLibrary = "2.7"
2424
Optimization = "3.24"
2525
OptimizationOptimisers = "0.2.1"
2626
OrdinaryDiffEq = "6.74"

docs/Project.toml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,27 @@
11
[deps]
22
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
3+
Lux = "b2108857-7c20-44ae-9111-449ecde12c47"
4+
ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78"
5+
ModelingToolkitStandardLibrary = "16a59e39-deab-5bd0-87e4-056b12336739"
6+
Optimization = "7f7a1694-90dd-40f0-9382-eb1efda571ba"
7+
OptimizationOptimisers = "42dfb2eb-d2b4-4451-abcd-913932933ac1"
8+
OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed"
9+
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
10+
SciMLStructures = "53ae85a6-f571-4167-b2af-e1d143709226"
11+
StableRNGs = "860ef19b-820b-49d6-a774-d7a799459cd3"
12+
SymbolicIndexingInterface = "2efcf032-c050-4f8e-a9bb-153293bab1f5"
313
ModelingToolkitNeuralNets = "f162e290-f571-43a6-83d9-22ecc16da15f"
414

515
[compat]
616
Documenter = "1.3"
17+
Lux = "0.5.32"
18+
ModelingToolkit = "9.9"
19+
ModelingToolkitStandardLibrary = "2.7"
20+
Optimization = "3.24"
21+
OptimizationOptimisers = "0.2.1"
22+
OrdinaryDiffEq = "6.74"
23+
Plots = "1"
24+
SciMLStructures = "1.1.0"
25+
StableRNGs = "1"
26+
SymbolicIndexingInterface = "0.3.15"
27+
ModelingToolkitNeuralNets = "1"

docs/make.jl

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,29 @@
11
using ModelingToolkitNeuralNets
22
using Documenter
3+
ENV["GKSwstype"] = "100"
4+
ENV["JULIA_DEBUG"] = "Documenter"
35

46
cp("./docs/Manifest.toml", "./docs/src/assets/Manifest.toml", force = true)
57
cp("./docs/Project.toml", "./docs/src/assets/Project.toml", force = true)
68

7-
DocMeta.setdocmeta!(ModelingToolkitNeuralNets, :DocTestSetup, :(using ModelingToolkitNeuralNets); recursive = true)
9+
DocMeta.setdocmeta!(ModelingToolkitNeuralNets, :DocTestSetup,
10+
:(using ModelingToolkitNeuralNets); recursive = true)
811

912
makedocs(;
1013
modules = [ModelingToolkitNeuralNets],
1114
authors = "Sebastian Micluța-Câmpeanu <[email protected]> and contributors",
1215
sitename = "ModelingToolkitNeuralNets.jl",
13-
format = Documenter.HTML(;
14-
canonical = "https://SciML.github.io/ModelingToolkitNeuralNets.jl",
15-
edit_link = "main",
16-
assets = String[]
17-
),
16+
format = Documenter.HTML(assets = ["assets/favicon.ico"],
17+
canonical = "https://docs.sciml.ai/ModelingToolkitNeuralNets.jl/stable/"),
18+
clean = true,
19+
doctest = false,
20+
linkcheck = true,
1821
pages = [
19-
"Home" => "index.md"
22+
"Home" => "index.md",
23+
"Tutorials" => [
24+
"Friction Model" => "friction.md"
25+
],
26+
"API" => "api.md"
2027
]
2128
)
2229

docs/src/api.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# API
2+
3+
```@docs
4+
NeuralNetworkBlock
5+
```

docs/src/assets/favicon.ico

1.36 KB
Binary file not shown.

docs/src/assets/logo.png

26 KB
Loading

docs/src/friction.md

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
# Modeling Non Linear Friction Model using UDEs
2+
3+
Friction between moving bodies is not trivial to model. There have been idealised linear models which are not always useful in complicated systems. There have been many theories and non linear models which we can use, but they are not perfect. The aim of this tutorial to use Universal Differential Equations to showcase how we can embed a neural network to learn an unknown non linear friction model.
4+
5+
## Julia Environment
6+
7+
First, lets import the required packages.
8+
9+
```@example friction
10+
using ModelingToolkitNeuralNets
11+
using ModelingToolkit
12+
import ModelingToolkit.t_nounits as t
13+
import ModelingToolkit.D_nounits as Dt
14+
using ModelingToolkitStandardLibrary.Blocks
15+
using OrdinaryDiffEq
16+
using Optimization
17+
using OptimizationOptimisers: Adam
18+
using SciMLStructures
19+
using SciMLStructures: Tunable
20+
using SymbolicIndexingInterface
21+
using StableRNGs
22+
using Lux
23+
using Plots
24+
using Test #hide
25+
```
26+
27+
## Problem Setup
28+
29+
Let's use the friction model presented in https://www.mathworks.com/help/simscape/ref/translationalfriction.html for generating data.
30+
31+
```@example friction
32+
Fbrk = 100.0
33+
vbrk = 10.0
34+
Fc = 80.0
35+
vst = vbrk / 10
36+
vcol = vbrk * sqrt(2)
37+
function friction(v)
38+
sqrt(2 * MathConstants.e) * (Fbrk - Fc) * exp(-(v / vst)^2) * (v / vst) +
39+
Fc * tanh(v / vcol)
40+
end
41+
```
42+
43+
Next, we define the model - an object sliding in 1D plane with a constant force `Fu` acting on it and friction force opposing the motion.
44+
45+
```@example friction
46+
function friction_true()
47+
@variables y(t) = 0.0
48+
@constants Fu = 120.0
49+
eqs = [
50+
Dt(y) ~ Fu - friction(y)
51+
]
52+
return ODESystem(eqs, t, name = :friction_true)
53+
end
54+
```
55+
56+
Now that we have defined the model, we will simulate it from 0 to 0.1 seconds.
57+
58+
```@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)
62+
```
63+
64+
Let's plot it.
65+
66+
```@example friction
67+
scatter(sol_ref, label = "velocity")
68+
```
69+
70+
That was the velocity. Let's also plot the friction force acting on the object throughout the simulation.
71+
72+
```@example friction
73+
scatter(sol_ref.t, friction.(first.(sol_ref.u)), label = "friction force")
74+
```
75+
76+
## Model Setup
77+
78+
Now, we will try to learn the same friction model using a neural network. We will use [`NeuralNetworkBlock`](@ref) to define neural network as a component. The input of the neural network is the velocity and the output is the friction force. We connect the neural network with the model using `RealInputArray` and `RealOutputArray` blocks.
79+
80+
```@example friction
81+
function friction_ude(Fu)
82+
@variables y(t) = 0.0
83+
@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
90+
91+
Fu = 120.0
92+
model = friction_ude(Fu)
93+
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+
nn = NeuralNetworkBlock(1, 1; chain = chain, rng = StableRNG(1111))
100+
101+
eqs = [connect(model.nn_in, nn.output)
102+
connect(model.nn_out, nn.input)]
103+
104+
ude_sys = complete(ODESystem(eqs, t, systems = [model, nn], name = :ude_sys))
105+
sys = structural_simplify(ude_sys)
106+
```
107+
108+
## Optimization Setup
109+
110+
We now setup the loss function and the optimization loop.
111+
112+
```@example friction
113+
function loss(x, (prob, sol_ref, get_vars, get_refs))
114+
new_p = SciMLStructures.replace(Tunable(), prob.p, x)
115+
new_prob = remake(prob, p = new_p, u0 = eltype(x).(prob.u0))
116+
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
122+
if SciMLBase.successful_retcode(new_sol)
123+
loss
124+
else
125+
Inf
126+
end
127+
end
128+
129+
of = OptimizationFunction{true}(loss, AutoForwardDiff())
130+
131+
prob = ODEProblem(sys, [], (0, 0.1), [])
132+
get_vars = getu(sys, [sys.friction.y])
133+
get_refs = getu(model_true, [model_true.y])
134+
x0 = reduce(vcat, getindex.((default_values(sys),), tunable_parameters(sys)))
135+
136+
cb = (opt_state, loss) -> begin
137+
@info "step $(opt_state.iter), loss: $loss"
138+
return false
139+
end
140+
141+
op = OptimizationProblem(of, x0, (prob, sol_ref, get_vars, get_refs))
142+
res = solve(op, Adam(5e-3); maxiters = 10000, callback = cb)
143+
```
144+
145+
## Visualization of results
146+
147+
We now have a trained neural network! We can check whether running the simulation of the model embedded with the neural network matches the data or not.
148+
149+
```@example friction
150+
res_p = SciMLStructures.replace(Tunable(), prob.p, res)
151+
res_prob = remake(prob, p = res_p)
152+
res_sol = solve(res_prob, Rodas4(), saveat = sol_ref.t)
153+
@test first.(sol_ref.u)≈first.(res_sol.u) rtol=1e-3 #hide
154+
@test friction.(first.(sol_ref.u))≈(Fu .- first.(res_sol(res_sol.t, Val{1}).u)) rtol=1e-1 #hide
155+
```
156+
157+
Also, it would be interesting to check the simulation before the training to get an idea of the starting point of the network.
158+
159+
```@example friction
160+
initial_sol = solve(prob, Rodas4(), saveat = sol_ref.t)
161+
```
162+
163+
Now we plot it.
164+
165+
```@example friction
166+
scatter(sol_ref, idxs = [model_true.y], label = "ground truth velocity")
167+
plot!(res_sol, idxs = [sys.friction.y], label = "velocity after training")
168+
plot!(initial_sol, idxs = [sys.friction.y], label = "velocity before training")
169+
```
170+
171+
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.
172+
173+
```@example friction
174+
scatter(sol_ref.t, friction.(first.(sol_ref.u)), label = "ground truth friction")
175+
plot!(res_sol.t, Fu .- first.(res_sol(res_sol.t, Val{1}).u),
176+
label = "friction from neural network")
177+
```
178+
179+
It learns the friction model well!

docs/src/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ CurrentModule = ModelingToolkitNeuralNets
44

55
# ModelingToolkitNeuralNets
66

7-
Documentation for [ModelingToolkitNeuralNets](https://github.com/SebastianM-C/ModelingToolkitNeuralNets.jl).
7+
Documentation for [ModelingToolkitNeuralNets](https://github.com/SciML/ModelingToolkitNeuralNets.jl).
88

99
```@index
1010
```

src/ModelingToolkitNeuralNets.jl

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
module ModelingToolkitNeuralNets
22

33
using ModelingToolkit: @parameters, @named, ODESystem, t_nounits
4-
using ModelingToolkitStandardLibrary.Blocks: RealInput, RealOutput
4+
using ModelingToolkitStandardLibrary.Blocks: RealInputArray, RealOutputArray
55
using Symbolics: Symbolics, @register_array_symbolic, @wrapped
66
using LuxCore: stateless_apply
77
using Lux: Lux
@@ -30,12 +30,12 @@ function NeuralNetworkBlock(n_input = 1,
3030
ca = ComponentArray{eltype}(init_params)
3131

3232
@parameters p[1:length(ca)] = Vector(ca)
33-
@parameters T::typeof(typeof(p))=typeof(p) [tunable = false]
33+
@parameters T::typeof(typeof(ca))=typeof(ca) [tunable = false]
3434

35-
@named input = RealInput(nin = n_input)
36-
@named output = RealOutput(nout = n_output)
35+
@named input = RealInputArray(nin = n_input)
36+
@named output = RealOutputArray(nout = n_output)
3737

38-
out = stateless_apply(chain, input.u, lazyconvert(typeof(ca), p))
38+
out = stateless_apply(chain, input.u, lazyconvert(T, p))
3939

4040
eqs = [output.u ~ out]
4141

test/lotka_volterra.jl

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ function lotka_ude()
1616
@variables t x(t)=3.1 y(t)=1.5
1717
@parameters α=1.3 [tunable = false] δ=1.8 [tunable = false]
1818
Dt = ModelingToolkit.D_nounits
19-
@named nn_in = RealInput(nin = 2)
20-
@named nn_out = RealOutput(nout = 2)
19+
@named nn_in = RealInputArray(nin = 2)
20+
@named nn_out = RealOutputArray(nout = 2)
2121

2222
eqs = [
2323
Dt(x) ~ α * x + nn_in.u[1],
@@ -50,7 +50,8 @@ eqs = [connect(model.nn_in, nn.output)
5050
connect(model.nn_out, nn.input)]
5151

5252
ude_sys = complete(ODESystem(
53-
eqs, ModelingToolkit.t_nounits, systems = [model, nn], name = :ude_sys))
53+
eqs, ModelingToolkit.t_nounits, systems = [model, nn],
54+
name = :ude_sys, defaults = [nn.input.u => [0.0, 0.0]]))
5455

5556
sys = structural_simplify(ude_sys)
5657

0 commit comments

Comments
 (0)