From 853e826dd17912c8ebb253de8ee1523b5bb17655 Mon Sep 17 00:00:00 2001 From: Sathvik Bhagavan Date: Thu, 11 Apr 2024 09:04:07 +0000 Subject: [PATCH 1/8] build(docs): add deps --- docs/Project.toml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/Project.toml b/docs/Project.toml index 79f849e..d886980 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -1,6 +1,27 @@ [deps] Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +Lux = "b2108857-7c20-44ae-9111-449ecde12c47" +ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78" +ModelingToolkitStandardLibrary = "16a59e39-deab-5bd0-87e4-056b12336739" +Optimization = "7f7a1694-90dd-40f0-9382-eb1efda571ba" +OptimizationOptimisers = "42dfb2eb-d2b4-4451-abcd-913932933ac1" +OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed" +Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" +SciMLStructures = "53ae85a6-f571-4167-b2af-e1d143709226" +StableRNGs = "860ef19b-820b-49d6-a774-d7a799459cd3" +SymbolicIndexingInterface = "2efcf032-c050-4f8e-a9bb-153293bab1f5" ModelingToolkitNeuralNets = "f162e290-f571-43a6-83d9-22ecc16da15f" [compat] Documenter = "1.3" +Lux = "0.5.32" +ModelingToolkit = "9.9" +ModelingToolkitStandardLibrary = "2.6" +Optimization = "3.24" +OptimizationOptimisers = "0.2.1" +OrdinaryDiffEq = "6.74" +Plots = "1" +SciMLStructures = "1.1.0" +StableRNGs = "1" +SymbolicIndexingInterface = "0.3.15" +ModelingToolkitNeuralNets = "1" From 0eeee0d9ed7fe74f3a9859e0b5d5a6a827a70b6a Mon Sep 17 00:00:00 2001 From: Sathvik Bhagavan Date: Thu, 11 Apr 2024 09:10:42 +0000 Subject: [PATCH 2/8] docs: add friction tutorial --- docs/make.jl | 19 ++-- docs/src/api.md | 5 + docs/src/assets/favicon.ico | Bin 0 -> 1394 bytes docs/src/assets/logo.png | Bin 0 -> 26575 bytes docs/src/friction.md | 178 ++++++++++++++++++++++++++++++++++++ 5 files changed, 195 insertions(+), 7 deletions(-) create mode 100644 docs/src/api.md create mode 100644 docs/src/assets/favicon.ico create mode 100644 docs/src/assets/logo.png create mode 100644 docs/src/friction.md diff --git a/docs/make.jl b/docs/make.jl index 54367ee..2393190 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -4,19 +4,24 @@ using Documenter cp("./docs/Manifest.toml", "./docs/src/assets/Manifest.toml", force = true) cp("./docs/Project.toml", "./docs/src/assets/Project.toml", force = true) -DocMeta.setdocmeta!(ModelingToolkitNeuralNets, :DocTestSetup, :(using ModelingToolkitNeuralNets); recursive = true) +DocMeta.setdocmeta!(ModelingToolkitNeuralNets, :DocTestSetup, + :(using ModelingToolkitNeuralNets); recursive = true) makedocs(; modules = [ModelingToolkitNeuralNets], authors = "Sebastian Micluța-Câmpeanu and contributors", sitename = "ModelingToolkitNeuralNets.jl", - format = Documenter.HTML(; - canonical = "https://SciML.github.io/ModelingToolkitNeuralNets.jl", - edit_link = "main", - assets = String[] - ), + format = Documenter.HTML(assets = ["assets/favicon.ico"], + canonical = "https://docs.sciml.ai/ModelingToolkitNeuralNets.jl/stable/"), + clean = true, + doctest = false, + linkcheck = true, pages = [ - "Home" => "index.md" + "Home" => "index.md", + "Tutorials" => [ + "Friction Model" => "friction.md" + ], + "API" => "api.md" ] ) diff --git a/docs/src/api.md b/docs/src/api.md new file mode 100644 index 0000000..28b49d5 --- /dev/null +++ b/docs/src/api.md @@ -0,0 +1,5 @@ +# API + +```@docs +NeuralNetworkBlock +``` diff --git a/docs/src/assets/favicon.ico b/docs/src/assets/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..3c6bd470373d2cccea2154d70907b0e02ab202c8 GIT binary patch literal 1394 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 literal 0 HcmV?d00001 diff --git a/docs/src/friction.md b/docs/src/friction.md new file mode 100644 index 0000000..d05ca9a --- /dev/null +++ b/docs/src/friction.md @@ -0,0 +1,178 @@ +# Modeling Non Linear Friction Model using UDEs + +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. + +## Julia Environment + +First, lets import the required packages. + +```@example friction +using ModelingToolkitNeuralNets +using ModelingToolkit +import ModelingToolkit.t_nounits as t +import ModelingToolkit.D_nounits as Dt +using ModelingToolkitStandardLibrary.Blocks +using OrdinaryDiffEq +using Optimization +using OptimizationOptimisers: Adam +using SciMLStructures +using SciMLStructures: Tunable +using SymbolicIndexingInterface +using StableRNGs +using Lux +using Plots +``` + +## Problem Setup + +Let's use the friction model presented in https://www.mathworks.com/help/simscape/ref/translationalfriction.html for generating data. + +```@example friction +Fbrk = 100.0 +vbrk = 10.0 +Fc = 80.0 +vst = vbrk / 10 +vcol = vbrk * sqrt(2) +function friction(v) + sqrt(2 * MathConstants.e) * (Fbrk - Fc) * exp(-(v / vst)^2) * (v / vst) + + Fc * tanh(v / vcol) +end +``` + +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. + +```@example friction +function friction_true() + @variables y(t) = 0.0 + @constants Fu = 120.0 + eqs = [ + Dt(y) ~ Fu - friction(y) + ] + return ODESystem(eqs, t, name = :friction_true) +end +``` + +Now that we have defined the model, we will simulate it from 0 to 0.1 seconds. + +```@example friction +model_true = structural_simplify(friction_true()) +prob_true = ODEProblem(model_true, [], (0, 0.1), []) +sol_ref = solve(prob_true, Rodas4(); saveat = 0.001) +``` + +Let's plot it. + +```@example friction +scatter(sol_ref, label = "velocity") +``` + +That was the velocity. Let's also plot the friction force acting on the object throughout the simulation. + +```@example friction +scatter(sol_ref.t, friction.(first.(sol_ref.u)), label = "friction force") +``` + +## Model Setup + +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. + +```@example friction +function friction_ude(Fu) + @variables y(t) = 0.0 + @constants Fu = Fu + @named nn_in = RealInputArray(nin = 1) + @named nn_out = RealOutputArray(nout = 1) + eqs = [Dt(y) ~ Fu - nn_in.u[1] + y ~ nn_out.u[1]] + return ODESystem(eqs, t, name = :friction, systems = [nn_in, nn_out]) +end + +Fu = 120.0 +model = friction_ude(Fu) + +chain = Lux.Chain( + Lux.Dense(1 => 10, Lux.mish, use_bias = false), + Lux.Dense(10 => 10, Lux.mish, use_bias = false), + Lux.Dense(10 => 1, use_bias = false) +) +nn = NeuralNetworkBlock(1, 1; chain = chain, rng = StableRNG(1111)) + +eqs = [connect(model.nn_in, nn.output) + connect(model.nn_out, nn.input)] + +ude_sys = complete(ODESystem(eqs, t, systems = [model, nn], name = :ude_sys)) +sys = structural_simplify(ude_sys) +``` + +## Optimization Setup + +We now setup the loss function and the optimization loop. + +```@example friction +function loss(x, (prob, sol_ref, get_vars, get_refs)) + new_p = SciMLStructures.replace(Tunable(), prob.p, x) + new_prob = remake(prob, p = new_p, u0 = eltype(x).(prob.u0)) + ts = sol_ref.t + new_sol = solve(new_prob, Rodas4(), saveat = ts, abstol = 1e-8, reltol = 1e-8) + loss = zero(eltype(x)) + for i in eachindex(new_sol.u) + loss += sum(abs2.(get_vars(new_sol, i) .- get_refs(sol_ref, i))) + end + if SciMLBase.successful_retcode(new_sol) + loss + else + Inf + end +end + +of = OptimizationFunction{true}(loss, AutoForwardDiff()) + +prob = ODEProblem(sys, [], (0, 0.1), []) +get_vars = getu(sys, [sys.friction.y]) +get_refs = getu(model_true, [model_true.y]) +x0 = reduce(vcat, getindex.((default_values(sys),), tunable_parameters(sys))) + +cb = (opt_state, loss) -> begin + @info "step $(opt_state.iter), loss: $loss" + return false +end + +op = OptimizationProblem(of, x0, (prob, sol_ref, get_vars, get_refs)) +res = solve(op, Adam(5e-3); maxiters = 10000, callback = cb) +``` + +## Visualization of results + +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. + +```@example friction +res_p = SciMLStructures.replace(Tunable(), prob.p, res) +res_prob = remake(prob, p = res_p) +res_sol = solve(res_prob, Rodas4(), saveat = sol_ref.t) +@test first.(sol_ref.u)≈first.(res_sol.u) rtol=1e-3 #hide +@test friction.(first.(sol_ref.u))≈(Fu .- first.(res_sol(res_sol.t, Val{1}).u)) rtol=1e-1 #hide +``` + +Also, it would be interesting to check the simulation before the training to get an idea of the starting point of the network. + +```@example friction +initial_sol = solve(prob, Rodas4(), saveat = sol_ref.t) +``` + +Now we plot it. + +```@example friction +scatter(sol_ref, idxs = [model_true.y], label = "ground truth velocity") +plot!(res_sol, idxs = [sys.friction.y], label = "velocity after training") +plot!(initial_sol, idxs = [sys.friction.y], label = "velocity before training") +``` + +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. + +```@example friction +scatter(sol_ref.t, friction.(first.(sol_ref.u)), label = "ground truth friction") +plot!(res_sol.t, Fu .- first.(res_sol(res_sol.t, Val{1}).u), + label = "friction from neural network") +``` + +It learns the friction model well! From 02041f1f9b50d52518fd0adc4f560a9746982da1 Mon Sep 17 00:00:00 2001 From: Sathvik Bhagavan Date: Thu, 11 Apr 2024 09:03:42 +0000 Subject: [PATCH 3/8] refactor: [temp] add RealInput/RealOutput which works for size equal to 1 --- src/ModelingToolkitNeuralNets.jl | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/ModelingToolkitNeuralNets.jl b/src/ModelingToolkitNeuralNets.jl index b9346ba..3f03c26 100644 --- a/src/ModelingToolkitNeuralNets.jl +++ b/src/ModelingToolkitNeuralNets.jl @@ -1,6 +1,7 @@ module ModelingToolkitNeuralNets -using ModelingToolkit: @parameters, @named, ODESystem, t_nounits +using ModelingToolkit: @parameters, @named, ODESystem, t_nounits, @connector, @variables, + Equation using ModelingToolkitStandardLibrary.Blocks: RealInput, RealOutput using Symbolics: Symbolics, @register_array_symbolic, @wrapped using LuxCore: stateless_apply @@ -12,6 +13,24 @@ export NeuralNetworkBlock, multi_layer_feed_forward include("utils.jl") +@connector function RealInput2(; name, nin = 1, u_start = zeros(nin)) + @variables u(t_nounits)[1:nin]=u_start [ + input = true, + description = "Inner variable in RealInput $name" + ] + u = collect(u) + ODESystem(Equation[], t_nounits, [u...], []; name = name) +end + +@connector function RealOutput2(; name, nout = 1, u_start = zeros(nout)) + @variables u(t_nounits)[1:nout]=u_start [ + output = true, + description = "Inner variable in RealOutput $name" + ] + u = collect(u) + ODESystem(Equation[], t_nounits, [u...], []; name = name) +end + """ NeuralNetworkBlock(n_input = 1, n_output = 1; chain = multi_layer_feed_forward(n_input, n_output), @@ -32,8 +51,8 @@ function NeuralNetworkBlock(n_input = 1, @parameters p[1:length(ca)] = Vector(ca) @parameters T::typeof(typeof(p))=typeof(p) [tunable = false] - @named input = RealInput(nin = n_input) - @named output = RealOutput(nout = n_output) + @named input = RealInput2(nin = n_input) + @named output = RealOutput2(nout = n_output) out = stateless_apply(chain, input.u, lazyconvert(typeof(ca), p)) From 578a56121f91f668bfe67ce01cc580476af62508 Mon Sep 17 00:00:00 2001 From: Sathvik Bhagavan Date: Fri, 19 Apr 2024 07:32:11 +0000 Subject: [PATCH 4/8] refactor: use RealInputArray and RealOutputArray --- src/ModelingToolkitNeuralNets.jl | 31 ++++++------------------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/src/ModelingToolkitNeuralNets.jl b/src/ModelingToolkitNeuralNets.jl index 3f03c26..fd55d09 100644 --- a/src/ModelingToolkitNeuralNets.jl +++ b/src/ModelingToolkitNeuralNets.jl @@ -1,8 +1,7 @@ module ModelingToolkitNeuralNets -using ModelingToolkit: @parameters, @named, ODESystem, t_nounits, @connector, @variables, - Equation -using ModelingToolkitStandardLibrary.Blocks: RealInput, RealOutput +using ModelingToolkit: @parameters, @named, ODESystem, t_nounits +using ModelingToolkitStandardLibrary.Blocks: RealInputArray, RealOutputArray using Symbolics: Symbolics, @register_array_symbolic, @wrapped using LuxCore: stateless_apply using Lux: Lux @@ -13,24 +12,6 @@ export NeuralNetworkBlock, multi_layer_feed_forward include("utils.jl") -@connector function RealInput2(; name, nin = 1, u_start = zeros(nin)) - @variables u(t_nounits)[1:nin]=u_start [ - input = true, - description = "Inner variable in RealInput $name" - ] - u = collect(u) - ODESystem(Equation[], t_nounits, [u...], []; name = name) -end - -@connector function RealOutput2(; name, nout = 1, u_start = zeros(nout)) - @variables u(t_nounits)[1:nout]=u_start [ - output = true, - description = "Inner variable in RealOutput $name" - ] - u = collect(u) - ODESystem(Equation[], t_nounits, [u...], []; name = name) -end - """ NeuralNetworkBlock(n_input = 1, n_output = 1; chain = multi_layer_feed_forward(n_input, n_output), @@ -49,12 +30,12 @@ function NeuralNetworkBlock(n_input = 1, ca = ComponentArray{eltype}(init_params) @parameters p[1:length(ca)] = Vector(ca) - @parameters T::typeof(typeof(p))=typeof(p) [tunable = false] + @parameters T::typeof(typeof(ca))=typeof(ca) [tunable = false] - @named input = RealInput2(nin = n_input) - @named output = RealOutput2(nout = n_output) + @named input = RealInputArray(nin = n_input) + @named output = RealOutputArray(nout = n_output) - out = stateless_apply(chain, input.u, lazyconvert(typeof(ca), p)) + out = stateless_apply(chain, input.u, lazyconvert(T, p)) eqs = [output.u ~ out] From aa625f86c010205a7d30ba3e6cec24abd8d5c56e Mon Sep 17 00:00:00 2001 From: Sathvik Bhagavan Date: Fri, 19 Apr 2024 07:32:58 +0000 Subject: [PATCH 5/8] test: update lv tutorial --- test/lotka_volterra.jl | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/lotka_volterra.jl b/test/lotka_volterra.jl index 3879c64..6b6f7a9 100644 --- a/test/lotka_volterra.jl +++ b/test/lotka_volterra.jl @@ -16,8 +16,8 @@ function lotka_ude() @variables t x(t)=3.1 y(t)=1.5 @parameters α=1.3 [tunable = false] δ=1.8 [tunable = false] Dt = ModelingToolkit.D_nounits - @named nn_in = RealInput(nin = 2) - @named nn_out = RealOutput(nout = 2) + @named nn_in = RealInputArray(nin = 2) + @named nn_out = RealOutputArray(nout = 2) eqs = [ Dt(x) ~ α * x + nn_in.u[1], @@ -50,7 +50,8 @@ eqs = [connect(model.nn_in, nn.output) connect(model.nn_out, nn.input)] ude_sys = complete(ODESystem( - eqs, ModelingToolkit.t_nounits, systems = [model, nn], name = :ude_sys)) + eqs, ModelingToolkit.t_nounits, systems = [model, nn], + name = :ude_sys, defaults = [nn.input.u => [0.0, 0.0]])) sys = structural_simplify(ude_sys) From e7e702e1eb2a4fcd4389b4fbde03d7700b8c7da4 Mon Sep 17 00:00:00 2001 From: Sathvik Bhagavan Date: Fri, 19 Apr 2024 08:09:35 +0000 Subject: [PATCH 6/8] docs: import Test --- docs/make.jl | 2 ++ docs/src/friction.md | 1 + 2 files changed, 3 insertions(+) diff --git a/docs/make.jl b/docs/make.jl index 2393190..8575c56 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -1,5 +1,7 @@ using ModelingToolkitNeuralNets using Documenter +ENV["GKSwstype"] = "100" +ENV["JULIA_DEBUG"] = "Documenter" cp("./docs/Manifest.toml", "./docs/src/assets/Manifest.toml", force = true) cp("./docs/Project.toml", "./docs/src/assets/Project.toml", force = true) diff --git a/docs/src/friction.md b/docs/src/friction.md index d05ca9a..842d6b7 100644 --- a/docs/src/friction.md +++ b/docs/src/friction.md @@ -21,6 +21,7 @@ using SymbolicIndexingInterface using StableRNGs using Lux using Plots +using Test #hide ``` ## Problem Setup From 4bac4dcbd68c9ac8fbfd625ad42c319b40c4e3a1 Mon Sep 17 00:00:00 2001 From: Sathvik Bhagavan Date: Fri, 19 Apr 2024 08:09:57 +0000 Subject: [PATCH 7/8] build: bump MTKStdLib compat --- Project.toml | 2 +- docs/Project.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Project.toml b/Project.toml index 599404d..09c07f0 100644 --- a/Project.toml +++ b/Project.toml @@ -20,7 +20,7 @@ JET = "0.8" Lux = "0.5.32" LuxCore = "0.1.14" ModelingToolkit = "9.9" -ModelingToolkitStandardLibrary = "2.6" +ModelingToolkitStandardLibrary = "2.7" Optimization = "3.24" OptimizationOptimisers = "0.2.1" OrdinaryDiffEq = "6.74" diff --git a/docs/Project.toml b/docs/Project.toml index d886980..92889db 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -16,7 +16,7 @@ ModelingToolkitNeuralNets = "f162e290-f571-43a6-83d9-22ecc16da15f" Documenter = "1.3" Lux = "0.5.32" ModelingToolkit = "9.9" -ModelingToolkitStandardLibrary = "2.6" +ModelingToolkitStandardLibrary = "2.7" Optimization = "3.24" OptimizationOptimisers = "0.2.1" OrdinaryDiffEq = "6.74" From b70f2cca81a24241b8d0d71c070f879e1f416c43 Mon Sep 17 00:00:00 2001 From: Sathvik Bhagavan Date: Fri, 19 Apr 2024 11:57:25 +0000 Subject: [PATCH 8/8] docs: fix link in index.md --- docs/src/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/index.md b/docs/src/index.md index f2945cc..a9ed1cf 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -4,7 +4,7 @@ CurrentModule = ModelingToolkitNeuralNets # ModelingToolkitNeuralNets -Documentation for [ModelingToolkitNeuralNets](https://github.com/SebastianM-C/ModelingToolkitNeuralNets.jl). +Documentation for [ModelingToolkitNeuralNets](https://github.com/SciML/ModelingToolkitNeuralNets.jl). ```@index ```