Skip to content
Merged
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
6 changes: 3 additions & 3 deletions docs/src/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ and edges used internally.
([#178](https://github.com/ITensor/NamedGraphs.jl/pull/178)).
- The "position graph" terminology is replaced with encode and decode
terminology. The overloads for implementing a new `AbstractNamedGraph` are
`encoded_graph(g)` (replaces `position_graph`, with a generic
`EncodedGraphView` fallback so a graph type does not need to store an integer
graph), `encoded_vertex(g, v)` (replaces `vertex_positions`), and
`encoded_graph(g)` (replaces `position_graph`, and can return
`EncodedGraphView(g)` for a type that does not store an integer graph),
`encoded_vertex(g, v)` (replaces `vertex_positions`), and
`decoded_vertex(g, c)` (replaces `ordered_vertices`). Codes are not stable
across mutation. The last two translate a single vertex where the functions
they replace returned a whole mapping, so a type that forwarded all three in a
Expand Down
10 changes: 7 additions & 3 deletions docs/src/dev_interface.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,12 @@ CollapsedDocStrings = true
Subtype [`AbstractNamedGraph`](@ref) and overload the minimal interface below,
the graph on integer vertex codes and the translation between names and codes.
Everything else in the Graphs.jl interface has generic fallbacks in terms of
these. Graph types that do not store an integer graph get a generic
`EncodedGraphView` fallback for `encoded_graph` and only need `encoded_vertex`
and `decoded_vertex`.
these.

A graph type that does not store an integer graph can return
[`EncodedGraphView(g)`](@ref EncodedGraphView) from `encoded_graph`. The view
answers `nv`, `ne`, `has_vertex`, `has_edge`, `edges`, and the neighbor queries
by asking the named graph itself, so such a type defines those directly.

Vertex codes are not stable across mutation: adding or removing vertices may
reassign the codes of other vertices.
Expand All @@ -26,6 +29,7 @@ encoded_vertex
decoded_vertex
encoded_edge
decoded_edge
EncodedGraphView
```

## Graphs.jl interface extensions
Expand Down
4 changes: 2 additions & 2 deletions src/NamedGraphs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ module NamedGraphs
export ⊔, AbstractNamedGraph, NamedDiGraph, NamedEdge, NamedGraph,
add_edge, add_edges, add_edges!, add_vertex, add_vertices, all_edges, boundary_edges,
convert_vertextype, default_root_vertex, directed_graph, disjoint_union,
edge_subgraph, edgeless_graph, empty_graph, forest_cover,
eccentricities, edge_subgraph, edgeless_graph, empty_graph, forest_cover,
forest_cover_edge_sequence, in_incident_edges, incident_edges, is_leaf_vertex,
leaf_vertices, named_binary_tree, named_comb_tree, named_cycle_graph, named_grid,
named_hexagonal_lattice_graph, named_path_digraph, named_path_graph,
Expand All @@ -40,7 +40,7 @@ if VERSION >= v"1.11.0-DEV.469"
# overload, so it is public rather than exported.
eval(
Meta.parse(
"public decoded_edge, decoded_vertex, encoded_edge, encoded_graph, encoded_vertex, to_graph_index"
"public EncodedGraphView, decoded_edge, decoded_vertex, encoded_edge, encoded_graph, encoded_vertex, to_graph_index"
)
)
end
Expand Down
12 changes: 9 additions & 3 deletions src/PartitionedGraphs/PartitionedGraphs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ This module provides data structures and functionalities to work with partitione
including quotient vertices and edges, as well as views of partitioned graphs.
It defines an abstract supertype `AbstractPartitionedGraph` for graphs that have
some notion of a non-trivial partitioning of their vertices. It also provides
a interface of functions that can be overloaded on any subtype of `Graphs.AbstractGraph` to
an interface of functions that can be overloaded on any subtype of `Graphs.AbstractGraph` to
make this subtype behave like a partitioned graph, without itself subtyping `AbstractPartitionedGraph`.

It defines the following concrete types:
Expand All @@ -18,7 +18,9 @@ It defines the following concrete types:
- `PartitionedGraph`: An implementation of a partitioned graph with extra caching
not provided by `PartitionedView`.
- `QuotientView`: A view of the quotient graph derived from a partitioned graph.
It provides the following functions:

It provides the following functions:

- `partitionedgraph`: Partitions an `AbstractGraph`.
- `departition`: Removes a single layer of partitioning from a partitioned graph.
- `unpartition`: Recursively removes all layers of partitioning from a partitioned graph.
Expand Down Expand Up @@ -81,7 +83,11 @@ is not supported for partitioned graphs as it is ambiguous which quotient vertex
should belong to. To add a vertex to a partitioned graph, one should define the method:

```julia
Graphs.add_subquotientvertex!(g::MyGraphType, quotientvertex::QuotientVertex, vertex)
PartitionedGraphs.add_subquotientvertex!(
g::MyGraphType,
quotientvertex::QuotientVertex,
vertex
)
```

Doing so enables the syntax:
Expand Down
4 changes: 2 additions & 2 deletions src/PartitionedGraphs/quotientedge.jl
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ to_quotient_index(edge::AbstractEdge) = QuotientEdge(edge)
"""
quotientedge(g::AbstractGraph{V}, edge) -> QuotientEdge{V}

Return the the quotient edge corresponding to `edge` of the graph `g`. Note,
Return the quotient edge corresponding to `edge` of the graph `g`. Note,
the returned quotient edge may be a self-loop.

See also: `quotientedges`, `quotienttvertex`.
See also: `quotientedges`, `quotientvertex`.
"""
quotientedge(g::AbstractGraph, edge::Pair) = quotientedge(g, edgetype(g)(edge))
function quotientedge(g::AbstractGraph, edge::AbstractEdge)
Expand Down
4 changes: 2 additions & 2 deletions src/PartitionedGraphs/quotientvertex.jl
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ Base.getindex(qvs::QuotientVertices, i) = QuotientVertices(qvs.vertices[i])
"""
quotientvertices(g::AbstractGraph, vs = vertices(g))

Return an iterator over unique quotient vertices corresponding to the set vertices `vs`
of the graph `pg`.
Return an iterator over unique quotient vertices corresponding to the vertices `vs`
of the graph `g`.
"""
quotientvertices(g) = QuotientVertices(g)
function quotientvertices(g::AbstractGraph, vs)
Expand Down
38 changes: 22 additions & 16 deletions src/abstractnamedgraph.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ using SimpleTraits: SimpleTraits, @traitfn, Not

Abstract type for graphs whose vertices are names of type `V` rather than
contiguous integers. Subtypes implement the Graphs.jl interface in terms of a
graph on integer vertex codes through the minimal interface
[`encoded_graph`](@ref), [`encoded_vertex`](@ref), and [`decoded_vertex`](@ref).
graph on integer vertex codes through [`encoded_graph`](@ref),
[`encoded_vertex`](@ref), and [`decoded_vertex`](@ref). The developer interface
page of the documentation covers what a subtype has to define.
"""
abstract type AbstractNamedGraph{V} <: AbstractGraph{V} end

Expand Down Expand Up @@ -76,18 +77,6 @@ julia> [decoded_vertex(g, c) for c in 1:nv(g)]
decoded_vertex(graph::AbstractNamedGraph, code::Integer) = not_implemented()
decoded_vertex(graph::AbstractSimpleGraph, code::Integer) = code

Graphs.rem_vertex!(graph::AbstractNamedGraph, vertex) = not_implemented()
Graphs.add_vertex!(graph::AbstractNamedGraph, vertex) = not_implemented()

function rename_vertices(f::Function, graph::AbstractNamedGraph)
new_vertices = map(c -> f(decoded_vertex(graph, c)), vertices(encoded_graph(graph)))
return namedgraph(copy(encoded_graph(graph)), new_vertices)
end

#
# Derived interface (overload for performance)
#

"""
encoded_graph(graph::AbstractNamedGraph) -> AbstractGraph{Int}

Expand All @@ -99,6 +88,11 @@ the edge `encoded_vertex(graph, u) => encoded_vertex(graph, v)` if and only if
May be a stored field or a view of `graph`; mutate the graph only through
`graph`.

A type that computes its topology directly rather than storing an integer graph
can return [`EncodedGraphView(graph)`](@ref EncodedGraphView), and must then define
`nv`, `ne`, `has_vertex`, `has_edge`, `edges`, and the neighbor hooks itself,
since the view answers those by asking `graph`.

# Examples

```jldoctest
Expand Down Expand Up @@ -126,9 +120,21 @@ julia> has_edge(cg, 1, 2)
true
```
"""
encoded_graph(graph::AbstractNamedGraph) = EncodedGraphView(graph)
encoded_graph(graph::AbstractNamedGraph) = not_implemented()
encoded_graph(graph::AbstractSimpleGraph) = graph

Graphs.rem_vertex!(graph::AbstractNamedGraph, vertex) = not_implemented()
Graphs.add_vertex!(graph::AbstractNamedGraph, vertex) = not_implemented()

function rename_vertices(f::Function, graph::AbstractNamedGraph)
new_vertices = map(c -> f(decoded_vertex(graph, c)), vertices(encoded_graph(graph)))
return namedgraph(copy(encoded_graph(graph)), new_vertices)
end

#
# Derived interface (overload for performance)
#

"""
vertices(graph::AbstractNamedGraph) -> Dictionaries.AbstractIndices

Expand Down Expand Up @@ -345,7 +351,7 @@ end
The neighbors of `vertex` in `graph`, as vertex names. On a directed graph these
are the out-neighbors, following the Graphs.jl convention. `inneighbors`,
`outneighbors`, and `all_neighbors` select the other directions and behave the
same way in every other respect, including the caveat below.
same way in every other respect.

# Examples

Expand Down
32 changes: 23 additions & 9 deletions src/encodedgraphview.jl
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
using Graphs: Graphs, Edge, add_edge!, add_vertex!, edges, has_edge, has_vertex,
inneighbors, is_directed, ne, nv, outneighbors, rem_edge!, rem_vertex!, vertices
using Graphs: Graphs, AbstractGraph, Edge, SimpleDiGraph, SimpleGraph, add_edge!,
add_vertex!, blockdiag, edges, has_edge, has_vertex, inneighbors, is_directed, ne, nv,
outneighbors, rem_edge!, rem_vertex!, vertices

# Reinterprets an AbstractNamedGraph as an AbstractGraph{Int} whose vertices
# are the codes `1:nv(graph)` of the named graph's vertices.
# Default output of `encoded_graph(graph::AbstractNamedGraph)` for graph types
# that are implemented directly, as opposed to as a wrapper around a stored
# integer graph, such as NamedGridGraph.
# Assumes `encoded_vertex(g.graph, v)` and `decoded_vertex(g.graph, c)` are
# implemented for the wrapped graph.
"""
EncodedGraphView(graph::AbstractNamedGraph)

An `AbstractGraph{Int}` presenting `graph` on its vertex codes `1:nv(graph)`, for
graph types that compute their topology directly rather than storing an integer
graph. Such a type can return `EncodedGraphView(graph)` from [`encoded_graph`](@ref)
and must then define `nv`, `ne`, `has_vertex`, `has_edge`, `edges`, and the neighbor
hooks itself, since the view answers every query by asking `graph`.
"""
struct EncodedGraphView{G <: AbstractGraph} <: AbstractGraph{Int}
graph::G
end
Expand All @@ -22,6 +25,7 @@ end
function Graphs.rem_vertex!(g::EncodedGraphView, v::Int)
return rem_vertex!(g.graph, decoded_vertex(g.graph, v))
end
Graphs.has_edge(g::EncodedGraphView, s::Int, d::Int) = has_edge(g, Edge(s, d))
function Graphs.has_edge(g::EncodedGraphView, e::Edge)
return has_edge(g.graph, decoded_edge(g.graph, e))
end
Expand All @@ -32,6 +36,16 @@ function Graphs.rem_edge!(g::EncodedGraphView, e::Edge)
return rem_edge!(g.graph, decoded_edge(g.graph, e))
end
Graphs.edgetype(g::EncodedGraphView) = Edge{Int}

# A view has no storage of its own, so its copy is a graph that does, as
# `copy(::SubArray)` gives an `Array`.
Base.copy(g::EncodedGraphView) = (is_directed(g) ? SimpleDiGraph : SimpleGraph)(g)

# Graphs.jl builds the result of `blockdiag` with `T(n)` for the operand type `T`,
# which a view cannot provide, so a view operand is materialized first.
Graphs.blockdiag(g::EncodedGraphView, h::EncodedGraphView) = blockdiag(copy(g), copy(h))
Graphs.blockdiag(g::EncodedGraphView, h::AbstractGraph) = blockdiag(copy(g), h)
Graphs.blockdiag(g::AbstractGraph, h::EncodedGraphView) = blockdiag(g, copy(h))
function Graphs.edges(g::EncodedGraphView)
return Iterators.map(edges(g.graph)) do e
return encoded_edge(g.graph, e)
Expand Down
30 changes: 30 additions & 0 deletions src/graphsextensions/abstractgraph.jl
Original file line number Diff line number Diff line change
Expand Up @@ -608,6 +608,36 @@ function mincut_partitions(graph::AbstractGraph, distmx = weights(graph))
return parts[1], parts[2]
end

"""
eccentricities(graph::AbstractGraph, vs = vertices(graph), distmx = weights(graph))

The eccentricity of each vertex in `vs`, that is, the length of the longest
shortest path from it to any other vertex, as `eccentricity(graph, v, distmx)`
gives for one vertex. The output has one entry per element of `vs`, keyed the
same way, so for the default `vertices(graph)` it is a `Dictionary` from vertex
to eccentricity.

# Examples

```jldoctest
julia> using Graphs: path_graph

julia> using NamedGraphs: NamedGraph, eccentricities

julia> g = NamedGraph(path_graph(3), ["a", "b", "c"]);

julia> eccentricities(g)
3-element Dictionaries.Dictionary{String, Int64}:
"a" │ 2
"b" │ 1
"c" │ 2

julia> eccentricities(g, ["a", "c"])
2-element Vector{Int64}:
2
2
```
"""
eccentricities(graph::AbstractGraph) = eccentricities(graph, vertices(graph))

function eccentricities(graph::AbstractGraph, vs, distmx = weights(graph))
Expand Down
2 changes: 1 addition & 1 deletion src/namedgridgraph.jl
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ function NamedGridGraph(grid_size::NTuple{N, Int}, ishypertorus::Bool = false) w
return NamedGridGraph{N, ishypertorus}(grid_size)
end
# Minimal interface functions
# `encoded_graph` uses the generic `EncodedGraphView` fallback.
encoded_graph(g::NamedGridGraph) = EncodedGraphView(g)
function encoded_vertex(g::NamedGridGraph, vertex)
# Membership is just a bounds check here, unlike the dictionary lookup in
# `NamedGraph`, so checking it up front costs nothing worth avoiding.
Expand Down
13 changes: 13 additions & 0 deletions test/test_abstractnamedgraph.jl
Original file line number Diff line number Diff line change
Expand Up @@ -400,3 +400,16 @@ end
@test Graphs.rem_vertices!(h, vertices(h)) == 4
@test nv(h) == 0
end

# Defines only the vertex translation, to check that the rest of the required
# interface fails loudly rather than recursing through `EncodedGraphView`.
struct IncompleteNamedGraph <: AbstractNamedGraph{String} end
NamedGraphs.encoded_vertex(::IncompleteNamedGraph, vertex) = 1
NamedGraphs.decoded_vertex(::IncompleteNamedGraph, code::Integer) = "a"
Graphs.is_directed(::Type{IncompleteNamedGraph}) = false

@testset "AbstractNamedGraph incomplete subtype" begin
g = IncompleteNamedGraph()
@test_throws ErrorException nv(g)
@test_throws ErrorException collect(edges(g))
end
36 changes: 33 additions & 3 deletions test/test_encodedgraphview.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using Graphs: Graphs, Edge, edges, edgetype, has_edge, has_vertex, inneighbors, is_directed,
ne, neighbors, nv, outneighbors, vertices
using NamedGraphs: EncodedGraphView, NamedEdge, NamedGridGraph, vertextype
using Graphs: Graphs, Edge, SimpleGraph, adjacency_matrix, blockdiag, edges, edgetype,
has_edge, has_vertex, inneighbors, is_directed, ne, neighbors, nv, outneighbors,
path_graph, vertices
using NamedGraphs: EncodedGraphView, NamedEdge, NamedGraph, NamedGridGraph, encoded_graph,
rename_vertices, vertextype, ⊔
using Test: @test, @test_broken, @testset

@testset "EncodedGraphView" begin
Expand All @@ -16,3 +18,31 @@ using Test: @test, @test_broken, @testset
@test length(edges(pg)) == ne(g)
@test all(e -> has_edge(pg, e), edges(pg))
end

@testset "EncodedGraphView as a Graphs.jl graph" begin
g = NamedGridGraph((2, 3))
@test encoded_graph(g) isa EncodedGraphView
pg = encoded_graph(g)
# Graphs.jl algorithms call the `(g, s, d)` form of `has_edge`.
@test has_edge(pg, 1, 2) == has_edge(pg, Edge(1, 2)) == true
@test has_edge(pg, 1, 6) == has_edge(pg, Edge(1, 6)) == false
A = adjacency_matrix(g)
@test size(A) == (6, 6)
@test count(!iszero, A) == 2 * ne(g)
end

@testset "EncodedGraphView materializes where a stored graph is needed" begin
g = NamedGridGraph((2, 3))
pg = encoded_graph(g)
c = copy(pg)
@test c isa SimpleGraph
@test issetequal(collect(edges(c)), collect(edges(pg)))
r = rename_vertices(string, g)
@test r isa NamedGraph{String}
@test (nv(r), ne(r)) == (nv(g), ne(g))
h = NamedGraph(path_graph(2), ["a", "b"])
@test nv(blockdiag(g, h)) == nv(blockdiag(h, g)) == nv(g) + 2
@test ne(blockdiag(g, h)) == ne(g) + 1
u = g ⊔ g
@test (nv(u), ne(u)) == (2 * nv(g), 2 * ne(g))
end
2 changes: 2 additions & 0 deletions test/test_exports.jl
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ using Test: @test, @testset
:default_root_vertex,
:directed_graph,
:disjoint_union,
:eccentricities,
:edge_subgraph,
:edgeless_graph,
:empty_graph,
Expand Down Expand Up @@ -56,6 +57,7 @@ using Test: @test, @testset
# `names` includes `public` names as well as exported ones.
public_names = if VERSION >= v"1.11.0-DEV.469"
[
:EncodedGraphView,
:PartitionedGraphs,
:decoded_edge,
:decoded_vertex,
Expand Down