Description
Is this what is meant by this documentation? I thought to open an issue to track this and have a MWE.
I think it is common to do simulations with parameters whose types can only be known abstractly at system-definition time, but that will be known concretely at problem-construction and solution time. Ideally, I would want the final problem to be type-stable and performant. But it seems to me that such setups currently suffer a performance hit due to type instabilities.
For example, consider a problem that calculates the work done on a particle following any data-given trajectory:
using ModelingToolkit, DataInterpolations, OrdinaryDiffEq
using ModelingToolkit: t_nounits as t, D_nounits as D
splvalue(s, t) = s(t)
splderiv(s, t, order) = DataInterpolations.derivative(s, t, order)
@register_symbolic splvalue(s::CubicSpline, t)
@register_symbolic splderiv(s::CubicSpline, t, order)
function create_system(; Tspl = CubicSpline, kwargs...)
# system that calculates work done on a particle following any data-given trajectory
@parameters m yspl::Tspl
@variables y(t) v(t) a(t) F(t) W(t)
return structural_simplify(ODESystem([
y ~ splvalue(yspl, t) # position (height)
v ~ splderiv(yspl, t, 1) # velocity
a ~ splderiv(yspl, t, 2) # acceleration
F ~ m * a # force
D(W) ~ F * v # power = time derivative of work
], t; defaults = [W => 0.0, m => 1.0], kwargs...))
end
function compute_work(v0; g = 1.0, concrete = false)
y(t) = v0*t - 1/2*g*t^2 # anal(ytical) traj of part in const grav field
ts = range(0.0, v0/g, step=0.01) # simulate from ground to highest point
yspline = CubicSpline(y.(ts), ts)
# HACK: inform system about abstract/concrete type
@named sys = create_system(; Tspl = concrete ? typeof(yspline) : CubicSpline)
prob = ODEProblem(sys, [], extrema(ts), [sys.yspl => yspline])
sol = solve(prob, Rodas5P(); reltol = 1e-10) # chosen to amplify performance differences
return only(sol[end]) # get work done (the only unknown) at final time
end
This has a workaround to inform create_system()
of which (abstract/concrete) type to use for the spline. Benchmarks report that the concrete version is faster:
@btime compute_work(1e4; concrete = true) # 2.705 s (7243956 allocations: 695.90 MiB)
@btime compute_work(1e4; concrete = false) # 4.226 s (57211102 allocations: 1.48 GiB)
This seems to be because of type instabilities (red) in the abstract version:
@profview compute_work(1e4; concrete = false)
In contrast, the concrete version looks type stable (blue):
@profview compute_work(1e4; concrete = true)
The performance hit becomes more severe for larger ODEs. I would like to get rid of the workaround, in order to make the problem naturally compatible and performant with automatic differentiation (which enters the spline type), etc. Is this something that could be fixed by MTK in the future, or should one find workarounds?