Solving type instability when using Graphs.jl

This is the flame plot I get when profiling my iterative solver.
You can see it’s pointed by tiny red blocks, which I’m trying to address one by one.

In particular I’m now looking at the one highlighted by the red arrow, occurring at solver.jl, solve!: line 51.

Here's the full `solve!` function
function solve!(n::Network, dt::Float64, current_time::Float64)::Nothing
    for edge in n.edges
        s = Graphs.src(edge)
        t = Graphs.dst(edge)

        s == 1 && set_inlet_bc(current_time, dt, n.vessels[(s, t)], n.heart)

        # TODO: multiple inlets

        # vessel
        muscl!(n.vessels[(s, t)], dt, n.blood)

        # downstream
        outdeg::Int64 = Graphs.outdegree(n.graph, t)
        if outdeg == 0 # outlet
            set_outlet_bc(dt, n.vessels[(s, t)], n.blood.rho)
        elseif outdeg == 1
            indeg::Int64 = Graphs.indegree(n.graph, t)
            d::Int64 = first(Graphs.outneighbors(n.graph, t))
            if indeg == 1 # conjunction
                join_vessels!(n.blood, n.vessels[(s, t)], n.vessels[(t, d)])
                # TODO: test ANASTOMOSIS!!!
            elseif indeg == 2 # anastomosis
                ps::Vector{Int64} = Graphs.inneighbors(n.graph, t)
                if t == max(ps[1], ps[2])
                    solveAnastomosis(
                        n.vessels[(ps[1], t)],
                        n.vessels[(ps[2], t)],
                        n.vessels[(t, d)],
                    )
                end
            end
        elseif outdeg == 2 # bifurcation
            ds::Vector{Int64} = Graphs.outneighbors(n.graph, t)
            join_vessels!(n, n.vessels[(s, t)], n.vessels[t, ds[1]], n.vessels[t, ds[2]])
        end
    end
end

and this is the Network struct in case it is useful

struct Network
    graph::SimpleDiGraph
    edges::Vector{Graphs.SimpleGraphs.SimpleEdge{Int64}}
    vessels::Dict{Tuple{Int,Int},Vessel}
    blood::Blood
    heart::Heart
    Ccfl::Float64
    bifU::MVector{6, Float64}
    bifW::MVector{3, Float64}
    bifF::MVector{6, Float64}
    bifJ::MArray{Tuple{6, 6}, Float64, 2, 36}
end

the relevant line is

outdeg::Int64 = Graphs.outdegree(n.graph, t)

when clicking on that block and calling warntype_clicked() I get the following

`warntype_clicked()` result
MethodInstance for openBF.solve!(::openBF.Network, ::Float64, ::Float64)
  from solve!(n::openBF.Network, dt::Float64, current_time::Float64) @ openBF ~/openBF/src/solver.jl:37
Arguments
  #self#::Core.Const(openBF.solve!)
  n::openBF.Network
  dt::Float64
  current_time::Float64
Locals
  @_5::Union{Nothing, Tuple{Graphs.SimpleGraphs.SimpleEdge{Int64}, Int64}}
  edge::Graphs.SimpleGraphs.SimpleEdge{Int64}
  ds::Vector{Int64}
  ps::Vector{Int64}
  d::Int64
  indeg::Int64
  outdeg::Int64
  t::Int64
  s::Int64
  @_14::Int64
  @_15::Int64
  @_16::Integer
  @_17::Vector{T} where T<:Integer
  @_18::Vector{T} where T<:Integer
  @_19::Nothing
