From d7d924e3953f309d0eb057610beb9fbee34a7ec8 Mon Sep 17 00:00:00 2001 From: Matthew Fishman Date: Tue, 8 Sep 2026 16:22:12 -0400 Subject: [PATCH 1/6] Fix the subtype recipe and the PartitionedGraphs interface docstring The developer page said a type without an integer graph only needs `encoded_vertex` and `decoded_vertex`. A type built that way recurses on every topology call, because `EncodedGraphView` answers those by asking the named graph back. The page now says what such a type defines and describes the `f_namedgraph` hooks. The `PartitionedGraphs` docstring put `add_subquotientvertex!` under `Graphs`, where it does not exist. --- docs/src/dev_interface.md | 20 +++++++++++++++++--- src/PartitionedGraphs/PartitionedGraphs.jl | 12 +++++++++--- src/PartitionedGraphs/quotientedge.jl | 4 ++-- src/PartitionedGraphs/quotientvertex.jl | 4 ++-- src/abstractnamedgraph.jl | 7 ++++--- 5 files changed, 34 insertions(+), 13 deletions(-) diff --git a/docs/src/dev_interface.md b/docs/src/dev_interface.md index 50d531f..acc7f39 100644 --- a/docs/src/dev_interface.md +++ b/docs/src/dev_interface.md @@ -10,9 +10,23 @@ 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 leave `encoded_graph` to +its generic `EncodedGraphView` fallback and define only `encoded_vertex` and +`decoded_vertex` for the translation. The view answers `nv`, `ne`, `has_vertex`, +`has_edge`, `edges`, and the neighbor queries by asking the named graph itself, +so such a type must also define those directly, or every one of them recurses. +`NamedGridGraph` is the model: it computes its topology from the grid size and +uses the view only to present it on integer codes. + +Rather than overloading a Graphs.jl function on your subtype, overload the hook +it forwards to, named `f_namedgraph` for a Graphs.jl function `f` +(`neighbors_namedgraph`, `dijkstra_shortest_paths_namedgraph`, and so on). The +Graphs.jl functions are defined once on `AbstractNamedGraph`, in both the untyped +and `::Integer` vertex forms, and forward to the hook. Overloading the hook keeps +a subtype's methods unambiguous with Graphs.jl's own `::Integer` methods on a +graph whose vertex names are integers, with no disambiguator of its own. Vertex codes are not stable across mutation: adding or removing vertices may reassign the codes of other vertices. diff --git a/src/PartitionedGraphs/PartitionedGraphs.jl b/src/PartitionedGraphs/PartitionedGraphs.jl index 1da09bc..42d85c2 100644 --- a/src/PartitionedGraphs/PartitionedGraphs.jl +++ b/src/PartitionedGraphs/PartitionedGraphs.jl @@ -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: @@ -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. @@ -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: diff --git a/src/PartitionedGraphs/quotientedge.jl b/src/PartitionedGraphs/quotientedge.jl index eaf1ada..4e792a7 100644 --- a/src/PartitionedGraphs/quotientedge.jl +++ b/src/PartitionedGraphs/quotientedge.jl @@ -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) diff --git a/src/PartitionedGraphs/quotientvertex.jl b/src/PartitionedGraphs/quotientvertex.jl index a163935..06cc171 100644 --- a/src/PartitionedGraphs/quotientvertex.jl +++ b/src/PartitionedGraphs/quotientvertex.jl @@ -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) diff --git a/src/abstractnamedgraph.jl b/src/abstractnamedgraph.jl index f32f345..4bde0b6 100644 --- a/src/abstractnamedgraph.jl +++ b/src/abstractnamedgraph.jl @@ -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 @@ -345,7 +346,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 From 2a4576d0ae2ab1f4221b547f6c8c77e340144108 Mon Sep 17 00:00:00 2001 From: Matthew Fishman Date: Tue, 8 Sep 2026 20:42:30 -0400 Subject: [PATCH 2/6] Require subtypes to define encoded_graph Makes `encoded_graph` a required method, with `EncodedGraphView` an explicit opt-in for types that compute their own topology. Its silent default was the view, which asks the named graph back for its topology, so a subtype defining only the vertex translation recursed. The view also gains `has_edge(g, s, d)`, `copy`, and `blockdiag`, which Graphs.jl and the fallbacks call on it. --- docs/src/changelog.md | 6 +++--- docs/src/dev_interface.md | 10 ++++----- src/NamedGraphs.jl | 2 +- src/abstractnamedgraph.jl | 31 ++++++++++++++++------------ src/encodedgraphview.jl | 33 +++++++++++++++++++++--------- src/namedgridgraph.jl | 2 +- test/test_abstractnamedgraph.jl | 13 ++++++++++++ test/test_encodedgraphview.jl | 36 ++++++++++++++++++++++++++++++--- 8 files changed, 98 insertions(+), 35 deletions(-) diff --git a/docs/src/changelog.md b/docs/src/changelog.md index c028312..cae1c81 100644 --- a/docs/src/changelog.md +++ b/docs/src/changelog.md @@ -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 returns + `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 diff --git a/docs/src/dev_interface.md b/docs/src/dev_interface.md index acc7f39..3a18319 100644 --- a/docs/src/dev_interface.md +++ b/docs/src/dev_interface.md @@ -12,11 +12,10 @@ 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. -A graph type that does not store an integer graph can leave `encoded_graph` to -its generic `EncodedGraphView` fallback and define only `encoded_vertex` and -`decoded_vertex` for the translation. The view answers `nv`, `ne`, `has_vertex`, -`has_edge`, `edges`, and the neighbor queries by asking the named graph itself, -so such a type must also define those directly, or every one of them recurses. +A graph type that does not store an integer graph returns +[`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. `NamedGridGraph` is the model: it computes its topology from the grid size and uses the view only to present it on integer codes. @@ -40,6 +39,7 @@ encoded_vertex decoded_vertex encoded_edge decoded_edge +EncodedGraphView ``` ## Graphs.jl interface extensions diff --git a/src/NamedGraphs.jl b/src/NamedGraphs.jl index 9ad7afd..3075720 100644 --- a/src/NamedGraphs.jl +++ b/src/NamedGraphs.jl @@ -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 diff --git a/src/abstractnamedgraph.jl b/src/abstractnamedgraph.jl index 4bde0b6..b500ca6 100644 --- a/src/abstractnamedgraph.jl +++ b/src/abstractnamedgraph.jl @@ -77,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} @@ -100,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 +returns [`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 @@ -127,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 diff --git a/src/encodedgraphview.jl b/src/encodedgraphview.jl index 5999611..2c9078c 100644 --- a/src/encodedgraphview.jl +++ b/src/encodedgraphview.jl @@ -1,13 +1,17 @@ -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 returns `EncodedGraphView(graph)` from [`encoded_graph`](@ref) +and must define `nv`, `ne`, `has_vertex`, `has_edge`, `edges`, and the neighbor +hooks itself, since the view answers every query by asking `graph`. +`NamedGridGraph` is an example. +""" struct EncodedGraphView{G <: AbstractGraph} <: AbstractGraph{Int} graph::G end @@ -22,6 +26,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 @@ -32,6 +37,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) diff --git a/src/namedgridgraph.jl b/src/namedgridgraph.jl index 0d70813..378d03b 100644 --- a/src/namedgridgraph.jl +++ b/src/namedgridgraph.jl @@ -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. diff --git a/test/test_abstractnamedgraph.jl b/test/test_abstractnamedgraph.jl index a4f30ed..9f4ba54 100644 --- a/test/test_abstractnamedgraph.jl +++ b/test/test_abstractnamedgraph.jl @@ -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 diff --git a/test/test_encodedgraphview.jl b/test/test_encodedgraphview.jl index ac47135..354b235 100644 --- a/test/test_encodedgraphview.jl +++ b/test/test_encodedgraphview.jl @@ -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 @@ -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 From f152ee2d8705f5961aa7a3cc2979cfe3068ba471 Mon Sep 17 00:00:00 2001 From: Matthew Fishman Date: Tue, 8 Sep 2026 20:49:58 -0400 Subject: [PATCH 3/6] Say a type can return an EncodedGraphView, and drop the NamedGridGraph aside --- docs/src/changelog.md | 2 +- docs/src/dev_interface.md | 4 +--- src/abstractnamedgraph.jl | 2 +- src/encodedgraphview.jl | 5 ++--- 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/docs/src/changelog.md b/docs/src/changelog.md index cae1c81..fa68d4f 100644 --- a/docs/src/changelog.md +++ b/docs/src/changelog.md @@ -43,7 +43,7 @@ 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`, and returns + `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 diff --git a/docs/src/dev_interface.md b/docs/src/dev_interface.md index 3a18319..c8aa7dd 100644 --- a/docs/src/dev_interface.md +++ b/docs/src/dev_interface.md @@ -12,12 +12,10 @@ 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. -A graph type that does not store an integer graph returns +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. -`NamedGridGraph` is the model: it computes its topology from the grid size and -uses the view only to present it on integer codes. Rather than overloading a Graphs.jl function on your subtype, overload the hook it forwards to, named `f_namedgraph` for a Graphs.jl function `f` diff --git a/src/abstractnamedgraph.jl b/src/abstractnamedgraph.jl index b500ca6..9d4973b 100644 --- a/src/abstractnamedgraph.jl +++ b/src/abstractnamedgraph.jl @@ -89,7 +89,7 @@ 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 -returns [`EncodedGraphView(graph)`](@ref EncodedGraphView), and must then define +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`. diff --git a/src/encodedgraphview.jl b/src/encodedgraphview.jl index 2c9078c..2eb38bc 100644 --- a/src/encodedgraphview.jl +++ b/src/encodedgraphview.jl @@ -7,10 +7,9 @@ using Graphs: Graphs, AbstractGraph, Edge, SimpleDiGraph, SimpleGraph, add_edge! 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 returns `EncodedGraphView(graph)` from [`encoded_graph`](@ref) -and must define `nv`, `ne`, `has_vertex`, `has_edge`, `edges`, and the neighbor +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`. -`NamedGridGraph` is an example. """ struct EncodedGraphView{G <: AbstractGraph} <: AbstractGraph{Int} graph::G From 22e60ae2d02bb3c634d3cbb209db50c31c3a3070 Mon Sep 17 00:00:00 2001 From: Matthew Fishman Date: Tue, 8 Sep 2026 20:50:40 -0400 Subject: [PATCH 4/6] Leave the hook convention out of the developer page --- docs/src/dev_interface.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/docs/src/dev_interface.md b/docs/src/dev_interface.md index c8aa7dd..6a536a8 100644 --- a/docs/src/dev_interface.md +++ b/docs/src/dev_interface.md @@ -17,14 +17,6 @@ A graph type that does not store an integer graph can return 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. -Rather than overloading a Graphs.jl function on your subtype, overload the hook -it forwards to, named `f_namedgraph` for a Graphs.jl function `f` -(`neighbors_namedgraph`, `dijkstra_shortest_paths_namedgraph`, and so on). The -Graphs.jl functions are defined once on `AbstractNamedGraph`, in both the untyped -and `::Integer` vertex forms, and forward to the hook. Overloading the hook keeps -a subtype's methods unambiguous with Graphs.jl's own `::Integer` methods on a -graph whose vertex names are integers, with no disambiguator of its own. - Vertex codes are not stable across mutation: adding or removing vertices may reassign the codes of other vertices. From c7a4d84826fcc14b6b6d1b5f1aa5ae75b9e303c5 Mon Sep 17 00:00:00 2001 From: Matthew Fishman Date: Tue, 8 Sep 2026 20:59:29 -0400 Subject: [PATCH 5/6] Document and export eccentricities, since the changelog names it --- src/NamedGraphs.jl | 2 +- src/graphsextensions/abstractgraph.jl | 30 +++++++++++++++++++++++++++ test/test_exports.jl | 1 + 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/NamedGraphs.jl b/src/NamedGraphs.jl index 3075720..38fc0f4 100644 --- a/src/NamedGraphs.jl +++ b/src/NamedGraphs.jl @@ -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, diff --git a/src/graphsextensions/abstractgraph.jl b/src/graphsextensions/abstractgraph.jl index 5dd8719..07dbc66 100644 --- a/src/graphsextensions/abstractgraph.jl +++ b/src/graphsextensions/abstractgraph.jl @@ -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)) diff --git a/test/test_exports.jl b/test/test_exports.jl index ffa1bbd..712ae81 100644 --- a/test/test_exports.jl +++ b/test/test_exports.jl @@ -20,6 +20,7 @@ using Test: @test, @testset :default_root_vertex, :directed_graph, :disjoint_union, + :eccentricities, :edge_subgraph, :edgeless_graph, :empty_graph, From 35e312eb02355e81c0c8f8a6859f7963b13c9a13 Mon Sep 17 00:00:00 2001 From: Matthew Fishman Date: Tue, 8 Sep 2026 21:09:30 -0400 Subject: [PATCH 6/6] Add EncodedGraphView to the exports test's public list --- test/test_exports.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/test/test_exports.jl b/test/test_exports.jl index 712ae81..230ec28 100644 --- a/test/test_exports.jl +++ b/test/test_exports.jl @@ -57,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,