|
| 1 | +module IGraphsExt |
| 2 | + |
| 3 | +using Graphs |
| 4 | +using IGraphs |
| 5 | +using LinearAlgebra |
| 6 | +using IGraphs: LibIGraph |
| 7 | + |
| 8 | +import Graphs: igraph, AbstractIGraph, IGraphAlgorithm |
| 9 | +import Graphs: sir_model, layout_kamada_kawai, layout_fruchterman_reingold, community_leiden, modularity_matrix |
| 10 | +import Graphs: betweenness_centrality, pagerank |
| 11 | +import Graphs: nv, ne, is_directed, vertices, edges, has_vertex, has_edge, inneighbors, outneighbors, edgetype, eltype |
| 12 | + |
| 13 | +# Note: IGraphs wrappers (IGraph, IGVectorInt, etc.) store the underlying C struct |
| 14 | +# in an `objref` field which is a Ref{LibIGraph.ctype}. |
| 15 | + |
| 16 | +function _check_ret(ret, funcname) |
| 17 | + if ret != LibIGraph.IGRAPH_SUCCESS |
| 18 | + error("$funcname failed with error code $ret") |
| 19 | + end |
| 20 | +end |
| 21 | + |
| 22 | +""" |
| 23 | + igraph(g::AbstractSimpleGraph) |
| 24 | +
|
| 25 | +Fast conversion from `Graphs.SimpleGraph` to `IGraphs.IGraph`. |
| 26 | +Uses `igraph_add_edges` for high performance. |
| 27 | +""" |
| 28 | +function igraph(g::Graphs.AbstractSimpleGraph) |
| 29 | + n = Graphs.nv(g) |
| 30 | + ig = IGraphs.IGraph(n; directed=Graphs.is_directed(g)) |
| 31 | + m = Graphs.ne(g) |
| 32 | + edges_vec = Vector{Int64}(undef, 2*m) |
| 33 | + for (i, e) in enumerate(Graphs.edges(g)) |
| 34 | + edges_vec[2*i-1] = Graphs.src(e) - 1 |
| 35 | + edges_vec[2*i] = Graphs.dst(e) - 1 |
| 36 | + end |
| 37 | + # Create the IGVectorInt wrapper |
| 38 | + v_edges = IGraphs.IGVectorInt(edges_vec) |
| 39 | + # Add edges in bulk using .objref to pass the underlying Ref to ccall |
| 40 | + ret = LibIGraph.igraph_add_edges(ig.objref, v_edges.objref, IGraphs.IGNull().objref) |
| 41 | + _check_ret(ret, "igraph_add_edges") |
| 42 | + return ig |
| 43 | +end |
| 44 | + |
| 45 | +# --- Graph API Implementation for AbstractIGraph --- |
| 46 | + |
| 47 | +Graphs.nv(g::AbstractIGraph) = Int(LibIGraph.igraph_vcount(g.objref)) |
| 48 | +Graphs.ne(g::AbstractIGraph) = Int(LibIGraph.igraph_ecount(g.objref)) |
| 49 | +Graphs.is_directed(g::AbstractIGraph) = Bool(LibIGraph.igraph_is_directed(g.objref)) |
| 50 | + |
| 51 | +Graphs.vertices(g::AbstractIGraph) = 1:nv(g) |
| 52 | + |
| 53 | +function Graphs.edges(g::AbstractIGraph) |
| 54 | + |
| 55 | + m = ne(g) |
| 56 | + v_edges = IGraphs.IGVectorInt(; _uninitialized=Val(true)) |
| 57 | + LibIGraph.igraph_vector_int_init(v_edges.objref, 2 * m) |
| 58 | + LibIGraph.igraph_get_edgelist(g.objref, v_edges.objref, false) |
| 59 | + edge_list = Vector(v_edges) |
| 60 | + ET = edgetype(g) |
| 61 | + return [ET(edge_list[2*i-1]+1, edge_list[2*i]+1) for i in 1:m] |
| 62 | +end |
| 63 | + |
| 64 | +Graphs.has_vertex(g::AbstractIGraph, v::Integer) = 1 <= v <= nv(g) |
| 65 | + |
| 66 | +function Graphs.has_edge(g::AbstractIGraph, u::Integer, v::Integer) |
| 67 | + eid = Ref{LibIGraph.igraph_integer_t}(-1) |
| 68 | + ret = LibIGraph.igraph_get_eid(g.objref, eid, u-1, v-1, is_directed(g), false) |
| 69 | + return eid[] != -1 |
| 70 | +end |
| 71 | + |
| 72 | +function Graphs.outneighbors(g::AbstractIGraph, v::Integer) |
| 73 | + res = IGraphs.IGVectorInt(; _uninitialized=Val(true)) |
| 74 | + LibIGraph.igraph_vector_int_init(res.objref, 0) |
| 75 | + # IGRAPH_OUT = 1 |
| 76 | + ret = LibIGraph.igraph_neighbors(g.objref, res.objref, v-1, 1) |
| 77 | + _check_ret(ret, "igraph_neighbors (out)") |
| 78 | + return [Int(x) + 1 for x in Vector(res)] |
| 79 | +end |
| 80 | + |
| 81 | +function Graphs.inneighbors(g::AbstractIGraph, v::Integer) |
| 82 | + res = IGraphs.IGVectorInt(; _uninitialized=Val(true)) |
| 83 | + LibIGraph.igraph_vector_int_init(res.objref, 0) |
| 84 | + # IGRAPH_IN = 2 |
| 85 | + ret = LibIGraph.igraph_neighbors(g.objref, res.objref, v-1, 2) |
| 86 | + _check_ret(ret, "igraph_neighbors (in)") |
| 87 | + return [Int(x) + 1 for x in Vector(res)] |
| 88 | +end |
| 89 | + |
| 90 | +Graphs.neighbors(g::AbstractIGraph, v::Integer) = outneighbors(g, v) |
| 91 | + |
| 92 | +Graphs.edgetype(g::AbstractIGraph) = Graphs.SimpleGraphs.SimpleEdge{Int} |
| 93 | +Graphs.eltype(g::AbstractIGraph) = Int |
| 94 | + |
| 95 | +# Fallback for other graph types |
| 96 | +igraph(g::AbstractGraph) = IGraphs.IGraph(g) |
| 97 | + |
| 98 | +# Helper to handle weights |
| 99 | +function _handle_weights(weights) |
| 100 | + if weights === nothing |
| 101 | + return IGraphs.IGNull() |
| 102 | + else |
| 103 | + return IGraphs.IGVectorFloat(weights) |
| 104 | + end |
| 105 | +end |
| 106 | + |
| 107 | +# --- Algorithm Implementations --- |
| 108 | + |
| 109 | +function sir_model(g::AbstractGraph{U}, ::IGraphAlgorithm; beta=0.1, gamma=0.1, no_sim=100) where U<:Integer |
| 110 | + ig = igraph(g) |
| 111 | + |
| 112 | + # result is an igraph_vector_ptr_t |
| 113 | + # We use a Ref to a pointer to hold the vector_ptr_t if it's not wrapped |
| 114 | + # or we can try to use a raw ccall if IGraphs doesn't provide it. |
| 115 | + |
| 116 | + # Let's assume LibIGraph has the type igraph_vector_ptr_t |
| 117 | + # Based on typical igraph wrappers, we might need to handle memory manually here. |
| 118 | + |
| 119 | + ptr_vec = Ref{LibIGraph.igraph_vector_ptr_t}() |
| 120 | + LibIGraph.igraph_vector_ptr_init(ptr_vec, 0) |
| 121 | + |
| 122 | + try |
| 123 | + ret = LibIGraph.igraph_sir(ig.objref, beta, gamma, no_sim, ptr_vec) |
| 124 | + _check_ret(ret, "igraph_sir") |
| 125 | + |
| 126 | + n_sim = LibIGraph.igraph_vector_ptr_size(ptr_vec) |
| 127 | + results = Vector{Vector{Float64}}(undef, n_sim) |
| 128 | + |
| 129 | + for i in 1:n_sim |
| 130 | + # Each element is an igraph_vector_t* |
| 131 | + v_ptr = LibIGraph.igraph_vector_ptr_get(ptr_vec, i-1) |
| 132 | + # We can wrap this in a temporary IGVectorFloat if we know it doesn't own the memory |
| 133 | + # or just copy it. |
| 134 | + # Assuming LibIGraph.igraph_vector_size(v_ptr) works |
| 135 | + v_size = LibIGraph.igraph_vector_size(v_ptr) |
| 136 | + res_v = Vector{Float64}(undef, v_size) |
| 137 | + for j in 1:v_size |
| 138 | + res_v[j] = LibIGraph.igraph_vector_get(v_ptr, j-1) |
| 139 | + end |
| 140 | + results[i] = res_v |
| 141 | + end |
| 142 | + return results |
| 143 | + finally |
| 144 | + # Clean up: destroy all vectors inside and the ptr vector itself |
| 145 | + # igraph_vector_ptr_destroy_all calls igraph_vector_destroy and then igraph_free on each element |
| 146 | + LibIGraph.igraph_vector_ptr_destroy_all(ptr_vec) |
| 147 | + end |
| 148 | +end |
| 149 | + |
| 150 | +function modularity_matrix(g::AbstractGraph{U}, ::IGraphAlgorithm; kwargs...) where U<:Integer |
| 151 | + ig = igraph(g) |
| 152 | + return IGraphs.modularity_matrix(ig; kwargs...) |
| 153 | +end |
| 154 | + |
| 155 | +function community_leiden(g::AbstractGraph{U}, ::IGraphAlgorithm; resolution=1.0, beta=0.01, n_iterations=10, kwargs...) where U<:Integer |
| 156 | + ig = igraph(g) |
| 157 | + membership = IGraphs.IGVectorInt(; _uninitialized=Val(true)) |
| 158 | + LibIGraph.igraph_vector_int_init(membership.objref, 0) |
| 159 | + |
| 160 | + nb_clusters = Ref{LibIGraph.igraph_int_t}(0) |
| 161 | + quality = Ref{LibIGraph.igraph_real_t}(0.0) |
| 162 | + |
| 163 | + ret = LibIGraph.igraph_community_leiden_simple(ig.objref, IGraphs.IGNull().objref, 0, resolution, beta, IGraphs.IGNull().objref, n_iterations, membership.objref, nb_clusters, quality) |
| 164 | + _check_ret(ret, "igraph_community_leiden_simple") |
| 165 | + return Vector(membership) |
| 166 | +end |
| 167 | + |
| 168 | +function layout_kamada_kawai(g::AbstractGraph{U}, ::IGraphAlgorithm; maxiter=100, epsilon=0.0, kkconst=0.0, kwargs...) where U<:Integer |
| 169 | + ig = igraph(g) |
| 170 | + n = nv(g) |
| 171 | + res = IGraphs.IGMatrixFloat(; _uninitialized=Val(true)) |
| 172 | + LibIGraph.igraph_matrix_init(res.objref, n, 2) |
| 173 | + ret = LibIGraph.igraph_layout_kamada_kawai(ig.objref, res.objref, false, maxiter, epsilon, kkconst, IGraphs.IGNull().objref, -100.0, 100.0, -100.0, 100.0) |
| 174 | + _check_ret(ret, "igraph_layout_kamada_kawai") |
| 175 | + return Matrix(res) |
| 176 | +end |
| 177 | + |
| 178 | +function layout_fruchterman_reingold(g::AbstractGraph{U}, ::IGraphAlgorithm; niter=500, kwargs...) where U<:Integer |
| 179 | + ig = igraph(g) |
| 180 | + n = nv(g) |
| 181 | + res = IGraphs.IGMatrixFloat(; _uninitialized=Val(true)) |
| 182 | + LibIGraph.igraph_matrix_init(res.objref, n, 2) |
| 183 | + ret = LibIGraph.igraph_layout_fruchterman_reingold(ig.objref, res.objref, false, niter, IGraphs.IGNull().objref, -100.0, 100.0, -100.0, 100.0) |
| 184 | + _check_ret(ret, "igraph_layout_fruchterman_reingold") |
| 185 | + return Matrix(res) |
| 186 | +end |
| 187 | + |
| 188 | +function Graphs.modularity(g::AbstractGraph{U}, membership::Vector{Int}, ::IGraphAlgorithm; weights=nothing, resolution=1.0, kwargs...) where U<:Integer |
| 189 | + ig = igraph(g) |
| 190 | + m_vec = IGraphs.IGVectorInt(membership .- 1) # igraph uses 0-indexed membership |
| 191 | + w = _handle_weights(weights) |
| 192 | + res = Ref{LibIGraph.igraph_real_t}(0.0) |
| 193 | + ret = LibIGraph.igraph_modularity(ig.objref, m_vec.objref, w.objref, resolution, is_directed(g), res) |
| 194 | + _check_ret(ret, "igraph_modularity") |
| 195 | + return res[] |
| 196 | +end |
| 197 | + |
| 198 | +# --- Dispatch Overrides for Core Algorithms --- |
| 199 | + |
| 200 | +function betweenness_centrality(g::AbstractGraph{U}, ::IGraphAlgorithm; weights=nothing, normalized=true, kwargs...) where U<:Integer |
| 201 | + ig = igraph(g) |
| 202 | + n = nv(g) |
| 203 | + res = IGraphs.IGVectorFloat(; _uninitialized=Val(true)) |
| 204 | + LibIGraph.igraph_vector_init(res.objref, n) |
| 205 | + w = _handle_weights(weights) |
| 206 | + |
| 207 | + ret = LibIGraph.igraph_betweenness(ig.objref, w.objref, res.objref, LibIGraph.igraph_vss_all(), is_directed(g), normalized) |
| 208 | + _check_ret(ret, "igraph_betweenness") |
| 209 | + |
| 210 | + return Vector(res) |
| 211 | +end |
| 212 | + |
| 213 | +function pagerank(g::AbstractGraph{U}, ::IGraphAlgorithm; damping=0.85, weights=nothing, kwargs...) where U<:Integer |
| 214 | + ig = igraph(g) |
| 215 | + n = nv(g) |
| 216 | + res = IGraphs.IGVectorFloat(; _uninitialized=Val(true)) |
| 217 | + LibIGraph.igraph_vector_init(res.objref, n) |
| 218 | + w = _handle_weights(weights) |
| 219 | + |
| 220 | + val = Ref{LibIGraph.igraph_real_t}(0.0) |
| 221 | + # Default behavior: try PRPACK first as it is more robust for small/disconnected graphs |
| 222 | + ret = LibIGraph.igraph_pagerank(ig.objref, w.objref, res.objref, val, damping, is_directed(g), LibIGraph.igraph_vss_all(), LibIGraph.IGRAPH_PAGERANK_ALGO_PRPACK, IGraphs.IGNull().objref) |
| 223 | + _check_ret(ret, "igraph_pagerank") |
| 224 | + return Vector(res) |
| 225 | +end |
| 226 | + |
| 227 | +# --- Specialized dispatches for AbstractIGraph --- |
| 228 | +# These ensure that IGraph types use C implementations automatically. |
| 229 | + |
| 230 | +Graphs.sir_model(g::AbstractIGraph; kwargs...) = sir_model(g, IGraphAlgorithm(); kwargs...) |
| 231 | +Graphs.modularity_matrix(g::AbstractIGraph; kwargs...) = modularity_matrix(g, IGraphAlgorithm(); kwargs...) |
| 232 | +Graphs.community_leiden(g::AbstractIGraph; kwargs...) = community_leiden(g, IGraphAlgorithm(); kwargs...) |
| 233 | +Graphs.layout_kamada_kawai(g::AbstractIGraph; kwargs...) = layout_kamada_kawai(g, IGraphAlgorithm(); kwargs...) |
| 234 | +Graphs.layout_fruchterman_reingold(g::AbstractIGraph; kwargs...) = layout_fruchterman_reingold(g, IGraphAlgorithm(); kwargs...) |
| 235 | +Graphs.betweenness_centrality(g::AbstractIGraph; kwargs...) = betweenness_centrality(g, IGraphAlgorithm(); kwargs...) |
| 236 | +Graphs.pagerank(g::AbstractIGraph; kwargs...) = pagerank(g, IGraphAlgorithm(); kwargs...) |
| 237 | + |
| 238 | +end # module |
0 commit comments