Body::Nothing
1 ── %1   = openBF.Nothing::Core.Const(Nothing)
│    %2   = Base.getproperty(n, :edges)::Vector{Graphs.SimpleGraphs.SimpleEdge{Int64}}
│           (@_5 = Base.iterate(%2))
│    %4   = (@_5 === nothing)::Bool
│    %5   = Base.not_int(%4)::Bool
└───        goto #32 if not %5
2 ┄─        Core.NewvarNode(:(ds))
│           Core.NewvarNode(:(ps))
│           Core.NewvarNode(:(d))
│           Core.NewvarNode(:(indeg))
│           Core.NewvarNode(:(outdeg))
│    %12  = @_5::Tuple{Graphs.SimpleGraphs.SimpleEdge{Int64}, Int64}
│           (edge = Core.getfield(%12, 1))
│    %14  = Core.getfield(%12, 2)::Int64
│    %15  = Graphs.src::Core.Const(Graphs.src)
│           (s = (%15)(edge))
│    %17  = Graphs.dst::Core.Const(Graphs.dst)
│           (t = (%17)(edge))
│    %19  = (s == 1)::Bool
└───        goto #4 if not %19
3 ── %21  = Base.getproperty(n, :vessels)::Dict{Tuple{Int64, Int64}, openBF.Vessel}
│    %22  = Core.tuple(s, t)::Tuple{Int64, Int64}
│    %23  = Base.getindex(%21, %22)::openBF.Vessel
│    %24  = Base.getproperty(n, :heart)::openBF.Heart
│           openBF.set_inlet_bc(current_time, dt, %23, %24)
└───        goto #4
4 ┄─ %27  = Base.getproperty(n, :vessels)::Dict{Tuple{Int64, Int64}, openBF.Vessel}
│    %28  = Core.tuple(s, t)::Tuple{Int64, Int64}
│    %29  = Base.getindex(%27, %28)::openBF.Vessel
│    %30  = Base.getproperty(n, :blood)::openBF.Blood
│           openBF.muscl!(%29, dt, %30)
│    %32  = Graphs.outdegree::Core.Const(Graphs.outdegree)
│    %33  = Base.getproperty(n, :graph)::Graphs.SimpleGraphs.SimpleDiGraph
│    %34  = (%32)(%33, t)::Int64
│           (@_14 = %34)
│    %36  = (@_14 isa openBF.Int64)::Core.Const(true)
└───        goto #6 if not %36
5 ──        goto #7
6 ──        Core.Const(:(Base.convert(openBF.Int64, @_14)))
└───        Core.Const(:(@_14 = Core.typeassert(%39, openBF.Int64)))
7 ┄─        (outdeg = @_14)
│    %42  = (outdeg == 0)::Bool
└───        goto #9 if not %42
8 ── %44  = Base.getproperty(n, :vessels)::Dict{Tuple{Int64, Int64}, openBF.Vessel}
│    %45  = Core.tuple(s, t)::Tuple{Int64, Int64}
│    %46  = Base.getindex(%44, %45)::openBF.Vessel
│    %47  = Base.getproperty(n, :blood)::openBF.Blood
│    %48  = Base.getproperty(%47, :rho)::Float64
│           openBF.set_outlet_bc(dt, %46, %48)
└───        goto #30
9 ── %51  = (outdeg == 1)::Bool
└───        goto #25 if not %51
10 ─ %53  = Graphs.indegree::Core.Const(Graphs.indegree)
│    %54  = Base.getproperty(n, :graph)::Graphs.SimpleGraphs.SimpleDiGraph
│    %55  = (%53)(%54, t)::Int64
│           (@_15 = %55)
│    %57  = (@_15 isa openBF.Int64)::Core.Const(true)
└───        goto #12 if not %57
11 ─        goto #13
12 ─        Core.Const(:(Base.convert(openBF.Int64, @_15)))
└───        Core.Const(:(@_15 = Core.typeassert(%60, openBF.Int64)))
13 ┄        (indeg = @_15)
│    %63  = Graphs.outneighbors::Core.Const(Graphs.outneighbors)
│    %64  = Base.getproperty(n, :graph)::Graphs.SimpleGraphs.SimpleDiGraph
│    %65  = (%63)(%64, t)::Vector{T} where T<:Integer
│    %66  = openBF.first(%65)::Integer
│           (@_16 = %66)
│    %68  = (@_16 isa openBF.Int64)::Bool
└───        goto #15 if not %68
14 ─        goto #16
15 ─ %71  = Base.convert(openBF.Int64, @_16)::Int64
└───        (@_16 = Core.typeassert(%71, openBF.Int64))
16 ┄        (d = @_16::Int64)
│    %74  = (indeg == 1)::Bool
└───        goto #18 if not %74
17 ─ %76  = Base.getproperty(n, :blood)::openBF.Blood
│    %77  = Base.getproperty(n, :vessels)::Dict{Tuple{Int64, Int64}, openBF.Vessel}
│    %78  = Core.tuple(s, t)::Tuple{Int64, Int64}
│    %79  = Base.getindex(%77, %78)::openBF.Vessel
│    %80  = Base.getproperty(n, :vessels)::Dict{Tuple{Int64, Int64}, openBF.Vessel}
│    %81  = Core.tuple(t, d)::Tuple{Int64, Int64}
│    %82  = Base.getindex(%80, %81)::openBF.Vessel
│           openBF.join_vessels!(%76, %79, %82)
└───        goto #24
18 ─ %85  = (indeg == 2)::Bool
└───        goto #24 if not %85
19 ─ %87  = Graphs.inneighbors::Core.Const(Graphs.inneighbors)
│    %88  = Base.getproperty(n, :graph)::Graphs.SimpleGraphs.SimpleDiGraph
│    %89  = (%87)(%88, t)::Vector{T} where T<:Integer
│    %90  = Core.apply_type(openBF.Vector, openBF.Int64)::Core.Const(Vector{Int64})
│           (@_17 = %89)
│    %92  = (@_17 isa %90)::Bool
└───        goto #21 if not %92
20 ─        goto #22
21 ─ %95  = Base.convert(%90, @_17)::Vector{Int64}
└───        (@_17 = Core.typeassert(%95, %90))
22 ┄        (ps = @_17::Vector{Int64})
│    %98  = t::Int64
│    %99  = Base.getindex(ps, 1)::Int64
│    %100 = Base.getindex(ps, 2)::Int64
│    %101 = openBF.max(%99, %100)::Int64
│    %102 = (%98 == %101)::Bool
└───        goto #24 if not %102
23 ─ %104 = Base.getproperty(n, :vessels)::Dict{Tuple{Int64, Int64}, openBF.Vessel}
│    %105 = Base.getindex(ps, 1)::Int64
│    %106 = Core.tuple(%105, t)::Tuple{Int64, Int64}
│    %107 = Base.getindex(%104, %106)::openBF.Vessel
│    %108 = Base.getproperty(n, :vessels)::Dict{Tuple{Int64, Int64}, openBF.Vessel}
│    %109 = Base.getindex(ps, 2)::Int64
│    %110 = Core.tuple(%109, t)::Tuple{Int64, Int64}
│    %111 = Base.getindex(%108, %110)::openBF.Vessel
│    %112 = Base.getproperty(n, :vessels)::Dict{Tuple{Int64, Int64}, openBF.Vessel}
│    %113 = Core.tuple(t, d)::Tuple{Int64, Int64}
│    %114 = Base.getindex(%112, %113)::openBF.Vessel
└───        openBF.solveAnastomosis(%107, %111, %114)
24 ┄        goto #30
25 ─ %117 = (outdeg == 2)::Bool
└───        goto #30 if not %117
26 ─ %119 = Graphs.outneighbors::Core.Const(Graphs.outneighbors)
│    %120 = Base.getproperty(n, :graph)::Graphs.SimpleGraphs.SimpleDiGraph
│    %121 = (%119)(%120, t)::Vector{T} where T<:Integer
│    %122 = Core.apply_type(openBF.Vector, openBF.Int64)::Core.Const(Vector{Int64})
│           (@_18 = %121)
│    %124 = (@_18 isa %122)::Bool
└───        goto #28 if not %124
27 ─        goto #29
28 ─ %127 = Base.convert(%122, @_18)::Vector{Int64}
└───        (@_18 = Core.typeassert(%127, %122))
29 ┄        (ds = @_18::Vector{Int64})
│    %130 = Base.getproperty(n, :vessels)::Dict{Tuple{Int64, Int64}, openBF.Vessel}
│    %131 = Core.tuple(s, t)::Tuple{Int64, Int64}
│    %132 = Base.getindex(%130, %131)::openBF.Vessel
│    %133 = Base.getproperty(n, :vessels)::Dict{Tuple{Int64, Int64}, openBF.Vessel}
│    %134 = t::Int64
│    %135 = Base.getindex(ds, 1)::Int64
│    %136 = Base.getindex(%133, %134, %135)::openBF.Vessel
│    %137 = Base.getproperty(n, :vessels)::Dict{Tuple{Int64, Int64}, openBF.Vessel}
│    %138 = t::Int64
│    %139 = Base.getindex(ds, 2)::Int64
│    %140 = Base.getindex(%137, %138, %139)::openBF.Vessel
└───        openBF.join_vessels!(n, %132, %136, %140)
30 ┄        (@_5 = Base.iterate(%2, %14))
│    %143 = (@_5 === nothing)::Bool
│    %144 = Base.not_int(%143)::Bool
└───        goto #32 if not %144
31 ─        goto #2
32 ┄        (@_19 = nothing)
│    %148 = (@_19 isa %1)::Core.Const(true)
└───        goto #34 if not %148
33 ─        goto #35
34 ─        Core.Const(:(Base.convert(%1, @_19)))
└───        Core.Const(:(@_19 = Core.typeassert(%151, %1)))
35 ┄        return @_19
and since the color coding is lost, here are screenshots



I think that line 51 refers to this

%33  = Base.getproperty(n, :graph)::Graphs.SimpleGraphs.SimpleDiGraph

but I’m not really understanding what the problem could be there.

The same warning occurs every time I use a function from Graphs.jl

I think this is due to your struct having an abstractly typed field.

You can fix this in many different ways, from specific to generic:

struct Network
    graph::SimpleDiGraph{Int}
    ...
end
struct Network{V<:Integer}
    graph::SimpleDiGraph{V}
    ...
end
struct Network{G<:AbstractGraph}
    graph::G
    ...
end
2 Likes

that did the trick thanks!

what about the yellow line?

@_5::Union{Nothing, Tuple{Graphs.SimpleGraphs.SimpleEdge{Int64}, Int64}}

I think it is related to the iterator

for edge in n.edges
    ...
end

An iterator always returns nothing at the end, so this type-stability is 1) expected and 2) a small Union between two types, so the compiler is not bothered at all. That is why you see it in yellow and not red

1 Like

thanks for the reply @gdalle

1 Like