-
-
Notifications
You must be signed in to change notification settings - Fork 39
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Interpolation
& ParametrizedInterpolation
#314
Conversation
This is what I was proposing should just replace the current SampledData and TimeVaryingFunction.
Yes
We only really need DataInterpolations. We can make a SciML package InterpolationInterface.jl if we need to, but right now DataInterpolations is the only useful interpolations package since Interpolations.jl has way too many limitations, so I wouldn't worry about it. |
It should replace |
f46198d
to
a6824ab
Compare
Regarding derivatives, I know that |
a6824ab
to
f88861c
Compare
The docs failures are quite strange. Looking at the dc motor code and running it locally, I don't understand how connect(sys.speed_sensor.w, :y, feedback.input2)
connect(sys.pi_controller.ctr_output, :u, source.V) runs in CI given that Moving on, the error is
which I believe it's just a misleading error message, as using I don't understand how this tutorial worked in the first place 😅 I also bumped the MTK compat since this PR needs parameter dependencies and that was broken for some time. In any case, [email protected] is ancient and again surprises me that no one had to bump it before to get the downgrade CI working 😅 |
It doesn't run in CI, it's a
Could have worked since 0 was the default value before, but no longer isn't |
cc7365c
to
6cbff0c
Compare
@AayushSabharwal I think there might be a codegen bug in the latest The julia> prob.f.f.f_iip
RuntimeGeneratedFunction(#=in ModelingToolkit=#, #=using ModelingToolkit=#, :((ˍ₋out, ˍ₋arg1, ˍ₋arg2, ˍ₋arg3, ˍ₋arg4, ˍ₋arg5, t)->begin
#= /home/sebastian/.julia/packages/SymbolicUtils/EGhOJ/src/code.jl:373 =#
#= /home/sebastian/.julia/packages/SymbolicUtils/EGhOJ/src/code.jl:374 =#
#= /home/sebastian/.julia/packages/SymbolicUtils/EGhOJ/src/code.jl:375 =#
begin
begin
i₊x = reshape(view(ˍ₋arg2, 1:15), (15,))
i₊u = reshape(view(ˍ₋arg2, 16:30), (15,))
begin
var"i₊output₊u(t)" = (ModelingToolkitStandardLibrary.Blocks.apply_interpolation)(ˍ₋arg5[1], t)
begin
#= /home/sebastian/.julia/packages/Symbolics/qKoME/src/build_function.jl:546 =#
#= /home/sebastian/.julia/packages/SymbolicUtils/EGhOJ/src/code.jl:422 =# @inbounds begin
#= /home/sebastian/.julia/packages/SymbolicUtils/EGhOJ/src/code.jl:418 =#
ˍ₋out[1] = var"i₊output₊u(t)"
#= /home/sebastian/.julia/packages/SymbolicUtils/EGhOJ/src/code.jl:420 =#
nothing
end
end
end
end
end
end)) but the parameters are julia> collect(prob.p)
4-element Vector{Any}:
[0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0 … 0.4452005375902247, 0.27260738099007953, 0.12211828219228682, 0.07903709080712873, 0.45123745321084807, 0.872779020856403, 0.7417928230058485, 0.7812799868464684, 0.6814546815330172, 0.32821342669957054]
LinearInterpolation{Vector{Float64}, StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}, Vector{Float64}, Vector{Float64}, Float64}[LinearInterpolation{Vector{Float64}, StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}, Vector{Float64}, Vector{Float64}, Float64}([0.16623813954545685, 0.8138788623862058, 0.936459137051247, 0.6545058313889524, 0.29096219036766036, 0.4452005375902247, 0.27260738099007953, 0.12211828219228682, 0.07903709080712873, 0.45123745321084807, 0.872779020856403, 0.7417928230058485, 0.7812799868464684, 0.6814546815330172, 0.32821342669957054], 0.0:1.0:14.0, Float64[], DataInterpolations.LinearParameterCache{Vector{Float64}}(Float64[]), false, Base.RefValue{Int64}(1), false, false)]
UnionAll[LinearInterpolation]
Tuple[()] The problem is that
calls This worked on [email protected], so I'm assuming that the recent changes in [email protected] might have introduced this. |
The current julia> prob.f.f.f_iip
RuntimeGeneratedFunction(#=in ModelingToolkit=#, #=using ModelingToolkit=#, :((ˍ₋out, ˍ₋arg1, ˍ₋arg2, ˍ₋arg3, ˍ₋arg4, t)->begin
#= /home/sebastian/.julia/packages/SymbolicUtils/EGhOJ/src/code.jl:373 =#
#= /home/sebastian/.julia/packages/SymbolicUtils/EGhOJ/src/code.jl:374 =#
#= /home/sebastian/.julia/packages/SymbolicUtils/EGhOJ/src/code.jl:375 =#
begin
begin
i₊x = reshape(view(ˍ₋arg2, 1:15), (15,))
i₊u = reshape(view(ˍ₋arg2, 16:30), (15,))
begin
i₊interpolator = (ModelingToolkitStandardLibrary.Blocks.build_interpolation)(ˍ₋arg3[1], i₊u, i₊x, ˍ₋arg4[1])
begin
var"i₊output₊u(t)" = (ModelingToolkitStandardLibrary.Blocks.apply_interpolation)(i₊interpolator, t)
begin
#= /home/sebastian/.julia/packages/Symbolics/qKoME/src/build_function.jl:546 =#
#= /home/sebastian/.julia/packages/SymbolicUtils/EGhOJ/src/code.jl:422 =# @inbounds begin
#= /home/sebastian/.julia/packages/SymbolicUtils/EGhOJ/src/code.jl:418 =#
ˍ₋out[1] = var"i₊output₊u(t)"
#= /home/sebastian/.julia/packages/SymbolicUtils/EGhOJ/src/code.jl:420 =#
nothing
end
end
end
end
end
end
end)) Is it expected that the parameter dependencies are always executed in the problem function? The parameter dependency implementation pre SciML/ModelingToolkit.jl#2934 would have cached the dependent parameters in the @ChrisRackauckas Will dependent parameter caching no longer be available in the future? |
I'm not sure about the bug, nothing immediately stands out as a potential cause. The not caching parameter dependencies is intentional. It was problematic to maintain, especially with AD and SciMLSensitivity. The new implementation of lazy computation is more in line with observed variables, which is exactly what parameter dependencies are. |
8e4b786
to
7e8cbee
Compare
Needs the [email protected] release to get CI in a working state. |
Was this completed with MTK v9.34+? 😃 |
Almost. I mainly stopped because I'm questioning if this approach is good vs just having support for callable parameters in MTK. I think that for most people callable parameters would be enough since having the interpolation data as a vector parameter is not something that one might need in general. Do you have an application where this PR would be useful? |
I am solving a "big" ODE model with a block-lower-triangular (BLT) structure (e.g. SciML/ModelingToolkit.jl#337), such as
Here the solution for Splines are what I use to pass on the full lower- I am currently relying on workarounds (e.g. the "proxy function" in SciML/ModelingToolkit.jl#2823) that does not fulfill these requirements. It looks to me like this PR improves this situation a lot! But as you said, I think properly working callable parameters would also solve my problems (e.g. what I want to work in SciML/ModelingToolkit.jl#2823). |
This looks like something that MTK should automate entirely 😅 I think this is what SciML/NonlinearSolve.jl#388 could help with in the future. The main advantage of the approach of this PR is that the parameters of an interpolation object are represented symbolically in the system, but I'm not so sure anymore when is this useful... |
Yeah... don't do this by hand 😅 , you'll get some fancy new tools for exactly this kind of problem over the next two months. I actually am trying to avoid some of the other work in order to work on the general tooling within the solvers for that, so hold your horses 😅 |
Thanks a lot! I completely agree that MTK should automate this process. I would kill for this feature, Chris 😅 Would it be helpful if I created a separate issue for this? Yes, I really want to use the custom |
Roadmap for callable parameters is in SciML/ModelingToolkit.jl#2910 |
1fdfc71
to
e4f7fb9
Compare
Looks like the latest MTK breaks a lot of stuff due to initialization issues. |
e4f7fb9
to
2d19a9d
Compare
Needs SciML/ModelingToolkit.jl#3079 since currently parameter deps don't allow callable parameters. With parameter deps, the implementation was simplified and I think we can remove the DataInterpolations extension as the API doesn't need it anymore. I also looked a bit into the performance and I think that it's better now compared to the previous implementation. This leads to flamegraphs like As we can see there's some runtime dispatch and GC inside the RGF. Looking more closely with Cthulhu, I saw that this is due to the interpolation type not being inferred. To asses the impact we can do a type assert on the return of return interp:::T and that leads to I this adversary case, where the ODE is trivial, @named i = ParametrizedInterpolation(LinearInterpolation, u, x)
eqs = [D(y) ~ i.output.u]
@named model = ODESystem(eqs, t, systems = [i]) and we provide the solver, the problem is prob = ODEProblem{true, SciMLBase.FullSpecialize}(sys, [], (0.0, 4)) and saving is turned off with # without the type assert
julia> @btime solve($prob, Tsit5(), save_everystep=false)
5.910 μs (178 allocations: 6.70 KiB)
retcode: Success
Interpolation: 1st order linear
t: 2-element Vector{Float64}:
0.0
4.0
u: 2-element Vector{Vector{Float64}}:
[0.0]
[2.9736997481549214] vs # with the type assert
julia> @btime solve($prob, Tsit5(), save_everystep=false)
5.041 μs (40 allocations: 4.55 KiB)
retcode: Success
Interpolation: 1st order linear
t: 2-element Vector{Float64}:
0.0
4.0
u: 2-element Vector{Vector{Float64}}:
[0.0]
[2.9736997481549214] (Of course, the type assert is rendering the Moreover, if we compare against a model where the interpolation is directly used as a callable parameter, @parameters (i::LinearInterpolation)(..)
eqs = [D(y) ~ i(t)]
@named model = ODESystem(eqs, t)
sys = structural_simplify(model)
prob = ODEProblem{true, SciMLBase.FullSpecialize}(sys, [i=>LinearInterpolation(u, x)], (0.0, 4)) and the timings are julia> @btime solve($prob, Tsit5(), save_everystep=false)
5.280 μs (178 allocations: 6.70 KiB)
retcode: Success
Interpolation: 1st order linear
t: 2-element Vector{Float64}:
0.0
4.0
u: 2-element Vector{Vector{Float64}}:
[0.0]
[2.9736997481549214] So the type unstable version in this PR is only a bit worse than the implementation that doesn't represent the data (due to some issue in the RGF that leads to dynamic dispatch and GC; I don't know yet what's happening there, I'll update the post when I will know). The allocation count is identical, so I suspect that maybe the interpolation is again at fault. @ChrisRackauckas I think that this implementation could be useful for cases where SampledData was used (as a replacement) & it doesn't seem much worse than just not having the data in the model. I think it could work as a more advanced option and I will rewrite the docs to mention this at the end instead of the beginning. I think that depending on what we want from the AD support, we can improve this further. I haven't looked into reverse mode AD for this, as that would have entirely different challenges. |
@parameters (i::LinearInterpolation)(..)
|
ParametrizedInterpolation
Interpolation
& ParametrizedInterpolation
The tests error due to a bug in codegen,
As we can see, |
Co-authored-by: Aayush Sabharwal <[email protected]>
Co-authored-by: Aayush Sabharwal <[email protected]>
Rebase onto the working master? |
I was about to rebase, but you were faster with the merge 😅 |
I'm not sure what's with with the formatting on the docs page, the VS Code formatter doesn't change anything and as far as I'm aware it's still JuliaFormatter. |
julia> prob.f.initializeprob.f.f.f_iip
RuntimeGeneratedFunction(#=in ModelingToolkit=#, #=using ModelingToolkit=#, :((ˍ₋out, ˍ₋arg1, ˍ₋arg2, ˍ₋arg3)->begin
#= C:\Users\sebastian\.julia\packages\SymbolicUtils\jf8aQ\src\code.jl:385 =#
#= C:\Users\sebastian\.julia\packages\SymbolicUtils\jf8aQ\src\code.jl:386 =#
#= C:\Users\sebastian\.julia\packages\SymbolicUtils\jf8aQ\src\code.jl:387 =#
begin
begin
src₊ts = reshape(view(ˍ₋arg2, 7:257), (251,))
src₊data = reshape(view(ˍ₋arg2, 258:508), (251,))
begin
begin
var"model₊x(t)" = 0.0
var"model₊dx(t)" = 0.0
var"model₊f(t)" = 0.0
var"model₊ddx(t)" = 0.0
var"clk₊output₊u(t)" = (+)(ˍ₋arg2[4], if (<)(ˍ₋arg2[3], ˍ₋arg2[2])
0
else
(+)((*)(-1, ˍ₋arg2[2]), ˍ₋arg2[3])
end)
var"src₊input₊u(t)" = var"clk₊output₊u(t)"
var"src₊output₊u(t)" = src₊interpolator(var"src₊input₊u(t)")
var"model₊input₊u(t)" = var"model₊f(t)"
begin
#= C:\Users\sebastian\.julia\packages\Symbolics\kErIg\src\build_function.jl:546 =#
#= C:\Users\sebastian\.julia\packages\SymbolicUtils\jf8aQ\src\code.jl:434 =# @inbounds begin
#= C:\Users\sebastian\.julia\packages\SymbolicUtils\jf8aQ\src\code.jl:430 =#
ˍ₋out[1] = var"src₊output₊u(t)"
ˍ₋out[2] = (+)((*)(-1, var"model₊ddx(t)"), (*)(1//10, (+)((+)(var"model₊f(t)", (*)(ˍ₋arg2[6], var"model₊dx(t)")), (*)(ˍ₋arg2[1], var"model₊x(t)"))))
#= C:\Users\sebastian\.julia\packages\SymbolicUtils\jf8aQ\src\code.jl:432 =#
nothing
end
end
end
end
end
end
end)) I think there's a bug in the initialization, as the generated code does not include the callable parameter ( Also, the |
How can I reproduce the initialization issue you're referring to? |
using ModelingToolkit
using ModelingToolkit: t_nounits as t, D_nounits as D
using ModelingToolkitStandardLibrary.Blocks
using DataInterpolations
using OrdinaryDiffEq
using DataFrames
using Plots
function MassSpringDamper(; name)
@named input = RealInput()
vars = @variables f(t)=0 x(t)=0 dx(t)=0 ddx(t)=0
pars = @parameters m=10 k=1000 d=1
eqs = [
f ~ input.u
ddx * 10 ~ k * x + d * dx + f
D(x) ~ dx
D(dx) ~ ddx]
ODESystem(eqs, t, vars, pars; name, systems = [input])
end
function MassSpringDamperSystem(data, time; name)
@named src = ParametrizedInterpolation(LinearInterpolation, data, time)
@named clk = ContinuousClock()
@named model = MassSpringDamper()
eqs = [
connect(model.input, src.output)
connect(src.input, clk.output)
]
ODESystem(eqs, t; name, systems = [src, clk, model])
end
function generate_data()
dt = 4e-4
time = 0:dt:0.1
data = sin.(2 * pi * time * 100)
return DataFrame(; time, data)
end
df = generate_data() # example data
@named system = MassSpringDamperSystem(df.data, df.time)
sys = structural_simplify(system)
prob = ODEProblem(sys, [], (0, df.time[end]))
sol = solve(prob)
plot(sol) I think that the equations from parameter deps might not be included in the initialization system. |
Right, yeah I think I know exactly what's missing here :D Missed a spot in parameter initialization |
SciML/ModelingToolkit.jl#3135 should fix this |
this is the example in the docs
I don't understand why the docs build doesn't see the latest MTK release as available. I think we can fix the docs in a separate PR, the tests pass now. |
Okay please follow up with the docs fix. |
Checklist
contributor guidelines, in particular the SciML Style Guide and
COLPRAC.
Additional context
This PR proposes a new way of doing interpolations with the MTKStdLib, taking advantage of the new MTK@v9 features.
As an example, this would look like
The main difference when compared to the approaches of
TimeVaryingFunction
andSampledData
is that the interpolation data is now represented via symbolic parameters, taking advantage of the new array symbolics.This allows us to change the interpolation data without rebuiling the system again via standard
remake
andsetp
, making it SII compatible as opposed toSampledData
.Moreover, the codegen is simplified, thus avoiding inlining the interpolation objects in the expression.
gives
There are a couple of things that I would like to mention about the implementation:
I tried to make this independent of the interpolation package, but we need a function that expresses the calling the interpolation (
apply_interpolation
) and this needs to be registered for each interpolation type. I implemented an extension forDataInterpolation
, so that's preffered over other pacakges, but I beleive that it should be possible to use this with a custom interpolation.I'm assuming 1D interpolation. I think this can be theoretically extended to more than that, but I'm not sure if that's something that's an immediate priority.
In the extension implementation I have to register
apply_interpolation
, but this requiresSymbolics
. SinceSymbolics
is a direct dependency of MTKStdLib, I am loading it viausing ModelingToolkitStandardLibrary.Blocks.Symbolics
, is this how am I supposed to do it?Should this replace or deprecate
SampledData
? This should be able to do more things (it's not restricted to a particular interpolation type) with a less complex implementation.cc @ChrisRackauckas @baggepinnen
Edit: There have been a couple of changes in this PR, but the main idea is that it introduces interpolations as specialized blocks in MTKStdLib. We have 2 types of blocks,
Interpolation
: this is the recommended one in most casesParametrizedInterpolation
: this is recommended if one wants to optimize the data or change it without rebuilding the model. Currently this is a slower due to usingGeneralLazyBufferCache
for AD compatibility.Both blocks have inputs and outputs and the
DataInterpolations
extension is no longer needed. The docs were updated to demonstrate how to use these blocks and the use ofTimeVaryingFunction
is no longer there.