Skip to content
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