Skip to content

WIP: Add an is_chordal algorithm #434

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
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
4 changes: 4 additions & 0 deletions src/Graphs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,9 @@ export
# coloring
greedy_color,

# chordality
is_chordal,

# connectivity
connected_components,
strongly_connected_components,
Expand Down Expand Up @@ -504,6 +507,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
78 changes: 78 additions & 0 deletions src/chordality.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
"""
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 nodes 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
TODO: Add examples
"""
function is_chordal(g::AbstractSimpleGraph)

Check warning on line 37 in src/chordality.jl

View check run for this annotation

Codecov / codecov/patch

src/chordality.jl#L37

Added line #L37 was not covered by tests
# The possibility of self-loops is already ruled out by the `AbstractSimpleGraph` type
is_directed(g) && throw(ArgumentError("Graph must be undirected"))
has_self_loops(g) && throw(ArgumentError("Graph must not have self-loops"))

Check warning on line 40 in src/chordality.jl

View check run for this annotation

Codecov / codecov/patch

src/chordality.jl#L39-L40

Added lines #L39 - L40 were not covered by tests

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

Check warning on line 43 in src/chordality.jl

View check run for this annotation

Codecov / codecov/patch

src/chordality.jl#L43

Added line #L43 was not covered by tests

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

Check warning on line 47 in src/chordality.jl

View check run for this annotation

Codecov / codecov/patch

src/chordality.jl#L45-L47

Added lines #L45 - L47 were not covered by tests

#= Searching by maximum cardinality ensures that in any possible perfect elimination
ordering of `g`, `purported_clique_nodes` is precisely the set of neighbors of `v` that
come later in the ordering. Hence, if the subgraph induced by `purported_clique_nodes`
in any iteration is not complete, `g` cannot be chordal. =#
while !isempty(unnumbered)

Check warning on line 53 in src/chordality.jl

View check run for this annotation

Codecov / codecov/patch

src/chordality.jl#L53

Added line #L53 was not covered by tests
# `v` is the vertex in `unnumbered` with the most neighbors in `numbered`
v = _max_cardinality_node(g, unnumbered, numbered)
delete!(unnumbered, v)
push!(numbered, v)

Check warning on line 57 in src/chordality.jl

View check run for this annotation

Codecov / codecov/patch

src/chordality.jl#L55-L57

Added lines #L55 - L57 were not covered by tests

# A complete subgraph of a larger graph is called a "clique," hence the naming here
purported_clique_nodes = intersect(neighbors(g, v), numbered)
purported_clique = induced_subgraph(g, purported_clique_nodes)

Check warning on line 61 in src/chordality.jl

View check run for this annotation

Codecov / codecov/patch

src/chordality.jl#L60-L61

Added lines #L60 - L61 were not covered by tests

_is_complete_graph(purported_clique) || return false
end

Check warning on line 64 in src/chordality.jl

View check run for this annotation

Codecov / codecov/patch

src/chordality.jl#L63-L64

Added lines #L63 - L64 were not covered by tests

#= That `g` admits a perfect elimination ordering is an "if and only if" condition for
chordality, so if every `purported_clique` was indeed complete, `g` must be chordal. =#
return true

Check warning on line 68 in src/chordality.jl

View check run for this annotation

Codecov / codecov/patch

src/chordality.jl#L68

Added line #L68 was not covered by tests
end

function _max_cardinality_node(

Check warning on line 71 in src/chordality.jl

View check run for this annotation

Codecov / codecov/patch

src/chordality.jl#L71

Added line #L71 was not covered by tests
g::AbstractSimpleGraph, unnumbered::Set{T}, numbered::Set{T}
) where {T}
cardinality(v::T) = count(in(numbered), neighbors(g, v))
return argmax(cardinality, unnumbered)

Check warning on line 75 in src/chordality.jl

View check run for this annotation

Codecov / codecov/patch

src/chordality.jl#L74-L75

Added lines #L74 - L75 were not covered by tests
end

_is_complete_graph(g::AbstractSimpleGraph) = density(g) == 1

Check warning on line 78 in src/chordality.jl

View check run for this annotation

Codecov / codecov/patch

src/chordality.jl#L78

Added line #L78 was not covered by tests
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
1 change: 1 addition & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ tests = [
"cycles/limited_length",
"cycles/incremental",
"edit_distance",
"chordality",
"connectivity",
"persistence/persistence",
"shortestpaths/utils",
Expand Down
Loading