Skip to content

Type instability with parameters that are abstract at system-level, but concrete at problem-level #3262

Closed
@hersle

Description

@hersle

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)

image

In contrast, the concrete version looks type stable (blue):

@profview compute_work(1e4; concrete = true)

image

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?

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions