You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
# Building Models with Discrete Data, Interpolations, and Lookup Tables
2
2
3
-
There are 3 ways to include data as part of a model.
3
+
There are 4 ways to include data as part of a model.
4
4
5
-
1. using `ModelingToolkitStandardLibrary.Blocks.TimeVaryingFunction`
6
-
2. using a custom component with external data
7
-
3. using `ModelingToolkitStandardLibrary.Blocks.SampledData`
5
+
1. using `ModelingToolkitStandardLibrary.Blocks.Interpolation`
6
+
2. using `ModelingToolkitStandardLibrary.Blocks.ParametrizedInterpolation`
7
+
3. using a custom component with external data (not recommended)
8
+
4. using `ModelingToolkitStandardLibrary.Blocks.SampledData` (legacy)
8
9
9
10
This tutorial demonstrate each case and explain the pros and cons of each.
10
11
11
-
## `TimeVaryingFunction` Component
12
+
## `Interpolation` Block
12
13
13
-
The `ModelingToolkitStandardLibrary.Blocks.TimeVaryingFunction` component is easy to use and is performant. However the data is locked to the `ODESystem` and can only be changed by building a new `ODESystem`. Therefore, running a batch of data would not be efficient. Below is an example of how to use the `TimeVaryingFunction` with `DataInterpolations` to build the function from sampled discrete data.
14
+
The `ModelingToolkitStandardLibrary.Blocks.Interpolation` component is easy to use and is performant.
15
+
It is similar to using callable parameters, but it provides a block interface with `RealInput` and `RealOutput` connectors.
16
+
The `Interpolation` is compatible with interpolation types from `DataInterpolation`.
ODESystem(eqs, t, [], []; name, systems = [src, clk, model])
61
+
end
62
+
63
+
function generate_data()
64
+
dt = 4e-4
65
+
time = 0:dt:0.1
66
+
data = sin.(2 * pi * time * 100)
67
+
68
+
return DataFrame(; time, data)
69
+
end
70
+
71
+
df = generate_data() # example data
72
+
73
+
@named system = MassSpringDamperSystem(df.data, df.time)
74
+
sys = structural_simplify(system)
75
+
prob = ODEProblem(sys, [], (0, df.time[end]))
76
+
sol = solve(prob)
77
+
plot(sol)
78
+
```
79
+
80
+
## `ParametrizedInterpolation` Block
81
+
82
+
The `ModelingToolkitStandardLibrary.Blocks.ParametrizedInterpolation` component is similar to `Interpolation`, but as the name suggests, it is parametrized by the data, allowing one to change the underlying data without rebuilding the model as the data is represented via vector parameters.
83
+
The main advantage of this block over the [`Interpolation`](@ref) one is that one can use it for optimization problems. Currently, this supports forward mode AD via ForwardDiff, but due to the increased flexibility of the types in the component, this is not as fast as the `Interpolation` block,
84
+
so it is recommended to use only when the added flexibility is required.
ODESystem(eqs, t; name, systems = [src, clk, model])
126
+
end
127
+
128
+
function generate_data()
129
+
dt = 4e-4
130
+
time = 0:dt:0.1
131
+
data = sin.(2 * pi * time * 100)
132
+
133
+
return DataFrame(; time, data)
134
+
end
41
135
42
-
@named system =System(f)
136
+
df = generate_data() # example data
137
+
138
+
@named system = MassSpringDamperSystem(df.data, df.time)
43
139
sys = structural_simplify(system)
44
-
prob =ODEProblem(sys, [], (0, time[end]))
45
-
sol =solve(prob, ImplicitEuler())
140
+
prob = ODEProblem(sys, [], (0, df.time[end]))
141
+
sol = solve(prob)
142
+
plot(sol)
143
+
```
144
+
145
+
If we want to run a new data set, this requires only remaking the problem and solving again
146
+
```@example parametrized_interpolation
147
+
prob2 = remake(prob, p = [sys.src.data => ones(length(df.data))])
148
+
sol2 = solve(prob2)
149
+
plot(sol2)
46
150
```
47
151
48
-
If we want to run a new data set, this requires building a new `LinearInterpolation` and `ODESystem` followed by running `structural_simplify`, all of which takes time. Therefore, to run several pieces of data it's better to re-use an `ODESystem`. The next couple methods will demonstrate how to do this.
152
+
!!! note
153
+
Note that when changing the data, the length of the new data must be the same as the length of the original data.
49
154
50
155
## Custom Component with External Data
51
156
52
157
The below code shows how to include data using a `Ref` and registered `get_sampled_data` function. This example uses a very basic function which requires non-adaptive solving and sampled data. As can be seen, the data can easily be set and changed before solving.
53
158
54
-
```julia
159
+
```@example custom_component_external_data
160
+
using ModelingToolkit
161
+
using ModelingToolkit: t_nounits as t, D_nounits as D
162
+
using ModelingToolkitStandardLibrary.Blocks
163
+
using OrdinaryDiffEq
164
+
55
165
const rdata = Ref{Vector{Float64}}()
56
166
167
+
dt = 4e-4
168
+
time = 0:dt:0.1
57
169
# Data Sets
58
170
data1 = sin.(2 * pi * time * 100)
59
171
data2 = cos.(2 * pi * time * 50)
@@ -106,8 +218,13 @@ Additional code could be added to resolve this issue, for example by using a `Re
106
218
To resolve the issues presented above, the `ModelingToolkitStandardLibrary.Blocks.SampledData` component can be used which allows for a resusable `ODESystem` and self contained data which ensures a solution which remains valid for it's lifetime. Now it's possible to also parallelize the call to `solve()`.
107
219
108
220
```julia
221
+
using ModelingToolkit
222
+
using ModelingToolkit: t_nounits as t, D_nounits as D
0 commit comments