# Speeding up repetitive calls to ODEProblem

**URL:** <https://discourse.julialang.org/t/speeding-up-repetitive-calls-to-odeproblem/87644>\
**Category:** Performance\
**Tags:** diffeq, type-stability\
**Created:** [September 22, 2022, 1:49pm UTC](https://discourse.julialang.org/t/speeding-up-repetitive-calls-to-odeproblem/87644 "2022-09-22T13:49:02Z")\
**Posts on this page:** 12\
**Page:** 1

<div class="post-metadata">

**Author:** ![cdawg](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cdawg/32/9811_2.png) [@cdawg](https://discourse.julialang.org/u/cdawg)\
**Post date:** [September 22, 2022, 1:49pm UTC](https://discourse.julialang.org/t/speeding-up-repetitive-calls-to-odeproblem/87644/1 "2022-09-22T13:49:02Z")

</div>

Hey ODE people. Thanks for all the mind-bending great work on top-of-the-line packages. I have stupid code solving the same ODE with different boundary conditions thousands of times. Unfortunately for this use case, this comes out to just one likelihood evaluation, so it’d be nice to be fast. 😅

So I took a look at the code profile, and it is painted with type instability inside ODEProblem

 ![Screenshot 2022-09-22 at 15.27.26](https://global.discourse-cdn.com/julialang/original/3X/b/b/bb5aa4ad3f79300526e46fcb4c090a4bd0bcd0a7.png)  
I’m wondering if I can feed ODEProblem something more digestible, maybe it needs annotations? Here’s the function that I’m calling so many times.

```julia
function construct_transitions(model, sol, start_time, end_time, 
    start_state, end_state)
    # returns the transition matrix element from start_time to end_time
    # constructed using the time-dependent potential given by sol
    u0::Vector{Float64} = zeros(model.nstates)
    u0[start_state] += 1
    tspan = (start_time, end_time)
    function mutgrad!(du,u,model,t)
        ψ::Vector{Float64} = sol(t)
        for j in 1:model.nstates
            du[j] = 0.0
            for i in 1:model.nstates
                if i != j
                    du[j] += u[i] * mutation_matrix(model, t, i, j) * exp(ψ[j] - ψ[i]) - 
                        u[j] * mutation_matrix(model, t, j, i) * exp(ψ[i] - ψ[j])
                    # a most intuitive representation of the probability
                end
            end
        end
    end
    prob = ODEProblem(mutgrad!, u0, tspan, model)
    psol = solve(prob,Tsit5(); tstops = model.tstops, save_everystep = false, save_start = false)
    return psol[1][end_state]
end

```

possibly relevant information **sol** is itself an ODE solution. **mutation\_matrix** is already annotated with a ::Float64 for the return value. (This is just integrating the Kolmolgorov equation with time dependent propagator.) If people are interested I can try to give enough to make a MWE but hoping that preexisting expertise will be enough. So grateful to Julian community.🙌

---

<div class="post-metadata">

**Author:** ![lmiq](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lmiq/32/18314_2.png) [@lmiq](https://discourse.julialang.org/u/lmiq)\
**Post date:** [September 22, 2022, 2:16pm UTC](https://discourse.julialang.org/t/speeding-up-repetitive-calls-to-odeproblem/87644/2 "2022-09-22T14:16:08Z")

</div>

You should try to avoid creating a new `ODEProblem` on every call.

Probably `remake` is what you are searching for. For example:

```julia

using DifferentialEquations

function multiple_odes()
    f(u,p,t) = 1.01*u
    u0 = 1/2
    tspan = (0.0,1.0)
    prob = ODEProblem(f,u0,tspan)
    for i in 1:100
        prob = remake(prob, u0 = rand())
        sol = solve(prob, Tsit5(), reltol=1e-8, abstol=1e-8)
    end
end

function multiple_odes0()
    f(u,p,t) = 1.01*u
    u0 = 1/2
    tspan = (0.0,1.0)
    for i in 1:100
        u0 = rand()
        prob = ODEProblem(f,u0,tspan)
        sol = solve(prob, Tsit5(), reltol=1e-8, abstol=1e-8)
    end
end

```

Gives:

```julia
julia> @btime multiple_odes()
  268.775 μs (4104 allocations: 621.91 KiB)

julia> @btime multiple_odes0()
  1.019 ms (14057 allocations: 1.04 MiB)

```

---

<div class="post-metadata">

**Author:** ![cdawg](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cdawg/32/9811_2.png) [@cdawg](https://discourse.julialang.org/u/cdawg)\
**Post date:** [September 22, 2022, 2:42pm UTC](https://discourse.julialang.org/t/speeding-up-repetitive-calls-to-odeproblem/87644/3 "2022-09-22T14:42:15Z")

</div>

Thanks for the tip @lmiq! `remake` looks very promising. I’ll have to refactor a little to share the ODEProblem instance between solves and will report back.

---

<div class="post-metadata">

**Author:** ![hbooth](https://avatars.discourse-cdn.com/v4/letter/h/e19b73/32.png) [@hbooth](https://discourse.julialang.org/u/hbooth)\
**Post date:** [September 22, 2022, 2:55pm UTC](https://discourse.julialang.org/t/speeding-up-repetitive-calls-to-odeproblem/87644/4 "2022-09-22T14:55:33Z")

</div>

Hi, sorry to hijack the thread but I am looking at a similar problem - do you know if remake is thread-safe? what I mean is, would it be possible to do

```julia
 @threads for i in 1:100
        new_prob = remake(prob, u0 = rand())
        sol = solve(new_prob, Tsit5(), reltol=1e-8, abstol=1e-8)
    end

```

Its not clear to me in what way remake affects the original problem… I am fairly new to julia so please excuse my ignorance! 🙂

---

<div class="post-metadata">

**Author:** ![lmiq](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lmiq/32/18314_2.png) [@lmiq](https://discourse.julialang.org/u/lmiq)\
**Post date:** [September 22, 2022, 2:59pm UTC](https://discourse.julialang.org/t/speeding-up-repetitive-calls-to-odeproblem/87644/5 "2022-09-22T14:59:14Z")

</div>

I think it is **not** (I may be wrong). You should use in this case a pool of problems, something like:

```julia
my_probs = [deepcopy(prob) for _ in 1:nthreads()]
for ithread in 1:nthreads()
    for i in 1:repeated_runs
        prob = remake(my_probs[ithread], ....)
        solve(prob)
    end
end

```

@hbooth I edited the code here, the `remake` should be inside the inner loop, maybe that caused the confusion.

---

<div class="post-metadata">

**Author:** ![hbooth](https://avatars.discourse-cdn.com/v4/letter/h/e19b73/32.png) [@hbooth](https://discourse.julialang.org/u/hbooth)\
**Post date:** [September 22, 2022, 3:06pm UTC](https://discourse.julialang.org/t/speeding-up-repetitive-calls-to-odeproblem/87644/6 "2022-09-22T15:06:08Z")

</div>

I was thinking about it from the perspective of using multithreading for the repeated runs i.e. use each available thread to evaluate each parameter choice. In this case, the code

```julia
repeated_runs = nthreads()
my_probs = [deepcopy(prob) for _ in 1:repeated_runs]

@threads for i in 1:repeated_runs
    new_prob = remake(my_probs[i], u0 = rand())
end

```

would surely just be equivalent in performance to the original

```julia
@threads for i in 1:repeated_runs
    u0 = rand()
    new_prob = ODEProblem(f,u0,tspan)
end

```

Does that make sense?

---

<div class="post-metadata">

**Author:** ![lmiq](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lmiq/32/18314_2.png) [@lmiq](https://discourse.julialang.org/u/lmiq)\
**Post date:** [September 22, 2022, 4:18pm UTC](https://discourse.julialang.org/t/speeding-up-repetitive-calls-to-odeproblem/87644/7 "2022-09-22T16:18:55Z")

</div>

No, not really. Building the problem is expensive (i. e. `prob = ODEProblem(f,u0,tspan)`) relative to `remake`ing it. You should use `remake` also to provide a different initial point for the problem, and if `prob` is not thread safe (I think it isn’t), the best to do is to create a different one for each thread.

(ps: I’m not a heavy user of these tools, so it may well be possible that someone else has better ideas about all this stuff).

---

<div class="post-metadata">

**Author:** ![ChrisRackauckas](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/chrisrackauckas/32/77_2.png) [@ChrisRackauckas](https://discourse.julialang.org/u/ChrisRackauckas)\
**Post date:** [September 22, 2022, 10:57pm UTC](https://discourse.julialang.org/t/speeding-up-repetitive-calls-to-odeproblem/87644/8 "2022-09-22T22:57:02Z")

</div>

> [@lmiq](#):
>
> You should use `remake` also to provide a different initial point for the problem, and if `prob` is not thread safe (I think it isn’t), the best to do is to create a different one for each thread.

Precisely.

Your original problem is also that inference is better if you do `ODEProblem{true}(f,u0,tspan)`, but I’m just going to see if I can fix that this week with Tricks.jl

---

<div class="post-metadata">

**Author:** ![hbooth](https://avatars.discourse-cdn.com/v4/letter/h/e19b73/32.png) [@hbooth](https://discourse.julialang.org/u/hbooth)\
**Post date:** [September 23, 2022, 1:37pm UTC](https://discourse.julialang.org/t/speeding-up-repetitive-calls-to-odeproblem/87644/9 "2022-09-23T13:37:44Z")

</div>

Just to clarify - `remake(prob;..)` is not an in-place function since prob is immutable, correct? In which case, shouldn’t it be assigned to some variable in the example codes given in this thread, i.e.

```julia
    new_prob = remake(prob;..)
    solve(new_prob,...)

```

rather than

```julia
    remake(prob;..)
    solve(prob,...)

```

---

<div class="post-metadata">

**Author:** ![Oscar\_Smith](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/oscar_smith/32/25343_2.png) [@Oscar\_Smith](https://discourse.julialang.org/u/Oscar_Smith)\
**Post date:** [September 23, 2022, 1:41pm UTC](https://discourse.julialang.org/t/speeding-up-repetitive-calls-to-odeproblem/87644/10 "2022-09-23T13:41:49Z")

</div>

yes.

---

<div class="post-metadata">

**Author:** ![lmiq](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lmiq/32/18314_2.png) [@lmiq](https://discourse.julialang.org/u/lmiq)\
**Post date:** [September 23, 2022, 2:35pm UTC](https://discourse.julialang.org/t/speeding-up-repetitive-calls-to-odeproblem/87644/11 "2022-09-23T14:35:10Z")

</div>

Thanks for pointing that out. I edited my posts to avoid future errors.

---

<div class="post-metadata">

**Author:** ![cdawg](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cdawg/32/9811_2.png) [@cdawg](https://discourse.julialang.org/u/cdawg)\
**Post date:** [October 11, 2022, 3:41pm UTC](https://discourse.julialang.org/t/speeding-up-repetitive-calls-to-odeproblem/87644/12 "2022-10-11T15:41:49Z")

</div>

Finally got around to refactoring with remake. Here’s the results

```julia
julia> @time evaluate_loglikelihood(lt, seasonal_model(ne_shared, 2.0*10^-3, 0.0))
  0.497888 seconds (1.41 M allocations: 90.216 MiB)
-79.5242729458968
julia> @time reevaluate_loglikelihood(lt, seasonal_model(ne_shared, 2.0*10^-3, 0.0))
  0.106464 seconds (416.61 k allocations: 36.014 MiB)
-79.5242729458968

```

profile looks good. Marking this as solved. Thanks for the help everyone!

 ![Screenshot 2022-10-11 at 17.36.01](https://global.discourse-cdn.com/julialang/original/3X/e/2/e298437582d425c6e008f621a1fc318a592b7052.png)
