Skip to content

Commit bbff513

Browse files
authored
Add examples and tests via literate (#335)
* Remove old examples * Adapt docs, examples and tests * Add Literate dep and linear cont * Fix spooky typo * Move examples into docs * Adapt examples and makefile * Fix error useage * Don't use literate * Create upper bound for SymbolicRegression * Add symbolic regression example * Adapt tests to enforce right result * Try to get consistent results * Adapt SR example * Add nonlinear example * Finalize nonlinear system * Add michaelis menten * Pass kwargs to solution * Last try to test symbolic regression * Adapt output of SR * Remove symbolic regression from the tests * Rmv nonlinear systems * Add cartpole * Slighty tighten tests * Reinclude tests and write note of caution * Fix time step problem * Adapt examples * Adapt error * Fix candidate matrix * Finalize docs * Refinalize docs * Missing refs * Fix plots in autoregulation * Adapt order * Adapt to exclude symbolic regression * Rmv old docs * Add heat equation * Add noisy nonlinear * Include operators and fix heat equation
1 parent 349164e commit bbff513

34 files changed

+1334
-840
lines changed

Project.toml

Lines changed: 29 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,31 @@
1+
authors = ["Julius Martensen <[email protected]>"]
12
name = "DataDrivenDiffEq"
23
uuid = "2445eb08-9709-466a-b3fc-47e12bd697a2"
3-
authors = ["Julius Martensen <[email protected]>"]
44
version = "0.7.0"
55

6+
[compat]
7+
CommonSolve = "0.2"
8+
Compat = "3.0"
9+
DataInterpolations = "3"
10+
DiffEqBase = "6"
11+
Distributions = "^0.25.9, 0.25"
12+
DocStringExtensions = "0.7, 0.8"
13+
Flux = "^0.12.4"
14+
Literate = "2"
15+
Measurements = "2.7"
16+
ModelingToolkit = "7, 8"
17+
Parameters = "0.12"
18+
PkgVersion = "0.1"
19+
ProgressMeter = "1.6"
20+
QuadGK = "2.4"
21+
RecipesBase = "1"
22+
Reexport = "1.0"
23+
Requires = "1"
24+
StatsBase = "0.32.0, 0.33"
25+
SymbolicRegression = "0.6.14 - 0.6.19"
26+
Symbolics = "4"
27+
julia = "1.6"
28+
629
[deps]
730
CommonSolve = "38540f10-b2f7-11e9-35d8-d573e4eb0ff2"
831
Compat = "34da2185-b29b-5c13-b0c7-acf172513d20"
@@ -11,9 +34,11 @@ DiffEqBase = "2b5f629d-d688-5b77-993f-72d75c75574e"
1134
Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f"
1235
DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
1336
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
37+
Literate = "98b081ad-f1c9-55d3-8b20-4c87d4299306"
1438
Measurements = "eff96d63-e80a-5855-80a2-b1b0885c5ab7"
1539
ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78"
1640
Parameters = "d96e819e-fc66-5662-9728-84c9c7592b0a"
41+
PkgVersion = "eebad327-c553-4316-9ea0-9fa01ccd7688"
1742
ProgressMeter = "92933f4c-e287-5a05-a399-4b506db050ca"
1843
QuadGK = "1fd47b50-473d-5c70-9696-f719f8f3bcdc"
1944
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
@@ -25,29 +50,10 @@ StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
2550
Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7"
2651
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
2752

28-
[compat]
29-
CommonSolve = "0.2"
30-
Compat = "3.0"
31-
DataInterpolations = "3"
32-
DiffEqBase = "6"
33-
Distributions = "^0.25.9, 0.25"
34-
DocStringExtensions = "0.7, 0.8"
35-
Flux = "^0.12.4"
36-
Measurements = "2.7"
37-
ModelingToolkit = "7, 8"
38-
Parameters = "0.12"
39-
ProgressMeter = "1.6"
40-
QuadGK = "2.4"
41-
RecipesBase = "1"
42-
Reexport = "1.0"
43-
Requires = "1"
44-
StatsBase = "0.32.0, 0.33"
45-
SymbolicRegression = "^0.6.11"
46-
Symbolics = "4"
47-
julia = "1.6"
48-
4953
[extras]
54+
CPUSummary = "2a0fbf3d-bb9c-48f3-b0a9-814d99fd7ab9"
5055
DiffEqFlux = "aae7a2af-3d4f-5e19-a356-7da93b79d9d0"
56+
DiffEqOperators = "9fdde737-9c7f-55bf-ade8-46b3f136cc48"
5157
DiffEqSensitivity = "41bf760c-e81c-5289-8e54-58b1f1f8abe2"
5258
Flux = "587475ba-b771-5e3f-ad9e-33799f191a9c"
5359
JLD2 = "033835bb-8acc-5ee8-8aae-3f567f8a3819"
@@ -59,4 +65,4 @@ SymbolicRegression = "8254be44-1295-4e6a-a16d-46603ac705cb"
5965
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
6066

6167
[targets]
62-
test = ["Flux", "SymbolicRegression", "OrdinaryDiffEq", "Test", "Random", "SafeTestsets"]
68+
test = ["DiffEqOperators", "Flux", "SymbolicRegression", "OrdinaryDiffEq", "Test", "Random", "SafeTestsets"]

docs/Project.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
[deps]
22
DataDrivenDiffEq = "2445eb08-9709-466a-b3fc-47e12bd697a2"
3+
DiffEqOperators = "9fdde737-9c7f-55bf-ade8-46b3f136cc48"
34
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
45
Flux = "587475ba-b771-5e3f-ad9e-33799f191a9c"
56
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
7+
Literate = "98b081ad-f1c9-55d3-8b20-4c87d4299306"
68
ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78"
79
OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed"
810
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
@@ -11,3 +13,4 @@ Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7"
1113

1214
[compat]
1315
Documenter = "0.27"
16+
SymbolicRegression = "0.6.19"

docs/examples/0_getting_started.jl

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# # [Getting Started](@id getting_started)
2+
#
3+
# The workflow for [DataDrivenDiffEq.jl](https://github.com/SciML/DataDrivenDiffEq.jl) is similar to other [SciML](https://sciml.ai/) packages.
4+
# You start by defining a [`DataDrivenProblem`](@ref) and then dispatch on the [`solve`](@ref) command to return a [`DataDrivenSolution`](@ref).
5+
6+
# Here is an outline of the required elements and choices:
7+
# + Define a [`problem`](@ref problem) using your data.
8+
# + Choose a [`basis`](@ref Basis).
9+
# + This is optional depending on which solver you choose.
10+
# + [`Solve`](@ref solve) the problem.
11+
12+
using DataDrivenDiffEq
13+
using ModelingToolkit
14+
using LinearAlgebra
15+
16+
# Generate a test problem
17+
18+
f(u) = u.^2 .+ 2.0u .- 1.0
19+
X = randn(1, 100);
20+
Y = reduce(hcat, map(f, eachcol(X)));
21+
22+
# Create a problem from the data
23+
problem = DirectDataDrivenProblem(X, Y, name = :Test)
24+
25+
# Choose a basis
26+
@variables u
27+
basis = Basis(monomial_basis([u], 2), [u])
28+
println(basis)
29+
30+
# Solve the problem, using the solver of your choosing
31+
32+
res = solve(problem, basis, STLSQ())
33+
println(res)
34+
println(result(res))
35+
36+
#md # ## [Copy-Pasteable Code](@id getting_started_code)
37+
#md #
38+
#md # ```julia
39+
#md # @__CODE__
40+
#md # ```
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# # [Sparse Identification with noisy data](@id noisy_sindy)
2+
#
3+
# Many data real world data sources are corrupted with measurment noise, which can have
4+
# a big impact on the recovery of the underlying equations of motion. This example show how we can
5+
# use [`collocation`](@ref collocation) and [`batching`](@ref DataSampler) to perform SINDy in the presence of
6+
# noise.
7+
8+
using DataDrivenDiffEq
9+
using LinearAlgebra
10+
using ModelingToolkit
11+
using OrdinaryDiffEq
12+
#md using Plots
13+
#md gr()
14+
15+
function pendulum(u, p, t)
16+
x = u[2]
17+
y = -9.81sin(u[1]) - 0.3u[2]^3 -3.0*cos(u[1]) - 10.0*exp(-((t-5.0)/5.0)^2)
18+
return [x;y]
19+
end
20+
21+
u0 = [0.99π; -1.0]
22+
tspan = (0.0, 15.0)
23+
prob = ODEProblem(pendulum, u0, tspan)
24+
sol = solve(prob, Tsit5(), saveat = 0.01)
25+
26+
# We add random noise to our measurements.
27+
28+
X = sol[:,:] + 0.2 .* randn(size(sol));
29+
ts = sol.t;
30+
31+
#md plot(ts, X', color = :red)
32+
#md plot!(sol, color = :black)
33+
34+
# To estimate the system, we first create a [`DataDrivenProblem`](@ref) via feeding in the measurement data.
35+
# Using a [Collocation](@ref) method, it automatically provides the derivative and smoothes the trajectory. Control signals can be passed
36+
# in as a function `(u,p,t)->control` or an array of measurements.
37+
38+
prob = ContinuousDataDrivenProblem(X, ts, GaussianKernel() ,
39+
U = (u,p,t)->[exp(-((t-5.0)/5.0)^2)], p = ones(2))
40+
41+
#md plot(prob, size = (600,600))
42+
43+
# Now we infer the system structure. First we define a [`Basis`](@ref) which collects all possible candidate terms.
44+
# Since we want to use SINDy, we call `solve` with an [`Optimizer`](@ref sparse_optimization), in this case [`STLSQ`](@ref) which iterates different sparsity thresholds
45+
# and returns a pareto optimal solution of the underlying [`sparse_regression!`](@ref). Note that we include the control signal in the basis as an additional variable `c`.
46+
47+
@variables u[1:2] c[1:1]
48+
@parameters w[1:2]
49+
u = collect(u)
50+
c = collect(c)
51+
w = collect(w)
52+
53+
h = Num[sin.(w[1].*u[1]);cos.(w[2].*u[1]); polynomial_basis(u, 5); c]
54+
55+
basis = Basis(h, u, parameters = w, controls = c)
56+
57+
# To solve the problem, we also define a [`DataSampler`](@ref) which defines randomly shuffled minibatches of our data and selects the
58+
# best fit.
59+
60+
sampler = DataSampler(Batcher(n = 5, shuffle = true, repeated = true))
61+
λs = exp10.(-10:0.1:-1)
62+
opt = STLSQ(λs)
63+
res = solve(prob, basis, opt, progress = false, sampler = sampler, denoise = false, normalize = false, maxiter = 5000)
64+
println(res) #hide
65+
66+
# !!! info
67+
# A more detailed description of the result can be printed via `print(res, Val{true})`, which also includes the discovered equations and parameter values.
68+
#
69+
# Where the resulting [`DataDrivenSolution`](@ref) stores information about the inferred model and the parameters:
70+
71+
system = result(res)
72+
params = parameters(res)
73+
println(system) #hide
74+
println(params) #hide
75+
76+
# And a visual check of the result can be perfomed via plotting the result
77+
78+
#md plot(
79+
#md plot(prob), plot(res), layout = (1,2)
80+
#md )
81+
82+
83+
#md # ## [Copy-Pasteable Code](@id autoregulation_copy_paste)
84+
#md #
85+
#md # ```julia
86+
#md # @__CODE__
87+
#md # ```
88+
89+
## Test #src
90+
for r_ in [res] #src
91+
@test all(aic(r_) .> 1e3) #src
92+
@test all(determination(r_) .>= 0.9) #src
93+
end #src
94+
95+
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
# # [Linear Time Discrete System](@id linear_discrete)
2+
#
3+
# We will start by estimating the underlying dynamical system of a time discrete process based on some measurements via [Dynamic Mode Decomposition](https://arxiv.org/abs/1312.0041) on a simple linear system of the form ``u(k+1) = A u(k)``.
4+
#
5+
# At first, we simulate the correspoding system using `OrdinaryDiffEq.jl` and generate a [`DiscreteDataDrivenProblem`](@ref DataDrivenProblem) from the simulated data.
6+
7+
using DataDrivenDiffEq
8+
using ModelingToolkit
9+
using LinearAlgebra
10+
using OrdinaryDiffEq
11+
#md using Plots
12+
13+
A = [0.9 -0.2; 0.0 0.2]
14+
u0 = [10.0; -10.0]
15+
tspan = (0.0, 11.0)
16+
17+
f(u,p,t) = A*u
18+
19+
sys = DiscreteProblem(f, u0, tspan)
20+
sol = solve(sys, FunctionMap());
21+
22+
# Next we transform our simulated solution into a [`DataDrivenProblem`](@ref). Given that the solution knows its a discrete solution, we can simply write
23+
24+
prob = DataDrivenProblem(sol)
25+
26+
# And plot the solution and the problem
27+
28+
#md plot(sol, label = string.([:x₁ :x₂]))
29+
#md scatter!(prob)
30+
31+
# To estimate the underlying operator in the states ``x_1, x_2``, we `solve` the estimation problem using the [`DMDSVD`](@ref) algorithm for approximating the operator. First, we will have a look at the [`DataDrivenSolution`](@ref)
32+
33+
res = solve(prob, DMDSVD(), digits = 1)
34+
#md println(res) # hide
35+
36+
# We see that the system has been recovered correctly, indicated by the small error and high AIC score of the result. We can confirm this by looking at the resulting [`Basis`](@ref)
37+
38+
system = result(res)
39+
using Symbolics
40+
41+
#md println(system) # hide
42+
43+
# And also plot the prediction of the recovered dynamics
44+
45+
#md plot(res)
46+
47+
# Or a have a look at the metrics of the result
48+
49+
#md metrics(res)
50+
51+
# To have a look at the representation of the operator as a `Matrix`, we can simply call
52+
53+
#md Matrix(system)
54+
55+
# to see that the operator is indeed our initial `A`. Since we have a linear representation, we can gain further insights into the stability of the dynamics via its eigenvalues
56+
57+
#md eigvals(system)
58+
59+
# And plot the stability margin of the discrete System
60+
61+
#md φ = 0:0.01π:2π
62+
#md plot(sin.(φ), cos.(φ), xlabel = "Real", ylabel = "Im", label = "Stability margin", color = :red, linestyle = :dash)
63+
#md scatter!(real(eigvals(system)), imag(eigvals(system)), label = "Eigenvalues", color = :black, marker = :cross)
64+
65+
# Similarly, we could use a sparse regression to derive our system from our data. We start by defining a [`Basis`](@ref)
66+
67+
using ModelingToolkit
68+
69+
@parameters t
70+
@variables x[1:2](t)
71+
72+
basis = Basis(x, x, independent_variable = t, name = :LinearBasis)
73+
#md print(basis) #hide
74+
75+
# Afterwards, we simply `solve` the already defined problem with our `Basis` and a `SparseOptimizer`
76+
77+
sparse_res = solve(prob, basis, STLSQ())
78+
#md println(sparse_res) #hide
79+
80+
# Which holds the same equations
81+
sparse_system = result(sparse_res)
82+
#md println(sparse_system) #hide
83+
84+
# Again, we can have a look at the result
85+
86+
#md plot(
87+
#md plot(prob), plot(sparse_res), layout = (1,2)
88+
#md )
89+
90+
# Both results can be converted into a `DiscreteProblem`
91+
92+
@named sys = DiscreteSystem(equations(sparse_system), get_iv(sparse_system),states(sparse_system), parameters(sparse_system))
93+
#md println(sys) #hide
94+
95+
# And simulated using `OrdinaryDiffEq.jl` using the (known) initial conditions and the parameter mapping of the estimation.
96+
97+
x0 = [x[1] => u0[1], x[2] => u0[2]]
98+
ps = parameter_map(sparse_res)
99+
100+
discrete_prob = DiscreteProblem(sys, x0, tspan, ps)
101+
estimate = solve(discrete_prob, FunctionMap());
102+
103+
# And look at the result
104+
#md plot(sol, color = :black)
105+
#md plot!(estimate, color = :red, linestyle = :dash)
106+
107+
#md # ## [Copy-Pasteable Code](@id linear_discrete_copy_paste)
108+
#md #
109+
#md # ```julia
110+
#md # @__CODE__
111+
#md # ```
112+
113+
## Test the result #src
114+
for r_ in [res, sparse_res] #src
115+
@test all(l2error(r_) .<= 1e-10) #src
116+
@test all(aic(r_) .>= 1e10) #src
117+
@test all(determination(r_) .≈ 1.0) #src
118+
end #src
119+
@test Array(sol) Array(estimate) #src
120+
121+

0 commit comments

Comments
 (0)