Skip to content
Draft
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
1 change: 1 addition & 0 deletions docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ pages_files = [
"Algorithms API" => [
"algorithms/biconnectivity.md",
"algorithms/centrality.md",
"algorithms/chordality.md",
"algorithms/community.md",
"algorithms/connectivity.md",
"algorithms/cut.md",
Expand Down
17 changes: 17 additions & 0 deletions docs/src/algorithms/chordality.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Degeneracy

*Graphs.jl* provides functionality for checking whether a graph is [chordal](https://en.wikipedia.org/wiki/Chordal_graph).

## Index

```@index
Pages = ["chordality.md"]
```

## Full docs

```@autodocs
Modules = [Graphs]
Pages = ["chordality.jl"]

```
4 changes: 4 additions & 0 deletions src/Graphs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,9 @@ export
# coloring
greedy_color,

# chordality
is_chordal,

# connectivity
connected_components,
connected_components!,
Expand Down Expand Up @@ -520,6 +523,7 @@ include("iterators/bfs.jl")
include("iterators/dfs.jl")
include("traversals/eulerian.jl")
include("traversals/all_simple_paths.jl")
include("chordality.jl")
include("connectivity.jl")
include("distance.jl")
include("editdist.jl")
Expand Down
103 changes: 103 additions & 0 deletions src/chordality.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
"""
is_chordal(g)

Check whether a graph is chordal.

A graph is said to be *chordal* if every cycle of length `≥ 4` has a chord
(i.e., an edge between two vertices not adjacent in the cycle).

### Performance
This algorithm is linear in the number of vertices and edges of the graph (i.e.,
it runs in `O(nv(g) + ne(g))` time).

### Implementation Notes
`g` is chordal if and only if it admits a perfect elimination ordering—that is,
an ordering of the vertices of `g` such that for every vertex `v`, the set of
all neighbors of `v` that come later in the ordering forms a complete graph.
This is precisely the condition checked by the maximum cardinality search
algorithm [1], implemented herein.

We take heavy inspiration here from the existing Python implementation in [2].

Not implemented for directed graphs, graphs with self-loops, or graphs with
parallel edges.

### References
[1] Tarjan, Robert E. and Mihalis Yannakakis. "Simple Linear-Time Algorithms to
Test Chordality of Graphs, Test Acyclicity of Hypergraphs, and Selectively
Reduce Acyclic Hypergraphs." *SIAM Journal on Computing* 13, no. 3 (1984):
566–79. https://doi.org/10.1137/0213035.
[2] NetworkX Developers. "is_chordal." NetworkX 3.5 documentation. NetworkX,
May 29, 2025. Accessed June 2, 2025.
https://networkx.org/documentation/stable/reference/algorithms/generated/networkx.algorithms.chordal.is_chordal.html.

### Examples
```jldoctest
julia> using Graphs

julia> is_chordal(cycle_graph(3))
true

julia> is_chordal(cycle_graph(4))
false

julia> g = cycle_graph(4); add_edge!(g, 1, 3);

julia> is_chordal(g)
true

```
"""
function is_chordal end

@traitfn function is_chordal(g::AG::(!IsDirected)) where {AG<:AbstractGraph}
# The `AbstractGraph` interface does not support parallel edges, so no need to check
if has_self_loops(g)
throw(ArgumentError("Graph must not have self-loops"))
end

# Every graph of order `< 4` has no cycles of length `≥ 4` and thus is trivially chordal
if nv(g) < 4
return true
end

unnumbered = Set(vertices(g))
start_vertex = pop!(unnumbered) # The search can start from any arbitrary vertex
numbered = Set(start_vertex)

#= Searching by maximum cardinality ensures that in any possible perfect elimination
ordering of `g`, `subsequent_neighbors` is precisely the set of neighbors of `v` that
come later in the ordering. Therefore, if the subgraph induced by `subsequent_neighbors`
in any iteration is not complete, `g` cannot be chordal. =#
while !isempty(unnumbered)
# `v` is the vertex in `unnumbered` with the most neighbors in `numbered`
v = _max_cardinality_vertex(g, unnumbered, numbered)
delete!(unnumbered, v)
push!(numbered, v)
subsequent_neighbors = filter(in(numbered), collect(neighbors(g, v)))

if !_induces_clique(subsequent_neighbors, g)
return false
end
end

#= A perfect elimination ordering is an "if and only if" condition for chordality, so if
every `subsequent_neighbors` set induced a complete subgraph, `g` must be chordal. =#
return true
end

function _max_cardinality_vertex(
g::AbstractGraph{T}, unnumbered::Set{T}, numbered::Set{T}
) where {T}
return argmax(v -> count(in(numbered), neighbors(g, v)), unnumbered)
end

function _induces_clique(vertex_subset::Vector{T}, g::AbstractGraph{T}) where {T}
for (i, u) in enumerate(vertex_subset), v in Iterators.drop(vertex_subset, i)
if !has_edge(g, u, v)
return false
end
end

return true
end
1 change: 1 addition & 0 deletions test/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
DelimitedFiles = "8bb1440f-4735-579b-a4ab-409b98df4dab"
Distributed = "8ba89e20-285c-5b6f-9357-94700520ee1b"
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
IGraphs = "647e90d3-2106-487c-adb4-c91fc07b96ea"
Inflate = "d25df0c9-e2be-5dd7-82c8-3ad0b3e990b9"
JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
Expand Down
3 changes: 3 additions & 0 deletions test/chordality.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@testset "Chordality" begin
# TODO: Add tests
end
2 changes: 2 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ using Random
using Logging: NullLogger, with_logger
using Statistics: mean, std
using StableRNGs
using IGraphs
using Pkg
using Unitful

Expand Down Expand Up @@ -93,6 +94,7 @@ tests = [
"cycles/limited_length",
"cycles/incremental",
"edit_distance",
"chordality",
"connectivity",
"persistence/persistence",
"shortestpaths/utils",
Expand Down
Loading