Skip to content
Open
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
7 changes: 7 additions & 0 deletions .typos.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[default.extend-words]
# igraph C library identifiers
neis = "neis"
eid = "eid"

[files]
extend-exclude = ["src/LibIGraph.jl"]
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# News

## v1.1.0 (Unreleased)

- Maintenance and CI scaffolding improvements.


## v1.0.0 - 2025-09-25

- Update the underlying igraph C library to v1.0.0.
Expand All @@ -12,7 +17,8 @@

## v0.10.17 - 2025-06-29

- `IGNull` is introduced as a convenient placehold argument for when the low-level C function expects a `NULL` as a default.
- `IGNull` is introduced as a convenient placeholder argument for when the low-level C function expects a `NULL` as a default.


## v0.10.16 - 2025-04-21

Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ By default, all of these types are initialized, but empty. If you want to create

### Alternatives to Graphs.jl algorithms

Some Graphs.jl functions have new methods defined here, which provide an alternative implementation for the given algorithm. E.g. `Graphs.diameter(graph)` runs a Julia-native implementation of that algorithm from `Graphs.jl`. Here we add the method `diamater(graph, ::IGraphAlg)` which converts `graph` to an `IGraph` type and runs the corresponding algorithm from the `igraph` C library.
Some Graphs.jl functions have new methods defined here, which provide an alternative implementation for the given algorithm. E.g. `Graphs.diameter(graph)` runs a Julia-native implementation of that algorithm from `Graphs.jl`. Here we add the method `diameter(graph, ::IGraphAlg)` which converts `graph` to an `IGraph` type and runs the corresponding algorithm from the `igraph` C library.


Dispatch to these new methods happens by adding an instance of the `IGraphAlg` type.

Expand Down
12 changes: 12 additions & 0 deletions benchmark/benchmarks.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using BenchmarkTools
using IGraphs
using Graphs

const SUITE = BenchmarkGroup()

SUITE["construction"] = BenchmarkGroup()
SUITE["construction"]["default"] = @benchmarkable IGraph()


SUITE["conversion"] = BenchmarkGroup()
SUITE["conversion"]["SimpleGraph_to_IGraph"] = @benchmarkable IGraph($g) setup=(g = Graphs.cycle_graph(100))
3 changes: 3 additions & 0 deletions docs/Project.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[deps]
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
IGraphs = "647e90d3-2106-487c-adb4-c91fc07b96ea"
11 changes: 11 additions & 0 deletions docs/make.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using Documenter
using IGraphs

makedocs(
sitename = "IGraphs.jl",
modules = [IGraphs],
warnonly = true,
pages = [
"Home" => "index.md",
],
)
27 changes: 27 additions & 0 deletions docs/src/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# IGraphs.jl

A Julia wrapper for the [igraph](https://igraph.org/) C library, providing high-performance graph algorithms through the [Graphs.jl](https://github.com/JuliaGraphs/Graphs.jl) interface.

## Installation

```julia
using Pkg
Pkg.add("IGraphs")
```

## Quick Start

```julia
using IGraphs, Graphs

# Create an undirected graph
g = IGraph(10)
add_edge!(g, 1, 2)
add_edge!(g, 2, 3)


# Use standard Graphs.jl algorithms

println(nv(g)) # 10
println(ne(g)) # 2
```
2 changes: 2 additions & 0 deletions src/IGraphs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export LibIGraph, IGraph, IGraphException,
IGBitSet,
#IGraphList,
IGVectorIntList, IGVectorFloatList, IGMatrixFloatList, IGBitSetList,
IGVectorPtr,
IGAdjList,
IGNull,
IGraphAlg,
Expand All @@ -19,6 +20,7 @@ include("wrapccall.jl")

include(modifymodule, "LibIGraph.jl")


include("scalar_types.jl")
include("types.jl")
include("graph_api.jl")
Expand Down
139 changes: 127 additions & 12 deletions src/graph_api.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
function IGraph(n::Integer)
g = IGraph(_uninitialized=Val(true))
LibIGraph.empty(g,n,false)
function IGraph(n::Integer, directed::Bool=false)
g = IGraph(_uninitialized=Val(true), directed=directed)
LibIGraph.empty(g, n, directed)
return g
end

Expand All @@ -23,25 +23,140 @@ end

function IGraph(g::Graphs.AbstractSimpleGraph)
n = Graphs.nv(g)
ig = IGraph(n)
for (;src,dst) in Graphs.edges(g)
LibIGraph.add_edge(ig, src-1, dst-1)
directed = Graphs.is_directed(g)
edges_vec = LibIGraph.igraph_int_t[]
sizehint!(edges_vec, 2 * Graphs.ne(g))
for e in Graphs.edges(g)
push!(edges_vec, Graphs.src(e) - 1)
push!(edges_vec, Graphs.dst(e) - 1)
end
vint = IGVectorInt(edges_vec)
ig = IGraph(_uninitialized=Val(true), directed=directed)
LibIGraph.igraph_create(ig.objref, vint.objref, n, directed)
return ig
end


Base.eltype(::IGraph) = LibIGraph.igraph_int_t
Base.zero(::Type{IGraph}) = IGraph(0)
# Graphs.edges # TODO
struct IGraphEdgeIterator
g::IGraph
end
Base.length(it::IGraphEdgeIterator) = Graphs.ne(it.g)
Base.eltype(::Type{IGraphEdgeIterator}) = Graphs.SimpleGraphs.SimpleEdge{Int}
function Base.iterate(it::IGraphEdgeIterator, state=0)
state >= Graphs.ne(it.g) && return nothing
from, to = LibIGraph.edge(it.g, state)
return (Graphs.SimpleGraphs.SimpleEdge(from + 1, to + 1), state + 1)
end
Graphs.edges(g::IGraph) = IGraphEdgeIterator(g)
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
function Graphs.has_edge(g::IGraph, s::Integer, d::Integer)
(s < 1 || s > Graphs.nv(g) || d < 1 || d > Graphs.nv(g)) && return false
eid = Ref{Int}(-1)
LibIGraph.igraph_get_eid(g.objref, eid, s-1, d-1, Graphs.is_directed(g), false)
return eid[] != -1
end
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
function Graphs.inneighbors(g::IGraph, v::Integer)
neis = IGVectorInt()
LibIGraph.neighbors(g, neis, v-1, LibIGraph.IGRAPH_IN, LibIGraph.IGRAPH_LOOPS, true)
return [LibIGraph.vector_int_get(neis, i-1) + 1 for i in 1:LibIGraph.vector_int_size(neis)]
end

Graphs.is_directed(g::IGraph{Directed}) where Directed = Directed
Graphs.is_directed(::Type{<:IGraph{Directed}}) where Directed = Directed

Graphs.ne(g::IGraph) = LibIGraph.ecount(g)
Graphs.nv(g::IGraph) = LibIGraph.vcount(g)
# Graphs.outneighbors # TODO

function Graphs.outneighbors(g::IGraph, v::Integer)
neis = IGVectorInt()
LibIGraph.neighbors(g, neis, v-1, LibIGraph.IGRAPH_OUT, LibIGraph.IGRAPH_LOOPS, true)
return [LibIGraph.vector_int_get(neis, i-1) + 1 for i in 1:LibIGraph.vector_int_size(neis)]
end
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)
Graphs.add_edge!(g::IGraph, e::Graphs.AbstractEdge) = Graphs.add_edge!(g, Graphs.src(e), Graphs.dst(e))
Graphs.add_edge!(g::IGraph, s::Integer, d::Integer) = (LibIGraph.igraph_add_edge(g.objref, s-1, d-1) == 0)

function Graphs.rem_edge!(g::IGraph, s::Integer, d::Integer)
eid = Ref{Int}(-1)
LibIGraph.igraph_get_eid(g.objref, eid, s-1, d-1, Graphs.is_directed(g), false)
eid[] == -1 && return false
es = Ref{LibIGraph.igraph_es_t}()
LibIGraph.igraph_es_1(es, eid[])
return LibIGraph.igraph_delete_edges(g.objref, es[]) == 0
end
Graphs.rem_edge!(g::IGraph, e::Graphs.AbstractEdge) = Graphs.rem_edge!(g, Graphs.src(e), Graphs.dst(e))

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

function Graphs.add_vertices!(g::IGraph, n::Integer)
LibIGraph.igraph_add_vertices(g.objref, n, C_NULL)
return n
end

function Graphs.rem_vertex!(g::IGraph, v::Integer)
(v < 1 || v > Graphs.nv(g)) && return false
vs = Ref{LibIGraph.igraph_vs_t}()
LibIGraph.igraph_vs_1(vs, v-1)
return LibIGraph.igraph_delete_vertices(g.objref, vs[]) == 0
end

function Graphs.rem_vertices!(g::IGraph, vs::AbstractVector)
# Convert Julia 1-based indices to 0-based
vint = IGVectorInt([Int(v-1) for v in vs])
igraph_vs = Ref{LibIGraph.igraph_vs_t}()
LibIGraph.igraph_vs_vector(igraph_vs, vint.objref)
res = LibIGraph.igraph_delete_vertices(g.objref, igraph_vs[])
return res == 0
end

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

function Graphs.all_neighbors(g::IGraph, v::Integer)
neis = IGVectorInt()
LibIGraph.neighbors(g, neis, v-1, LibIGraph.IGRAPH_ALL, LibIGraph.IGRAPH_LOOPS, true)
return [LibIGraph.vector_int_get(neis, i-1) + 1 for i in 1:LibIGraph.vector_int_size(neis)]
end

function Graphs.degree(g::IGraph, v::Integer)
return LibIGraph.degree(g, v-1, LibIGraph.IGRAPH_ALL, LibIGraph.IGRAPH_LOOPS)[1]
end

function Graphs.indegree(g::IGraph, v::Integer)
return LibIGraph.degree(g, v-1, LibIGraph.IGRAPH_IN, LibIGraph.IGRAPH_LOOPS)[1]
end

function Graphs.outdegree(g::IGraph, v::Integer)
return LibIGraph.degree(g, v-1, LibIGraph.IGRAPH_OUT, LibIGraph.IGRAPH_LOOPS)[1]
end

function Graphs.has_self_loops(g::IGraph)
res = Ref{LibIGraph.igraph_bool_t}()
LibIGraph.igraph_has_loop(g.objref, res)
return Bool(res[])
end

function Graphs.num_self_loops(g::IGraph)
count = 0
# This is slow, but correct for now. igraph might have a better way but it's not obvious.
for e in Graphs.edges(g)
if Graphs.src(e) == Graphs.dst(e)
count += 1
end
end
return count
end

function Base.copy(g::IGraph{Directed}) where Directed
# igraph_copy is not wrapped in LibIGraph.jl in a high-level way usually,
# but we can use the raw one and wrap it.
new_g = IGraph(_uninitialized=Val(true), directed=Directed)
LibIGraph.igraph_copy(new_g.objref, g.objref)
return new_g
end
Loading
Loading