Why the separation of `ODEProblem` and `solve` in DifferentialEquations.jl?

We need this information in order to know how to dispatch between ODEs, SDEs, DAEs, DDEs, jump equations, etc. In some cases, a function, initial condition, and timespan are not enough to distinguish between these cases. It also let’s us to a lot of engineering and introspection at a higher level. See the coming JuliaCon talk on automated optimization and parallelism which relies on introspection of the problem types pre-solve. Would could have people paste the same 20 arguments in multiple points of their code, but seems decidedly against DRY.

The first thing to note is that if you are creating thousands of really small ODEProblems and want to avoid the dynamic checking going on, then you should do ODEProblem{false}(...) or ODEProblem{true}(...), i.e. directly declare whether it’s in in-place or out-of-place form. Normally this isn’t a huge deal so it’s not mentioned very often in the documentation, but it’s in there since there are scenarios where this helps.

Secondly, note that there are a lot of optimizations in Julia v1.5 (coming out next week) and Julia v1.6 which are specifically designed to optimize this case. Most notably,

  • Immutable structs (including tuples) that contain references can now be allocated on the stack, and allocated inline within arrays and other structs (#33886). This significantly reduces the number of heap allocations in some workloads. Code that requires assumptions about object layout and addresses (usually for interoperability with C or other languages) might need to be updated; for example any object that needs a stable address should be a mutable struct . As a result, Array view s no longer allocate ([#34126]).

On v1.5 the ODEProblem, since it is a fully typed immutable struct, should be completely elided on Julia v1.5. So that issue should completely disappear.

That’s the issue that you know about. Let me just quickly mention though that’s not the real issue: this is only 80 bytes and while annoying it’s not the allocation you’re actually worried about. The one you’re actually worried about a little bit more insidious is:

Essentially previous versions of Julia were unable to rely on constant values inside of keyword arguments, and so because you could do things like save_idxs=1 to change the output from an array to a scalar of just the first value, the existence of these features caused some instabilities which you have to be careful about. We were able to prove this was the case by isolated every issue and get it statically compiling on Julia v1.4, but it required removing a few keyword arguments which we noted were all linked to this idea.

This was fixed in more precise inference of `splatnew` by JeffBezanson · Pull Request #35976 · JuliaLang/julia · GitHub and it was the last thing added to the v1.5 backports: Backports for Julia 1.5-RC1 (or beta2) by KristofferC · Pull Request #36098 · JuliaLang/julia · GitHub so it should be fixed in the RC1. So please test the RC and see if that’s all fixed up. If that doesn’t reduce the last small allocation, I have a hook:

which would allow for specializing the compiled output, but it requires these changes in v1.5 to handle all of the keyword arguments (otherwise it’ll just error outputs which don’t match the default).

That said, one person did find a case which wasn’t specialized by this: Add keyword argument constant propogation to News by ChrisRackauckas · Pull Request #36292 · JuliaLang/julia · GitHub but we can make sure to open up an issue so this gets fixed in Julia v1.6 if that effects DiffEq users.

This sounds like a good JuliaCon time issue though: let’s get this all cleaned up.

5 Likes