Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# News

## Unreleased

- Complete `Graphs.jl` `AbstractGraph` interface for `IGraph`: `edges`, `outneighbors`/`inneighbors`, `add_vertex!`, `rem_vertex!`, `copy`.
- Fix 0-indexing bug in `has_edge` (was passing 1-based indices to 0-based C API).
- Add `GraphsInterfaceChecker.jl` compliance tests.

## v1.0.0 - 2025-09-25

- Update the underlying igraph C library to v1.0.0.
Expand Down
58 changes: 53 additions & 5 deletions src/graph_api.jl
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,63 @@ end

Base.eltype(::IGraph) = LibIGraph.igraph_int_t
Base.zero(::Type{IGraph}) = IGraph(0)
# Graphs.edges # TODO
Graphs.edgetype(g::IGraph) = Graphs.SimpleGraphs.SimpleEdge{eltype(g)} # TODO maybe expose the edge id information from IGraph
Graphs.has_edge(g::IGraph,s,d) = LibIGraph.get_eid(g,s,d,false,false)[1]!=-1
Graphs.has_edge(g::IGraph,s,d) = LibIGraph.get_eid(g,s-1,d-1,false,false)[1]!=-1
Graphs.has_vertex(g::IGraph,n::Integer) = 1≤n≤Graphs.nv(g)
# Graphs.inneighbors # TODO
Graphs.is_directed(::Type{IGraph}) = false # TODO support directed graphs
Graphs.ne(g::IGraph) = LibIGraph.ecount(g)
Graphs.nv(g::IGraph) = LibIGraph.vcount(g)
# Graphs.outneighbors # TODO
Graphs.vertices(g::IGraph) = 1:Graphs.nv(g)

Graphs.add_edge!(g::IGraph, e::Graphs.SimpleGraphEdge) = LibIGraph.add_edge(g,e.src-1,e.dst-1)
struct IGraphEdgeIterator
graph::IGraph
end

Base.length(it::IGraphEdgeIterator) = Graphs.ne(it.graph)
Base.eltype(::Type{IGraphEdgeIterator}) = Graphs.SimpleGraphs.SimpleEdge{LibIGraph.igraph_int_t}

function Base.iterate(it::IGraphEdgeIterator, eid=0)
eid ≥ Graphs.ne(it.graph) && return nothing
(from, to) = LibIGraph.edge(it.graph, eid)
return (Graphs.SimpleGraphs.SimpleEdge(from+1, to+1), eid+1)
end

Graphs.edges(g::IGraph) = IGraphEdgeIterator(g)

function Graphs.outneighbors(g::IGraph, v::Integer)
neis = IGVectorInt()
LibIGraph.neighbors(g, neis, v-1, LibIGraph.IGRAPH_ALL, LibIGraph.IGRAPH_NO_LOOPS, LibIGraph.IGRAPH_NO_MULTIPLE)
result = Vector{LibIGraph.igraph_int_t}(undef, length(neis))
for i in eachindex(result)
result[i] = neis[i] + 1
end
return sort!(result)
end

Graphs.inneighbors(g::IGraph, v::Integer) = Graphs.outneighbors(g, v)

function Graphs.add_edge!(g::IGraph, s::Integer, d::Integer)
s == d && return false # no self-loops for simple graphs
Graphs.has_edge(g, s, d) && return false
LibIGraph.add_edge(g, s-1, d-1)
return true
end
Graphs.add_edge!(g::IGraph, e::Graphs.SimpleGraphEdge) = Graphs.add_edge!(g, e.src, e.dst)

function Graphs.add_vertex!(g::IGraph)
LibIGraph.igraph_add_vertices(g.objref, LibIGraph.igraph_int_t(1), C_NULL)
return true
end

function Graphs.rem_vertex!(g::IGraph, v::Integer)
Graphs.has_vertex(g, v) || return false
vs = LibIGraph.igraph_vss_1(LibIGraph.igraph_int_t(v-1))
LibIGraph.igraph_delete_vertices(g.objref, vs)
return true
end

function Base.copy(g::IGraph)
g2 = IGraph(;_uninitialized=Val(true))
LibIGraph.copy(g2, g)
return g2
end
1 change: 1 addition & 0 deletions test/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
ExplicitImports = "7d51a73a-1435-4ff3-83d9-f097790105c7"
Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6"
GraphsInterfaceChecker = "3bef136c-15ff-4091-acbb-1a4aafe67608"
IGraphs = "647e90d3-2106-487c-adb4-c91fc07b96ea"
Interfaces = "85a1e053-f937-4924-92a5-1367d23b7b87"
JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b"
Expand Down
77 changes: 77 additions & 0 deletions test/test_graphs_interface.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
@testitem "GraphsInterfaceChecker" begin

using IGraphs
using Graphs
using GraphsInterfaceChecker
using Interfaces
using Test

# Build a few representative graphs for testing
graphs = IGraph[
IGraph(cycle_graph(5)),
IGraph(path_graph(4)),
IGraph(complete_graph(6)),
IGraph(star_graph(5)),
]

# Test the mandatory part of the AbstractGraph interface
@test Interfaces.test(AbstractGraphInterface, IGraph, graphs; show=false)

# Test the optional mutation interface
@test Interfaces.test(AbstractGraphInterface{(:mutation,)}, IGraph, graphs; show=false)

@testset "edges roundtrip" begin
for g in graphs
sg = SimpleGraph(g)
ig = IGraph(sg)
@test Set(collect(edges(ig))) == Set(collect(edges(sg)))
@test nv(ig) == nv(sg)
@test ne(ig) == ne(sg)
end
end

@testset "neighbors" begin
g = IGraph(cycle_graph(5))
@test sort(outneighbors(g, 1)) == [2, 5]
@test sort(outneighbors(g, 3)) == [2, 4]
@test inneighbors(g, 1) == outneighbors(g, 1)
end

@testset "has_edge" begin
g = IGraph(path_graph(4))
@test has_edge(g, 1, 2)
@test has_edge(g, 2, 1)
@test has_edge(g, 2, 3)
@test !has_edge(g, 1, 3)
@test !has_edge(g, 1, 4)
end

@testset "mutation" begin
g = IGraph(path_graph(3))
@test nv(g) == 3
@test ne(g) == 2

@test add_vertex!(g)
@test nv(g) == 4
@test add_edge!(g, 3, 4)
@test ne(g) == 3
@test has_edge(g, 3, 4)

# Duplicate edge
@test !add_edge!(g, 3, 4)
# Self-loop
@test !add_edge!(g, 1, 1)

g2 = copy(g)
@test nv(g2) == nv(g)
@test ne(g2) == ne(g)
add_vertex!(g2)
@test nv(g2) == nv(g) + 1

@test rem_vertex!(g, 4)
@test nv(g) == 3
@test ne(g) == 2
@test !rem_vertex!(g, 10)
end

end
Loading