# Type-instability with DifferentialEquations.jl when using nested structs

**URL:** <https://discourse.julialang.org/t/type-instability-with-differentialequations-jl-when-using-nested-structs/109764>\
**Category:** General Usage\
**Tags:** struct, type-stability, differentialequation\
**Created:** [February 5, 2024, 9:28pm UTC](https://discourse.julialang.org/t/type-instability-with-differentialequations-jl-when-using-nested-structs/109764 "2024-02-05T21:28:04Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![fgittins](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/fgittins/32/203670_2.png) [@fgittins](https://discourse.julialang.org/u/fgittins)\
**Post date:** [February 5, 2024, 9:28pm UTC](https://discourse.julialang.org/t/type-instability-with-differentialequations-jl-when-using-nested-structs/109764/1 "2024-02-05T21:28:04Z")

</div>

Hi Julia community,

I have been using `DifferentialEquations.jl` recently, which is an excellent package. In the spirit of writing performant Julia code, I have been trying to eliminate unnecessary heap allocations.

I have come across a type-instability that I have been unable to fix. The type instability arises when I nest one struct within another. Here I include an MWE illustrating the instability with a struct `Fit` nested in another struct `EOS`:

```julia
using DifferentialEquations

const conversion_factor = 755400.0

struct Fit
    m₁::Float64
    c₁::Float64
    m₂::Float64
    c₂::Float64

    function Fit()
        m₁ = 1.595
        c₁ = 3.438
        m₂ = 1.075
        c₂ = 3.484

        new(m₁, c₁, m₂, c₂)
    end
end

nbfun(fit::Fit, pnum) = 10^((log10(pnum) - fit.c₁)/fit.m₁)
ε(fit::Fit, nb) = 10^(fit.m₂*log10(nb) + fit.c₂)

struct EOS
    fit::Fit

    function EOS()
        fit = Fit()

        new(fit)
    end
end

ε(eos::EOS, p) = ε(eos.fit, nbfun(eos.fit, conversion_factor*p))/conversion_factor

function structure!(dy_dr, y, eos, r)
    m, p = y

    εᵣ = ε(eos, p)
    dν_dr = 2*(m + 4*π*r^3*p)/(r*(r - 2*m))

    dy_dr[1] = 4*π*r^2*εᵣ
    dy_dr[2] = - (εᵣ + p)*dν_dr/2
    dy_dr
end

function main()
    eos = EOS()
    pc = 1.19e-4

    prob = ODEProblem{true}(structure!, [0, pc], (1e-5, 10), eos)
    sol = solve(prob, Tsit5())
end

```

When I examine the types, I find

```julia
julia> @code_warntype main()
MethodInstance for main()
  from main() @ Main REPL[9]:1
Arguments
  #self#::Core.Const(main)
Locals
  sol::Any
  prob::ODEProblem{Vector{Float64}, Tuple{Float64, Float64}, true, EOS, ODEFunction{true, SciMLBase.AutoSpecialize, typeof(structure!), LinearAlgebra.UniformScaling{Bool}, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, typeof(SciMLBase.DEFAULT_OBSERVED), Nothing, Nothing}, Base.Pairs{Symbol, Union{}, Tuple{}, @NamedTuple{}}, SciMLBase.StandardODEProblem}
  pc::Float64
  eos::EOS
Body::Any
1 ─ (eos = Main.EOS())
│ (pc = 0.000119)
│ %3 = Core.apply_type(Main.ODEProblem, true)::Core.Const(ODEProblem{true})
│ %4 = Main.structure!::Core.Const(structure!)
│ %5 = Base.vect(0, pc::Core.Const(0.000119))::Vector{Float64}
│ %6 = Core.tuple(1.0e-5, 10)::Core.Const((1.0e-5, 10))
│ (prob = (%3)(%4, %5, %6, eos::Core.Const(EOS(Fit(1.595, 3.438, 1.075, 3.484)))))
│ %8 = prob::ODEProblem{Vector{Float64}, Tuple{Float64, Float64}, true, EOS, ODEFunction{true, SciMLBase.AutoSpecialize, typeof(structure!), LinearAlgebra.UniformScaling{Bool}, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, typeof(SciMLBase.DEFAULT_OBSERVED), Nothing, Nothing}, Base.Pairs{Symbol, Union{}, Tuple{}, @NamedTuple{}}, SciMLBase.StandardODEProblem}
│ %9 = Main.Tsit5()::Core.Const(Tsit5(; stage_limiter! = trivial_limiter!, step_limiter! = trivial_limiter!, thread = static(false),))
│ %10 = Main.solve(%8, %9)::Any
│ (sol = %10)
└── return %10

```

The type-unstable part is at the end with the variable `sol` as type `Any`.

Now, for this simple example, it is trivial to combine the two structs into one, which does eliminate the type-instability. However, for my purposes, I would like to keep the two structs separate.

How can I remove the type-instability? I would be grateful for any tips or suggestions on how I can address this problem.

---

<div class="post-metadata">

**Author:** ![abraemer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/abraemer/32/51403_2.png) [@abraemer](https://discourse.julialang.org/u/abraemer)\
**Post date:** [February 6, 2024, 8:23am UTC](https://discourse.julialang.org/t/type-instability-with-differentialequations-jl-when-using-nested-structs/109764/2 "2024-02-06T08:23:25Z")

</div>

Welcome to the discourse 🙂  
Unfortunately, I am not sure where this type instability is coming from. You took good care to not use global variables, no undertyped fields and the like. I suspect it could have to do with inlining, so you could try to just slap `@inline` in front of all functions and see whether that improves inference.

That being said, I think this particular type instability is not important for performance, since you can just use a [function barrier](https://docs.julialang.org/en/v1/manual/performance-tips/#kernel-functions) when you continue using `sol`.

---

<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:** [February 8, 2024, 7:31am UTC](https://discourse.julialang.org/t/type-instability-with-differentialequations-jl-when-using-nested-structs/109764/3 "2024-02-08T07:31:30Z")

</div>

What if you do:

```julia
DiffEqBase.anyeltypedual(x:: EOS, counter = 0) = Any

```

?

---

<div class="post-metadata">

**Author:** ![fgittins](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/fgittins/32/203670_2.png) [@fgittins](https://discourse.julialang.org/u/fgittins)\
**Post date:** [February 8, 2024, 10:07am UTC](https://discourse.julialang.org/t/type-instability-with-differentialequations-jl-when-using-nested-structs/109764/4 "2024-02-08T10:07:13Z")

</div>

Thanks for the suggestions. I can confirm that

```julia
DiffEqBase.anyeltypedual(x::EOS, counter=0) = Any

```

fixes the type-instability.

Is there a way to understand how the type-instability arose in the first place?

---

<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:** [February 8, 2024, 10:17am UTC](https://discourse.julialang.org/t/type-instability-with-differentialequations-jl-when-using-nested-structs/109764/5 "2024-02-08T10:17:46Z")

</div>

It’s from the Dual detection, something about Julia’s inference is giving up early. Can you open an issue?

---

<div class="post-metadata">

**Author:** ![fgittins](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/fgittins/32/203670_2.png) [@fgittins](https://discourse.julialang.org/u/fgittins)\
**Post date:** [February 8, 2024, 10:38am UTC](https://discourse.julialang.org/t/type-instability-with-differentialequations-jl-when-using-nested-structs/109764/6 "2024-02-08T10:38:59Z")

</div>

Sure. I can do that.

---

<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:** [February 9, 2024, 10:24am UTC](https://discourse.julialang.org/t/type-instability-with-differentialequations-jl-when-using-nested-structs/109764/7 "2024-02-09T10:24:57Z")

</div>

I fixed it in [Check dualness by fields instead of properties by ChrisRackauckas · Pull Request #999 · SciML/DiffEqBase.jl · GitHub](https://github.com/SciML/DiffEqBase.jl/pull/999)
