Catalyst + DifferentiaEquations startup time

I am using Catalyist and DifferentialEquations to study a chemical reaction, but the startup times are somewhat annoying. I have the option, in this case, to write the ODE by hand (thus not requiring Catalyst), and I can also integrate the ODE “by hand”, and with that the complete package would be very quick to load.

Would it be possible, instead of reimplementing the ODEs and the integrator, to load only a small subset of these packages to have a reasonably fast startup time?

Or, more specifically, can I save the “reaction_network” object, at least, and then not require Catalyst anymore in the project?

Minimal working example:

module Test
  
using Catalyst
using DifferentialEquations

function __init__()
  rn = @reaction_network begin k, A --> B; end k
  ode = ODEProblem(rn,[0,0],(0,1),[0])
  s = solve(ode,p=[0,0],verbose=false)
end

end

julia> @time include("./Test.jl")
 75.207030 seconds (142.36 M allocations: 8.881 GiB, 3.66% gc time, 0.01% compilation time)
Main.Test

julia> @time include("./Test.jl")
WARNING: replacing module Test.
  0.071724 seconds (169.12 k allocations: 10.957 MiB, 92.80% compilation time)
Main.Test

If you don’t need the SDE/DDE/etc stuff from DiffEq.jl, you can just use OrdinaryDiffEq.jl, which should reduce load times significantly. Likewise, Catalyst just constructs a ReactionSystem from ModelingToolkit.jl, which you can also construct directly.

2 Likes

Those are definitely good suggestions, reducing by 30s the load time.

Does it make sense to try to save the ReactionSystem object to pass it directly to the ODEProblem, without having to construct it (and require ModelingToolkit) every time?

1 Like

The code to translate a ReactionSystem into an ODEProblem lives in MTK.jl, so if you’re saving & loading a ReactionSystem, you’ll have to have MTK.jl loaded to perform that translation. If you can, save the ODEProblem instead and use remake to modify parameters as needed.

2 Likes

That is definitely an option. Thanks for the idea.

1 Like

Is catylist using precompile statements effectively? Part of the answer could be to just fix the load speed.

None of the packages alone is too bad. But each of them takes ~10-15s to load, and also another 20s for the first run of the solve command.

(if anything can be improved on the package side I am not in the position to say anything right now).

1 Like

I’m working on it: Reduce the first time to solve from 5 seconds to 1 second for Tsit5 by ChrisRackauckas · Pull Request #1465 · SciML/OrdinaryDiffEq.jl · GitHub

4 Likes

You can do toexpr(convert(ODESystem,rn)) and it will spit out the code for generating the ODEs, or generate_function, etc.

1 Like

That is interesting. I tried that with the code below. But it is the first call to solve that is taking most of the time. Without Catalyst the startup is 10s faster, but the call to solve is taking ~30s here. Seems something is not quite right here, given that you are trying to reduce the first call to solve from 5 to 1 second?

module Test

#using Catalyst
using DifferentialEquations

function __init__()
  #rn = @reaction_network begin k, A --> B; end k
  rn_sys = begin
   var"##iv#258" = (@variables(t))[1]
   var"##sts#259" = (collect)(@variables(A(t), B(t)))
   var"##ps#260" = (collect)(@parameters(k))
   var"##eqs#261" = [(Differential(t))(A) ~ (*)(-1, k, A); (Differential(t))(B) ~ (*    )(k, A)] 
   var"##defs#262" = (Dict)()
   (ODESystem)(var"##eqs#261", var"##iv#258", var"##sts#259", var"##ps#260"; defaults = var"##defs#262", name = Symbol("##ReactionSystem#257"))
  end  
  ode = ODEProblem(rn_sys,[0,0],(0,1),[0])
  s = solve(ode,p=[0,0],verbose=false)
end

end

Depends on the solver…

Ah, sorry, I have understood that the default solver was Tsit5. With Tsit5 set explicitly the solve call takes 11s instead of 30s:

julia> @time using Catalyst
 11.435469 seconds (24.02 M allocations: 1.461 GiB, 5.40% gc time, 0.07% compilation time)

julia> @time using DifferentialEquations
  2.735503 seconds (7.82 M allocations: 691.218 MiB, 3.09% gc time, 3.02% compilation time)

julia> @time rn = @reaction_network begin k, A --> B; end k
  0.890990 seconds (1.91 M allocations: 115.968 MiB, 4.41% gc time, 99.74% compilation time)
Model ##ReactionSystem#257 with 1 equations
States (2):
  A(t)
  B(t)
Parameters (1):
  k

julia> @time ode = ODEProblem(rn,[0,0],(0,1),[0])
 18.670225 seconds (46.33 M allocations: 2.897 GiB, 3.76% gc time)
ODEProblem with uType Vector{Int64} and tType Int64. In-place: true
timespan: (0, 1)
u0: 2-element Vector{Int64}:
 0
 0

julia> @time s = solve(ode,Tsit5(),p=[0,0],verbose=false)
 11.466755 seconds (23.37 M allocations: 1.551 GiB, 4.95% gc time)

Would it be possible to have the ODEProblem saved just to pass it to the solver with different initial points?

Yeah, just use generate_function as mentioned earlier and cache the function.

1 Like

This is my full response: 22 seconds to 3 and now more: Let's fix all of the DifferentialEquations.jl + universe compile times! .

2 Likes

Thanks for all that, I was just reading that. Were all these PRs merged just these days? Today I have installed everything again to try to get rid of those Catalyst warnings and my impression was that startup time was way better, not anymore being a problem (I even added Plots as a dependency today :joy:)

Yes, it was in this last week. I’ve learned a lot about the Base Julia compiler, precompile files, and how it all interacts with type inference in my free time :sweat_smile:. We will need to merge Define prepare_alg dispatch for OrdinaryDiffEq AD algorithms by ChrisRackauckas · Pull Request #1470 · SciML/OrdinaryDif, and your specific case hits the issue https://github.com/SciML/DifferentialEquations.jl/issues/785 which I described in the post, so you won’t get the full benefit but you will get a chunk of it (you’ll be missing a good 20 seconds off of the compile time though…). I still need to rummage through Symbolics and ModelingToolkit as well for their first call time issues are too, which would affect Catalyst. However, we now have a plan laid out which is the important piece because it now means it can change from the effort of one to an effort of the community.

2 Likes

The exact example I posted initially, which took 75s to, now takes 61s (an improvement, but not much).

My package, though, now loads in 30s, which for the eventual restarts needed because of some struct change is totally ok.

Yup, so I think you’re hitting the same invalidation issues. That’s next… this will be a process. We’re just starting.

2 Likes

Since you are there, congratulations for the Catalyst package, it is great. By the way, I couldn’t find a paper to cite, and publications using it will come soon. (for non-computer scientists github stars are the same facebook likes :slight_smile:, we feel great, but nobody really cares).

Cite ModelingToolkit’s paper for now, since it’s the underlying compiler, and DifferentialEquations.jl for the solvers. Catalyst is getting is own paper very soon. We need to finish it :sweat_smile:

(for non-computer scientists github stars are the same facebook likes :slight_smile:, we feel great, but nobody really cares).

We’ve been putting them into grant applications and the like as a measurement of usage. It’s not a great measurement of usage, but having exponential growth in metrics is never bad and it’s been working for us.

2 Likes