# Performance Warning when Solving Parameterized ODE

**URL:** <https://discourse.julialang.org/t/performance-warning-when-solving-parameterized-ode/101541>\
**Category:** Performance\
**Created:** [July 12, 2023, 3:19pm UTC](https://discourse.julialang.org/t/performance-warning-when-solving-parameterized-ode/101541 "2023-07-12T15:19:20Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![wg030](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/wg030/32/12944_2.png) [@wg030](https://discourse.julialang.org/u/wg030)\
**Post date:** [July 12, 2023, 3:19pm UTC](https://discourse.julialang.org/t/performance-warning-when-solving-parameterized-ode/101541/1 "2023-07-12T15:19:20Z")

</div>

Hi!

With one of the latest Julia updates I got the following warning while running my code:

> Warning: Using arrays or dicts to store parameters of different types can hurt performance.  
> │ Consider using tuples instead.

I figured out quickly that the warning has its origin in the parameters that I have associated with my ODE.  
I created the following MWE to demonstrate the behaviour of my code:

```julia
using DifferentialEquations

function pendulum!(u̇, u, p, t)

    m = 1.0
    g = 9.81

    l = p[1]
    M = p[3]

    u̇[1] = u[2]
    u̇[2] = -3g / (2l) * sin(u[1]) + 3 / (m * l^2) * M(t)

end

function ctrl_f!( integrator)

    t = integrator.t

    if t ≥ 5.0
        A = integrator.p[2]
        M_new(t) = 2A * sin(t)
        integrator.p[3] = M_new
        #integrator.p = (integrator.p[1:2]..., M_new) # this doen't work
    end

    u_modified!( integrator, false)

end

rhs = ODEFunction( pendulum!)

u₀ = [0.01, 0.0]

t_0, t_f = 0.0, 10.0
t = t_0:0.1:t_f

l = 1.0
A = 0.1
M(t) = A * sin(t)
ode_paras = [l, A, M]
#ode_paras = (l, A, M) # this doen't work

cb = PresetTimeCallback( t[2:end], ctrl_f!)

prob = ODEProblem( rhs, u₀, (t_0, t_f), ode_paras, callback=cb)
sol = solve( prob)

```

As you can see the problem here is not simply solved by using a tuple instead of an array. The main reason for this is that i need to change the parameters during the integration process of my ODE solver which can be done using arrays with `integrator.p[3] = M_new`. However, switching from an array to a tuple in order to avoid the warning above changing the parameters by` integrator.p = (integrator.p[1:2]..., M_new)` yields the following error:

> ERROR: MethodError: Cannot `convert` an object of type var"#M\_new#7"{Float64} to an object of type typeof(M)

So my question is: What is the recommened way when you want to get rid of this performance warning? So how can I use tuples here effectively?

**Please note:** I am aware of the fact that for this MWE there would be absolutely no need to use a `PresetTimeCallback` and change the parameters. The easiest way would be to construct a suitable piecewise function `M` and work without a callback. But I did that for the illustration of my acutal, of course, way more complex problem.

---

<div class="post-metadata">

**Author:** ![contradict](https://avatars.discourse-cdn.com/v4/letter/c/ac91a4/32.png) [@contradict](https://discourse.julialang.org/u/contradict)\
**Post date:** [July 12, 2023, 4:18pm UTC](https://discourse.julialang.org/t/performance-warning-when-solving-parameterized-ode/101541/2 "2023-07-12T16:18:55Z")

</div>

Every function is a distinct type in Julia, so code with mutable function values will always be type-unstable which is what this performance warning is for.

I think the best option would be to define `M` once and parameterize it more fully. This may be similar to your piecewise idea, and it is likely to have the best performance.

```julia
p = (l, A, 1.0)
M(p, t) = p[3] * p[2] * sin(t)

```

Of if you need more flexibility

```julia
p = (l, A, :initial)
function M(p, t)
    mode = p[3]
    if mode == :initial
       ...
    elseif mode == :next
      ....
    ...
    end
end

```

If there is some reason that won’t work and you have a small number of functions, you could store them all in the tuple and modify a function index:

```julia
M1(t) = A * sin(t)
M2(t) = 2A * sin(t)
ode_paras = (l, A, M1, M2, fidx)

... inside your integrand ...

    M = p[p[5]]

... inside your callback ...
    if t ≥ 5.0
        # tuples are immutable so replace the whole thing (Accessors.jl can make this less verbose)
        integrator.p = (integrator.p[1:4]..., 4)
    end

```

If you have more details about your real problem, perhaps there is a better solution still!

---

<div class="post-metadata">

**Author:** ![wg030](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/wg030/32/12944_2.png) [@wg030](https://discourse.julialang.org/u/wg030)\
**Post date:** [July 13, 2023, 2:57pm UTC](https://discourse.julialang.org/t/performance-warning-when-solving-parameterized-ode/101541/3 "2023-07-13T14:57:02Z")

</div>

Thanks a lot for your suggestions, @contradict

> [@contradict](#):
>
> I think the best option would be to define `M` once and parameterize it more fully.

It seems as if your are completely right here.

I ran the original array version that I posted yesterday and used `BenchmarkTools` which gave me

```julia
BenchmarkTools.Trial: 10000 samples with 1 evaluation.
 Range (min … max): 392.400 μs … 3.845 ms ┊ GC (min … max): 0.00% … 84.24%
 Time (median): 407.600 μs ┊ GC (median): 0.00%
 Time (mean ± σ): 451.262 μs ± 274.042 μs ┊ GC (mean ± σ): 6.08% ± 8.62%

  █▆▄▁ ▁
  ████▇▄▄▃▃▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▅ █
  392 μs Histogram: log(frequency) by time 3.01 ms <

 Memory estimate: 475.16 KiB, allocs estimate: 16271.

```

Then I compared that with your flexible solution that uses the symbols `:initial` and `:next` and got the results

```julia
BenchmarkTools.Trial: 10000 samples with 1 evaluation.
 Range (min … max): 145.700 μs … 3.529 ms ┊ GC (min … max): 0.00% … 92.74%
 Time (median): 157.400 μs ┊ GC (median): 0.00%
 Time (mean ± σ): 178.789 μs ± 189.894 μs ┊ GC (mean ± σ): 8.00% ± 7.07%

   ▃▆▇█▇▇▆▄▃▄▅▆▅▅▄▃▃▂▁▁▁▁▁▁▁▁ ▂
  █████████████████████████████▇██▇██▇█▇▇▇▆█▆▇▆▆▆██▇▇▅▅▅▄▅▅▆▇▆▅ █
  146 μs Histogram: log(frequency) by time 256 μs <

 Memory estimate: 264.36 KiB, allocs estimate: 2835.

```

Needless to say that the performance warning does not appear here anymore.  
I haven’t tried it out for my actual problem yet, but thinking about it makes me confident that your idea can be realized for it.

Nevertheless, I also found an interesting other solution that might be worth mentioning for those cases where for some reason you cannot do it the way you suggested.  
The solution works with `NamedTuple` only needs little modification compared to the MWE:

```julia
#ode_paras = [l, A, M]
ode_paras = NamedTuple{(:p1, :p2, :p3), Tuple{typeof(l), typeof(A), Function}}( ( l, A, M))

```

```julia
#integrator.p[3] = M_new
integrator.p = merge( integrator.p, (p3=M_new, ))

```

This gets rid of the performance warning, too, is, of course, more efficient than the array version, but not as efficient that simply parametrizing `M`:

```julia
BenchmarkTools.Trial: 10000 samples with 1 evaluation.
 Range (min … max): 259.800 μs … 3.652 ms ┊ GC (min … max): 0.00% … 88.50%
 Time (median): 273.000 μs ┊ GC (median): 0.00%
 Time (mean ± σ): 307.277 μs ± 226.273 μs ┊ GC (mean ± σ): 6.66% ± 8.10%

  ▂▅██▆▃▃▃▄▅▆▆▄▃▃▂▂▂▁▁▁▂▁▁▁▁▁▁ ▂
  ████████████████████████████████▇█▇▇▇▅▆▆▆▅▄▄▃▂▃▄▂▂▃▅▆▆▅▄▅▂▄▂▂ █
  260 μs Histogram: log(frequency) by time 451 μs <

 Memory estimate: 388.67 KiB, allocs estimate: 10763.

```
