Skip to content
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

Compatibility with DifferentialEquations.jl #1

Open
stevengj opened this issue Feb 9, 2025 · 3 comments
Open

Compatibility with DifferentialEquations.jl #1

stevengj opened this issue Feb 9, 2025 · 3 comments

Comments

@stevengj
Copy link
Member

stevengj commented Feb 9, 2025

Right now, this package is not compatible with the RecursiveArrayTools.jl package used by DifferentialEquations.jl.

e.g. here is a simple example from the DifferentialEquations tutorial, modified to use a TupleVec(dx, [dy, dz]) of a scalar and a vector:

using DifferentialEquations, TupleVectorSpaces

function lorenz(u, p, t)
    dx, (dy, dz) = Tuple(u)
    dx = 10.0 * (u[2] - u[1])
    dy = u[1] * (28.0 - u[3]) - u[2]
    dz = u[1] * u[2] - (8 / 3) * u[3]
    TupleVec(dx, [dy, dz])
end

u0 = TupleVec(1.0, [0.0, 0.0])
tspan = (0.0, 100.0)
prob = ODEProblem(lorenz, u0, tspan)
solve(prob, Tsit5());

This gives the error:

MethodError: no method matching ndims(::Type{TupleVec{Tuple{Float64, Vector{Float64}}}})
The function `ndims` exists, but no method is defined for this combination of argument types.

Closest candidates are:
  ndims(::Type{Union{}}, Any...)
   @ Base abstractarray.jl:276
  ndims(::Type{<:AbstractVectorOfArray{T, N}}) where {T, N}
   @ RecursiveArrayTools ~/.julia/packages/RecursiveArrayTools/Y3i0V/src/vector_of_array.jl:595

Although it would technically be possible to support indexing into a TupleVec, i.e. to make it act like an AbstractVector, I'd really rather keep it more general and represent an element of an arbitrary normed vector space. I don't see any fundamental technical reason why this shouldn't be compatible with numerical ODE solvers (just as it's already compatible with numerical integration).

@ChrisRackauckas, is there a way to tell RecursiveArrayTools not to try to "look inside" a TupleVec, and instead treat it as an opaque mathematical object? As long as we're not trying to work in-place, at least for explicit solvers I don't see why you should need to look inside the vector.

@ChrisRackauckas
Copy link
Member

I think things are mostly close. I'd add:

Base.ndims(u::Type{<:TupleVec}) = 1
Base.zero(u::TupleVec) = TupleVec(zero.(Tuple(u)))

But the main issue I'm seeing is:

julia> u0 = TupleVec(1.0, [0.0; 0.0])
TupleVec(1.0, [0.0, 0.0])

julia> u0 .+ u0
3-element Vector{Float64}:
 2.0
 0.0
 0.0

While it's generally not a requirement of broadcast, we do generally require that broadcast is type-preserving if it's a linear combination of the same types + scalars, i.e. that the vector space property is held and the linear combination is in the vector space.

@stevengj
Copy link
Member Author

There's already a zero function. ndims seems a bit weird because it's not indexable … why is that required? But I can certainly add it.

Would it suffice to just define

 Broadcast.broadcastable(v::TupleVec) = Ref(v)

so that it is not treated as a container for broadcasting?

@stevengj
Copy link
Member Author

Disabling broadcasting then causes DiffEq to be stuck at this line: https://github.com/SciML/OrdinaryDiffEq.jl/blob/0ea61dc630ea0d6159e70aa134bbb674ae13fa25/lib/OrdinaryDiffEqCore/src/solve.jl#L239

(Aside from the question of why there is a default nonzero abstol at all, is there a way to get DiffEq to not treat the errors elementwise and instead just treat the solution as an opaque normed object?)

If I override the default tolerances with solve(prob, Tsit5(), abstol=0, reltol=1e-6) (since the default reltol is also computed elementwise: https://github.com/SciML/OrdinaryDiffEq.jl/blob/0ea61dc630ea0d6159e70aa134bbb674ae13fa25/lib/OrdinaryDiffEqCore/src/solve.jl#L252), I then get an error at: https://github.com/SciML/OrdinaryDiffEq.jl/blob/0ea61dc630ea0d6159e70aa134bbb674ae13fa25/lib/OrdinaryDiffEqCore/src/solve.jl#L520

because zero(uBottomEltypeNoUnits) ends up calling zero(Any). I'm not sure what the right answer is here because I don't know what last_event_error represents.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants