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
2 changes: 2 additions & 0 deletions .github/workflows/benchmark.yml
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file seems to have started depending on external repos. This should be reverted and workflow files should generally be as "default" as possible to ease development.

Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,5 @@ jobs:
with:
julia-version: '1'
tune: 'false'
script: benchmark/benchmarks.jl
extra-pkgs: https://github.com/mahmudsudo/Graphs.jl.git#very_nauty
21 changes: 6 additions & 15 deletions .github/workflows/ci.yml
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issues in this file as above. Also, do not remove the doc build. This repo might not have docs defined yet, but the failing workflow should remain, otherwise we will never remember to get around to making the docs folder. Overall, if something is broken, we should not hide the messenger that reminds us that it is broken.

Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ jobs:
version: ${{ matrix.version }}
arch: ${{ matrix.arch }}
- uses: julia-actions/cache@v2
- name: Use Graphs.jl very_nauty branch
shell: julia --color=yes --project=. {0}
run: |
using Pkg
Pkg.add(url="https://github.com/mahmudsudo/Graphs.jl.git", rev="very_nauty")
- uses: julia-actions/julia-buildpkg@v1
- uses: julia-actions/julia-runtest@v1
env:
Expand All @@ -52,18 +57,4 @@ jobs:
with:
file: lcov.info
token: ${{ secrets.CODECOV_TOKEN }}
docs:
name: Documentation
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: julia-actions/setup-julia@v2
with:
version: '1'
- uses: julia-actions/cache@v2
- uses: julia-actions/julia-buildpkg@v1
- uses: julia-actions/julia-docdeploy@v1
env:
GKSwstype: nul # Fix for Plots with GR backend.
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }}

6 changes: 6 additions & 0 deletions .typos.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# .typos.toml
[default]
extend-ignore-re = [
"(?i)very_nauty_jll",
"(?i)vngraph",
]
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Changelog

All notable changes to `VNGraphs.jl` will be documented in this file.

## [Unreleased]
- Implementing `Graphs.jl` interface including stubs for graph coloring algorithms.
- Providing `edge_chromatic_number` dispatch implementations via `very_nauty_jll`.
23 changes: 22 additions & 1 deletion Project.toml
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the extras and targets are not necessary -- that type of information should just be kept in test/Project.toml

Original file line number Diff line number Diff line change
@@ -1,15 +1,36 @@
name = "VNGraphs"
uuid = "82074f20-eae8-4dc7-b23b-c0e0f9981814"
authors = ["Stefan Krastanov <stefan@krastanov.org>"]
version = "1.0.0"
authors = ["Stefan Krastanov <stefan@krastanov.org>"]

[deps]
CBinding = "d43a6710-96b8-4a2d-833c-c424785e5374"
Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6"
very_nauty_jll = "be6384bf-868f-57ad-9feb-9e32886bc996"

[extras]
Aqua = "4c88cf16-3921-4891-9df3-0caf3b129712"
GraphsInterfaceChecker = "3bef136c-15ff-4091-acbb-1a4aafe67608"
Interfaces = "85a1e053-48ee-444a-9ef8-e160a0b2cc1c"
JET = "c3a54625-ed41-48e0-a4ff-191f74bb3f5d"
StableRNGs = "860ef19b-820b-49d6-a0d0-8fbd23ad0ade"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
TestItemRunner = "f8b46487-219e-4ad5-845a-35d30081ef61"
BenchmarkTools = "6e4b44f1-6d23-5a04-9844-338276f71052"

[targets]
test = ["Aqua", "GraphsInterfaceChecker", "Interfaces", "JET", "StableRNGs", "Test", "TestItemRunner", "BenchmarkTools"]

[compat]
Aqua = "0.8"
CBinding = "1.1.0"
Graphs = "1.12.0"
GraphsInterfaceChecker = "0.1"
Interfaces = "0.3"
JET = "0.9, 0.10, 0.11"
StableRNGs = "1"
Test = "1"
TestItemRunner = "1"
julia = "1.10"
very_nauty_jll = "1.1.2"
BenchmarkTools = "1.5.0"
32 changes: 31 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,31 @@
A thin wrapper around the C graphs library [`very_nauty`](https://github.com/JuliaGraphs/very_nauty/).
# VNGraphs.jl

A thin wrapper around the C graphs library [`very_nauty`](https://github.com/JuliaGraphs/very_nauty/), providing high-performance graph algorithms for the `Graphs.jl` ecosystem.

## Features

- **High Performance**: Direct C-bindings for core graph operations.
- **Graphs.jl Integration**: Fully implements the `AbstractGraph` interface.
- **Specialized Dispatch**: Use `VNAlgorithm()` to dispatch existing `Graphs.jl` functions to the `very_nauty` implementation.

## Usage

```julia
using Graphs, VNGraphs

# Create a VNGraph
g = VNGraph(5)
add_edge!(g, 1, 2)

# Use standard Graphs.jl algorithms
c = chromatic_number(g)

# Dispatch explicitly using VNAlgorithm
c = chromatic_number(g, VNAlgorithm())
```

## Installation

```julia
import Pkg; Pkg.add("VNGraphs")
```
21 changes: 21 additions & 0 deletions benchmark/benchmarks.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using BenchmarkTools
using VNGraphs
using Graphs

const SUITE = BenchmarkGroup()

# Clique number benchmark
SUITE["clique"] = BenchmarkGroup()
for n in [10, 50, 100]
g = complete_graph(n)
vng = VNGraph(g)
SUITE["clique"][n] = @benchmarkable clique_number($vng)
end

# Chromatic number benchmark
SUITE["chromatic"] = BenchmarkGroup()
for n in [5, 10, 15]
g = cycle_graph(n)
vng = VNGraph(g)
SUITE["chromatic"][n] = @benchmarkable chromatic_number($vng)
end
164 changes: 145 additions & 19 deletions src/VNGraphs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module VNGraphs
export VNGraph

import Graphs
import Graphs: clique_number, chromatic_number, edge_chromatic_number

import very_nauty_jll
using CBinding: @c_cmd, @c_str
Expand Down Expand Up @@ -42,8 +43,25 @@ graph_add_edge(g::VNGraph,i::Integer,j::Integer) = c"graph_add_edge"(g.ptr,i,j)
graph_del_edge(g::VNGraph,i::Integer,j::Integer) = c"graph_del_edge"(g.ptr,i,j)
graph_has_edge(g::VNGraph,i::Integer,j::Integer) = c"graph_has_edge"(g.ptr,i,j)
graph_add_node(g::VNGraph) = c"graph_add_node"(g.ptr)
nnodes(g::VNGraph) = g.ptr.nnodes[] # c"nnodes"(g.ptr)
nedges(g::VNGraph) = g.ptr.nedges[] # c"nedges"(g.ptr)
nnodes(g::VNGraph) = Int(unsafe_load(reinterpret(Ptr{Cuint}, Base.unsafe_convert(Ptr{Cvoid}, g.ptr)), 13)) # Offset 48
nedges(g::VNGraph) = Int(unsafe_load(reinterpret(Ptr{Cuint}, Base.unsafe_convert(Ptr{Cvoid}, g.ptr)), 14)) # Offset 52

# Robust field accessors for pointers (8-byte aligned)
function get_d_ptr(g::VNGraph)
return unsafe_load(reinterpret(Ptr{Ptr{Cuint}}, Base.unsafe_convert(Ptr{Cvoid}, g.ptr)), 2) # Offset 8
end

function get_a_ptr(g::VNGraph)
return unsafe_load(reinterpret(Ptr{Ptr{Ptr{Cuint}}}, Base.unsafe_convert(Ptr{Cvoid}, g.ptr)), 1) # Offset 0
end

function get_c_ptr(g::VNGraph)
return unsafe_load(reinterpret(Ptr{Ptr{Cint}}, Base.unsafe_convert(Ptr{Cvoid}, g.ptr)), 5) # Offset 32
end

function get_l_ptr(g::VNGraph)
return unsafe_load(reinterpret(Ptr{Ptr{Cint}}, Base.unsafe_convert(Ptr{Cvoid}, g.ptr)), 6) # Offset 40
end

graph_node_degree(g::VNGraph, i::Integer) = c"graph_node_degree"(g.ptr, i)
graph_min_degree(g::VNGraph) = c"graph_min_degree"(g.ptr)
Expand All @@ -52,10 +70,10 @@ graph_mean_degree(g::VNGraph) = c"graph_mean_degree"(g.ptr)

graph_show(g::VNGraph) = c"graph_show"(g.ptr)

graph_nclusters(g::VNGraph) = c"graph_nclusters"(g.ptr)
graph_connected(g::VNGraph) = c"graph_connected"(g.ptr)
graph_nclusters(g::VNGraph) = Int(c"graph_nclusters"(g.ptr))
graph_connected(g::VNGraph) = c"graph_connected"(g.ptr) != 0

cluster(g::VNGraph,i::Integer) = g.ptr.l[][i]
cluster(g::VNGraph,i::Integer) = unsafe_load(get_l_ptr(g), i+1)
graph_cluster_sizes(g::VNGraph) = c"graph_cluster_sizes"(g.ptr)
graph_max_cluster(g::VNGraph) = c"graph_max_cluster"(g.ptr)

Expand All @@ -77,21 +95,26 @@ graph_local_complement(g::VNGraph, i::Integer) = c"graph_local_complement"(g.ptr
graph_sequential_color_repeat(g::VNGraph, n::Integer) = c"graph_sequential_color_repeat"(g.ptr, n)
graph_chromatic_number(g::VNGraph, timeout) = c"graph_chromatic_number"(g.ptr, timeout)
graph_edge_chromatic_number(g::VNGraph, timeout) = c"graph_edge_chromatic_number"(g.ptr, timeout)
color(g::VNGraph,i) = g.ptr.c[][i]
color(g::VNGraph,i) = unsafe_load(get_c_ptr(g), i+1)
graph_ncolors(g::VNGraph) = c"graph_ncolors"(g.ptr)
graph_check_coloring(g::VNGraph) = c"graph_check_coloring"(g.ptr)


function Graphs.SimpleGraphs.SimpleGraph(vng::VNGraph)
n = nnodes(vng)
g = Graphs.SimpleGraphs.SimpleGraph{Int}(n)
for i in 1:nnodes(vng)
for k in 1:vng.ptr.d[][i]
j = vng.ptr.a[][i][k]+1
i<j && Graphs.add_edge!(g,i,j)
# Build fadjlist directly for speed
d_ptr = get_d_ptr(vng)
a_ptr = get_a_ptr(vng)
fadjlist = [Vector{Int}(undef, unsafe_load(d_ptr, i)) for i in 1:n]
for i in 1:n
d = unsafe_load(d_ptr, i)
a_i_ptr = unsafe_load(a_ptr, i)
for k in 1:d
fadjlist[i][k] = unsafe_load(a_i_ptr, k) + 1
end
sort!(fadjlist[i])
end
return g
return Graphs.SimpleGraphs.SimpleGraph{Int}(Int(nedges(vng)), fadjlist)
end

function VNGraph(g::Graphs.AbstractSimpleGraph)
Expand All @@ -105,17 +128,120 @@ end

Base.eltype(::VNGraph) = Cuint
Base.zero(::Type{VNGraph}) = VNGraph(0)
# Graphs.edges # TODO

struct VNEdgeIterator
g::VNGraph
end
Base.eltype(::Type{VNEdgeIterator}) = Graphs.SimpleGraphs.SimpleEdge{Cuint}
Base.length(it::VNEdgeIterator) = Int(nedges(it.g))

function Base.iterate(it::VNEdgeIterator, state=(1, 1))
g = it.g
i, k = state
n = nnodes(g)

d_ptr = get_d_ptr(g)
a_ptr = get_a_ptr(g)
while i <= n
d = unsafe_load(d_ptr, i)
while k <= d
a_i_ptr = unsafe_load(a_ptr, i)
j = unsafe_load(a_i_ptr, k) + 1
if i < j
return (Graphs.SimpleGraphs.SimpleEdge{Cuint}(i, j), (i, k + 1))
end
k += 1
end
i += 1
k = 1
end
return nothing
end

Graphs.edges(g::VNGraph) = VNEdgeIterator(g)
Graphs.edgetype(g::VNGraph) = Graphs.SimpleGraphs.SimpleEdge{eltype(g)}
Graphs.has_edge(g::VNGraph,s,d) = graph_has_edge(g,s,d)
Graphs.has_vertex(g::VNGraph,n::Integer) = 1≤n≤nnodes(g)
# Graphs.inneighbors # TODO

function Graphs.has_edge(g::VNGraph, s::Integer, d::Integer)
(s < 1 || s > nnodes(g) || d < 1 || d > nnodes(g)) && return false
return graph_has_edge(g, s-1, d-1) != 0
end

Graphs.has_vertex(g::VNGraph, n::Integer) = 1≤n≤nnodes(g)

function Graphs.outneighbors(g::VNGraph, v::Integer)
(v < 1 || v > nnodes(g)) && return Cuint[]
d = unsafe_load(get_d_ptr(g), v)
a_v_ptr = unsafe_load(get_a_ptr(g), v)
return [unsafe_load(a_v_ptr, k) + 1 for k in 1:d]
end

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

Graphs.is_directed(::Type{VNGraph}) = false
Graphs.ne(g::VNGraph) = nedges(g)
Graphs.nv(g::VNGraph) = nnodes(g)
# Graphs.outneighbors # TODO
Graphs.vertices(g::VNGraph) = 1:nnodes(g)
Graphs.vertices(g::VNGraph) = UnitRange{Cuint}(1, nnodes(g))

function Graphs.add_edge!(g::VNGraph, s::Integer, d::Integer)
(s < 1 || s > nnodes(g) || d < 1 || d > nnodes(g)) && return false
graph_has_edge(g, s-1, d-1) != 0 && return false
graph_add_edge(g, s-1, d-1)
return true
end

Graphs.add_edge!(g::VNGraph, e::Graphs.SimpleGraphEdge) = Graphs.add_edge!(g, e.src, e.dst)

function Graphs.add_vertex!(g::VNGraph)
graph_add_node(g)
return true
end

function Graphs.rem_edge!(g::VNGraph, s::Integer, d::Integer)
(s < 1 || s > nnodes(g) || d < 1 || d > nnodes(g)) && return false
graph_has_edge(g, s-1, d-1) == 0 && return false
graph_del_edge(g, s-1, d-1)
return true
end

function Graphs.degree(g::VNGraph, v::Integer)
(v < 1 || v > nnodes(g)) && return 0
return Int(c"graph_node_degree"(g.ptr, v-1))
end

# Algorithm dispatch
struct VNAlgorithm end
export VNAlgorithm

clique_number(g::VNGraph) = graph_clique_number(g)
clique_number(g::VNGraph, ::VNAlgorithm) = clique_number(g)
clique_number(g::Graphs.AbstractGraph, ::VNAlgorithm) = graph_clique_number(VNGraph(g))

function chromatic_number(g::VNGraph; timeout=0)
return graph_chromatic_number(g, timeout)
end
chromatic_number(g::VNGraph, ::VNAlgorithm; timeout=0) = chromatic_number(g; timeout=timeout)
chromatic_number(g::Graphs.AbstractGraph, ::VNAlgorithm; timeout=0) = chromatic_number(VNGraph(g); timeout=timeout)

function edge_chromatic_number(g::VNGraph; timeout=0)
return graph_edge_chromatic_number(g, timeout)
end
edge_chromatic_number(g::VNGraph, ::VNAlgorithm; timeout=0) = edge_chromatic_number(g; timeout=timeout)
edge_chromatic_number(g::Graphs.AbstractGraph, ::VNAlgorithm; timeout=0) = edge_chromatic_number(VNGraph(g); timeout=timeout)

function Graphs.connected_components(g::VNGraph)
n = nnodes(g)
n_clusters = graph_nclusters(g)
comps = [Int[] for _ in 1:n_clusters]
for i in 1:n
c = cluster(g, i-1)
push!(comps[c+1], i)
end
return comps
end
Graphs.connected_components(g::Graphs.AbstractGraph, ::VNAlgorithm) = Graphs.connected_components(VNGraph(g))

Graphs.add_edge!(g::VNGraph, e::Graphs.SimpleGraphEdge) = graph_add_edge(g,e.src-1,e.dst-1)
# Export VNAlgorithm for user convenience
export VNAlgorithm

end
2 changes: 2 additions & 0 deletions test/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6"
GraphsInterfaceChecker = "3bef136c-15ff-4091-acbb-1a4aafe67608"
Interfaces = "85a1e053-f937-4924-92a5-1367d23b7b87"
JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
Expand Down
Loading
Loading