diff --git a/D3130_Container_Interface/src/concepts_adj_list.hpp b/D3130_Container_Interface/src/concepts_adj_list.hpp index 2c77ee2..79c4305 100644 --- a/D3130_Container_Interface/src/concepts_adj_list.hpp +++ b/D3130_Container_Interface/src/concepts_adj_list.hpp @@ -31,3 +31,13 @@ template concept mapped_bidirectional_adjacency_list = bidirectional_adjacency_list && mapped_vertex_range; + +// Semantic refinement: each vertex's out-edges are sorted by ascending +// target\_id. The structural check below only confirms a forward edge range; +// the ascending-order property is a semantic requirement the author asserts. +template +concept ordered_vertex_edges = + adjacency_list && + requires(G& g, vertex_t u) { + requires forward_iterator; + }; diff --git a/D3130_Container_Interface/tex/container_interface.tex b/D3130_Container_Interface/tex/container_interface.tex index fd7ad0d..64e0846 100644 --- a/D3130_Container_Interface/tex/container_interface.tex +++ b/D3130_Container_Interface/tex/container_interface.tex @@ -123,6 +123,11 @@ \subsubsection{Adjacency List Concepts} \item \tcode{mapped_bidirectional_adjacency_list} — \tcode{bidirectional_adjacency_list} with \tcode{mapped_vertex_range}. \end{itemize} +A seventh, orthogonal concept describes a graph whose adjacency lists are sorted: +\begin{itemize} + \item \tcode{ordered_vertex_edges} — an \tcode{adjacency_list} in which each vertex's out-edges are ordered by ascending \tcode{target_id}. This is primarily a \emph{semantic} property: the concept can only confirm structurally that \tcode{out_edges(g,u)} is a \tcode{forward_range}, while the ascending-order guarantee is asserted by the graph author. Graphs that store edges in ordered containers (e.g.\ \tcode{set} or \tcode{map} keyed by target id) satisfy it; unordered storage (e.g.\ \tcode{vector} appended in arbitrary order, or \tcode{unordered_set}) generally does not. It is used by algorithms that rely on a linear set-intersection merge, such as triangle counting (see the Algorithms proposal). +\end{itemize} + The previous interface defined \tcode{basic_*} and \tcode{sourced_*} variants to distinguish edges with or without a source id. The descriptor design makes this distinction unnecessary: all edge descriptors carry source information, so the \tcode{basic_*} and \tcode{sourced_*} concept families have been removed. @@ -363,6 +368,33 @@ \subsection{Vertex Property Map} \end{center} \end{table} +Two further utilities let algorithms accept a caller-supplied property map without +caring which underlying container backs it: + +\begin{lstlisting} +// Per-vertex value type of a property-map container: +// vector yields T (value\_type); unordered\_map yields V (mapped\_type) +template +using vertex_property_map_value_t = /* see prose */; + +// A container usable as a per-vertex property map for graph G: +// it can be subscripted by the graph's vertex id. +template +concept vertex_property_map_for = + requires(M& m, const vertex_id_t& uid) { + { m[uid] } -> convertible_to>; + }; +\end{lstlisting} + +\tcode{vertex_property_map_value_t} extracts the stored value type from +either container shape (the \tcode{mapped_type} of an \tcode{unordered_map}, or the +\tcode{value_type} of a \tcode{vector}). \tcode{vertex_property_map_for} is the +precise constraint for algorithm parameters such as \emph{Distances} or +\emph{Predecessors}: it requires only that \tcode{m[uid]} is valid for the graph's +\tcode{vertex_id_t}, since algorithms subscript such maps by vertex id rather than +iterating them as a range. Both \tcode{vector} (index graphs) and +\tcode{unordered_map,T>} (mapped graphs) satisfy it. + \subsection{Functions} Tables \ref{tab:graph_func}, \ref{tab:vertex_func} and \ref{tab:edge_func} summarize the primitive functions in the Graph Container Interface. @@ -430,8 +462,8 @@ \subsection{Functions} \tcode{in_degree(g,u)} & \tcode{integral} & constant & \tcode{size(in\_edges(g,u))} if \tcode{sized_range>} \\ \tcode{in_degree(g,uid)} & \tcode{integral} & constant & \tcode{size(in\_edges(g,uid))} if \tcode{sized_range>} \\ \hdashline - \tcode{partition_id(g,u)} & \tcode{partition_id_t} & constant & \\ - \tcode{partition_id(g,uid)} & \tcode{partition_id_t} & constant & \\ + \tcode{partition_id(g,u)} & \tcode{partition_id_t} & constant & \tcode{0} (partition 0) \\ + \tcode{partition_id(g,uid)} & \tcode{partition_id_t} & constant & \tcode{partition_id(g,*find_vertex(g,uid))} \\ \hline \hline \end{tabular}} @@ -444,6 +476,11 @@ \subsection{Functions} to have constant complexity. If the underlying container has a non-linear \tcode{size(R)} function, the degree functions will also be non-linear. This is expected to be an uncommon case. +The descriptor form \tcode{partition_id(g,u)} is always available, defaulting to \tcode{0} +(all vertices in partition~0). The id form \tcode{partition_id(g,uid)} --- the convenience +overload that would resolve to \tcode{partition_id(g,*find_vertex(g,uid))} --- is not yet in +the reference implementation. + \begin{table}[h!] \begin{center} \resizebox{\textwidth}{!} @@ -565,6 +602,15 @@ \subsection{Vertex and Edge Descriptor Views} explicit vertex storage. An iterator-based approach cannot offer this property --- an iterator must point to an actual object. +For such index-only graphs the GCI provides a canonical descriptor specialization: +\tcode{index_iterator} is the iterator type of an \tcode{iota_view} +(a random-access iterator that yields indices by value, with no backing element), and +\tcode{index_vertex_descriptor} is \tcode{vertex_descriptor}. Because +\tcode{index_iterator} is random-access, the descriptor stores a \tcode{size_t} index +and behaves like any other index-based \tcode{vertex_descriptor}, except that +\tcode{underlying_value}/\tcode{inner_value} are not applicable (there is no physical +element to dereference). + CPOs automatically detect the storage pattern of an adjacency list and select the appropriate descriptor strategy: \textbf{random-access} containers (\tcode{vector}, \tcode{deque}) produce index-based vertex descriptors; \textbf{associative} containers @@ -795,14 +841,16 @@ \section{Using Existing Data Structures} Reasonable defaults have been defined for the adjacency list and edgelist functions to minimize the amount of work needed to adapt existing data structures to be used by the views and algorithms. -Useful defaults have been created using types and containers in the standard library, with the ability -to override them for external data structures. This is described in more detail in the paper for Graph Library -Containers. +This section specifies the structural patterns the GCI CPOs recognize automatically --- the +\emph{normative trigger} that determines when no function overrides are required. The +companion Graph Containers proposal catalogs the concrete standard containers that match these +patterns, their performance trade-offs, and worked examples; see that paper for the catalog and +usage. \subsection{Recognized Vertex Patterns} When a graph type \tcode{G} is itself a \tcode{forward_range} whose elements are also ranges, the -GCI CPOs can detect and adapt to the storage strategy automatically. Two broad categories of vertex +GCI CPOs detect and adapt to the storage strategy automatically. Two broad categories of vertex container are recognized, each producing a different descriptor and vertex-id strategy. \subsubsection{Random-Access Vertex Pattern} @@ -817,13 +865,6 @@ \subsubsection{Random-Access Vertex Pattern} \item \tcode{out_edges(g, u)} returns the inner range at that index. \end{itemize} -Examples of standard containers matching this pattern: -\begin{itemize} - \item \tcode{vector>} --- vertices in a \tcode{vector}, edges as \tcode{int} target ids. - \item \tcode{vector>>} --- vertices in a \tcode{vector}, edges as weighted pairs in a \tcode{list}. - \item \tcode{deque>} --- vertices in a \tcode{deque}, edges in a \tcode{forward_list}. -\end{itemize} - \subsubsection{Associative Vertex Pattern} When \tcode{G} is an associative container (\tcode{std::map} or \tcode{std::unordered_map}) whose mapped values are forward ranges, vertices are keyed by an arbitrary id type rather than @@ -838,73 +879,18 @@ \subsubsection{Associative Vertex Pattern} range) from the key-value pair. \end{itemize} -Examples of standard containers matching this pattern: -\begin{itemize} - \item \tcode{map>} --- sparse vertex ids in a sorted \tcode{map}, edges as target ids. - \item \tcode{unordered_map>>} --- hash-based vertex lookup, weighted edges. -\end{itemize} - -\subsubsection{Edge Container Patterns} +\subsection{Recognized Edge Patterns} The edge range returned by \tcode{out_edges(g, u)} is the inner range stored at (or -referenced by) each vertex. Any standard container satisfying \tcode{forward_range} -whose element type matches a recognized edge element pattern (described below) is -accepted automatically. The library does \emph{not} maintain a fixed list of containers; -it relies on concept-based detection. - -Table~\ref{tab:edge_container_patterns} groups the standard containers by category and -summarises their trade-offs when used as edge containers. - -\begin{table}[h!] -\begin{center} -\resizebox{\textwidth}{!} -{\begin{tabular}{l l L{7.5cm}} -\hline - \textbf{Category} & \textbf{Container} & \textbf{Characteristics} \\ -\hline - Sequence & - \tcode{vector} & - Random-access, cache-friendly, amortized $\mathcal{O}(1)$ push-back. \\ - & - \tcode{deque} & - Random-access, stable references on push-back. \\ - & - \tcode{list} & - Bidirectional, $\mathcal{O}(1)$ splice, stable iterators. \\ - & - \tcode{forward_list} & - Forward-only, minimal overhead, $\mathcal{O}(1)$ push-front. \\ -\hdashline - Ordered associative & - \tcode{set} & - Sorted, automatic deduplication, $\mathcal{O}(\log n)$ lookup; element is the edge itself. \\ - & - \tcode{map} & - Sorted by target id, $\mathcal{O}(\log n)$ lookup, no parallel edges; \tcode{.first} is - target id, \tcode{.second} is edge value. \\ -\hdashline - Unordered associative & - \tcode{unordered_set} & - Hash-based deduplication, amortized $\mathcal{O}(1)$ insert/lookup. \\ - & - \tcode{unordered_map} & - Hash-based by target id, amortized $\mathcal{O}(1)$ lookup, no parallel edges; - \tcode{.first} is target id, \tcode{.second} is edge value. \\ -\hline -\end{tabular}} -\caption{Recognized Edge Container Patterns} -\label{tab:edge_container_patterns} -\end{center} -\end{table} - -For associative containers whose \tcode{value_type} is a \tcode{pair} -(i.e.\ \tcode{map} and \tcode{unordered_map}), the key is -treated as the target vertex id and the mapped type as the edge value. This means that -\tcode{target_id(g,uv)} returns \tcode{.first} and \tcode{edge_value(g,uv)} returns -\tcode{.second}, following the same pair convention described below. +referenced by) each vertex. Any range satisfying \tcode{forward_range} whose element type matches +a recognized edge element pattern (below) is accepted automatically. The library does \emph{not} +maintain a fixed list of containers; it relies on concept-based detection, so non-standard +containers (e.g.\ \tcode{boost} containers) work identically. -For set-like containers (\tcode{set}, \tcode{unordered_set}), each element is the edge -itself. If the element is integral it serves as the target id directly; if it is a -\tcode{pair} or \tcode{tuple} the element-level rules below apply. +For associative edge containers whose \tcode{value_type} is a \tcode{pair} +(i.e.\ \tcode{map} and \tcode{unordered_map}), the key is treated as the target vertex id and the +mapped type as the edge value: \tcode{target_id(g,uv)} returns \tcode{.first} and +\tcode{edge_value(g,uv)} returns \tcode{.second}. For set-like containers (\tcode{set}, +\tcode{unordered_set}) each element is the edge itself, and the element-level rules below apply. \subsubsection{Edge Element Patterns} Within each vertex's edge range the GCI recognizes the following element forms and @@ -934,12 +920,8 @@ \subsubsection{Edge Element Patterns} \end{table} \noindent -These vertex and edge patterns combine freely: any recognized vertex container can hold -any recognized edge container, and any recognized edge container can hold any recognized -edge element type. For example, -\tcode{vector>>} is a random-access graph with weighted edges, -\tcode{vector>} is a random-access graph with sorted deduplicated unweighted edges, -\tcode{vector>} is a random-access graph with $\mathcal{O}(\log n)$ edge lookup by target, -and \tcode{map>} is an associative graph with unweighted edges. -When the element type does not match a recognized pattern, the user must override the -appropriate CPOs for the graph type. +These vertex and edge patterns combine freely: any recognized vertex container can hold any +recognized edge container, and any recognized edge container can hold any recognized edge element +type. When the element type does not match a recognized pattern, the user must override the +appropriate CPOs for the graph type. The Graph Containers proposal enumerates the standard +containers in each category and their trade-offs. diff --git a/D3130_Container_Interface/tex/revision.tex b/D3130_Container_Interface/tex/revision.tex index 35a2015..d301cd7 100644 --- a/D3130_Container_Interface/tex/revision.tex +++ b/D3130_Container_Interface/tex/revision.tex @@ -123,4 +123,8 @@ \subsection*{\paperno r4} containers. \item Edgelist CPOs moved to 2-argument form \tcode{f(el,uv)}; \tcode{edge_info} renamed to \tcode{edge_data}; \tcode{edge_reference_t} removed; \tcode{raw-vertex-id-type} added. + \item Completed the migration of std-container material to the Graph Containers proposal: this + paper now states only the normative recognition patterns (random-access / associative vertex + patterns and the integral/\tcode{pair}/\tcode{tuple} edge element patterns), with the concrete + container catalog, trade-off table and worked examples moved to that paper. \end{itemize} diff --git a/D3131_Containers/src/compressed_graph.hpp b/D3131_Containers/src/compressed_graph.hpp index d6a8f27..2f04d6d 100644 --- a/D3131_Containers/src/compressed_graph.hpp +++ b/D3131_Containers/src/compressed_graph.hpp @@ -27,14 +27,16 @@ class compressed_graph { // compressed\_graph(gv\&\&, erng, eprojection, alloc) template - requires convertible_to, VId> + requires copyable_edge>, VId, EV> && + convertible_to, VId> constexpr compressed_graph(const ERng& erng, EProj eprojection, const PartRng& partition_start_ids = vector(), const Alloc& alloc = Alloc()); template - requires convertible_to, VId> + requires copyable_edge>, VId, EV> && + convertible_to, VId> constexpr compressed_graph(const graph_value_type& value, const ERng& erng, EProj eprojection, @@ -42,7 +44,8 @@ class compressed_graph { const Alloc& alloc = Alloc()); template - requires convertible_to, VId> + requires copyable_edge>, VId, EV> && + convertible_to, VId> constexpr compressed_graph(graph_value_type&& value, const ERng& erng, EProj eprojection, @@ -58,7 +61,9 @@ class compressed_graph { forward_range PartRng, class EProj = identity, class VProj = identity> - requires convertible_to, VId> + requires copyable_edge>, VId, EV> && + copyable_vertex>, VId, VV> && + convertible_to, VId> constexpr compressed_graph(const ERng& erng, const VRng& vrng, EProj eprojection = {}, @@ -71,7 +76,9 @@ class compressed_graph { forward_range PartRng, class EProj = identity, class VProj = identity> - requires convertible_to, VId> + requires copyable_edge>, VId, EV> && + copyable_vertex>, VId, VV> && + convertible_to, VId> constexpr compressed_graph(const graph_value_type& value, const ERng& erng, const VRng& vrng, @@ -85,7 +92,9 @@ class compressed_graph { forward_range PartRng, class EProj = identity, class VProj = identity> - requires convertible_to, VId> + requires copyable_edge>, VId, EV> && + copyable_vertex>, VId, VV> && + convertible_to, VId> constexpr compressed_graph(graph_value_type&& value, const ERng& erng, const VRng& vrng, diff --git a/D3131_Containers/src/compressed_graph_gvoid.hpp b/D3131_Containers/src/compressed_graph_gvoid.hpp index c709f11..b3401f6 100644 --- a/D3131_Containers/src/compressed_graph_gvoid.hpp +++ b/D3131_Containers/src/compressed_graph_gvoid.hpp @@ -1,10 +1,6 @@ -template > template class compressed_graph + : public compressed_graph_base { public: // Construction/Destruction constexpr compressed_graph() = default; constexpr compressed_graph(const compressed_graph&) = default; @@ -22,14 +18,17 @@ class compressed_graph constexpr compressed_graph(const ERng& erng, EProj eprojection = identity(), const PartRng& partition_start_ids = vector(), - const Alloc& alloc = Alloc()) + const Alloc& alloc = Alloc()); // edge and vertex value construction template + class EProj = identity, + class VProj = identity, + forward_range PartRng = vector> + requires copyable_edge>, VId, EV> && + copyable_vertex>, VId, VV> && + convertible_to, VId> constexpr compressed_graph(const ERng& erng, const VRng& vrng, EProj eprojection = {}, @@ -37,7 +36,7 @@ class compressed_graph const PartRng& partition_start_ids = vector(), const Alloc& alloc = Alloc()); - // initializer list using edge\_info + // initializer list using edge\_data constexpr compressed_graph(const initializer_list>& ilist, const Alloc& alloc = Alloc()); }; diff --git a/D3131_Containers/tex/containers.tex b/D3131_Containers/tex/containers.tex index ba799e1..34a64db 100644 --- a/D3131_Containers/tex/containers.tex +++ b/D3131_Containers/tex/containers.tex @@ -33,9 +33,9 @@ \section{compressed\_graph Graph Container} \centering \begin{tabular}{|P{0.38\textwidth}|P{0.35\textwidth}|P{0.25\textwidth}|} \hline - \textbf{vertex\_id assignment:} Contiguous & \textbf{\tcode{has_edge(g)}} $O(1)$ & \textbf{Append vertices?} No \\ + \textbf{vertex\_id assignment:} Contiguous & \textbf{\tcode{has_edges(g)}} $O(1)$ & \textbf{Append vertices?} No \\ \textbf{Vertices range:} Contiguous & \textbf{\tcode{num_edges(g)}} $O(1)$ & \textbf{Append edges?} No \\ - \textbf{Edge range:} Contiguous & \textbf{\tcode{partition_id(g,uid)}} $O(log(P+1))$ & \textbf{Partitions?} Yes\\ + \textbf{Edge range:} Contiguous & \textbf{\tcode{partition_id(g,u)}} $O(log(P+1))$ & \textbf{Partitions?} Yes\\ & & \textbf{is\_directed?} No \\ %O(log(P+1)) is the complexity of std::upper_bound; +1 is for the number of vertices added at the end of partition_start_ids \hline @@ -49,7 +49,11 @@ \section{compressed\_graph Graph Container} The \tcode{is_directed} trait is not supported. If \tcode{compressed_graph} is intended to be used for an undirected graph, then the edge pairs must be included for both directions, (uid,vid) and (vid,uid), when constructing the graph. -\phil{Add \tcode{operator[](vertex_id_t)}?} +A public \tcode{operator[](vertex_id_t)} is \emph{not} provided. The only value it could return is an +internal CSR element that is not useful on its own, and exposing it would breach the rule that +\tcode{compressed_graph} is accessed solely through the Graph Container Interface. The equivalent functionality +is already available generically via \tcode{vertex_value(g,uid)} for the vertex value and +\tcode{out_edges(g,*find_vertex(g,uid))} for the incident edges. \subsection{compressed\_graph when \tcode{GV} is not \tcode{void}} \label{compressed_full} {\small @@ -65,7 +69,15 @@ \subsection{compressed\_graph specialization when \tcode{GV} is \tcode{void}} \l \subsection{compressed\_graph description} -\phil{Is it possible to support movable EV and VV types?} +\tcode{EV} and \tcode{VV} are currently required to be copyable. Supporting move-only \tcode{EV}/\tcode{VV} +is possible future work but is not proposed here, because it would: (1) disable the +\tcode{initializer_list} constructor (an \tcode{initializer_list} exposes only \tcode{const} elements, so its +values can only be copied); (2) restrict construction to the rvalue-range overloads that consume an owning +input range (the \tcode{const}-lvalue-range overloads copy each value); and (3) make the whole +\tcode{compressed_graph} move-only for those instantiations, since its defaulted copy constructor would be +deleted once the internal value vectors are non-copyable. It would also require a corresponding relaxation of +the \tcode{copyable_edge}/\tcode{copyable_vertex} requirements in the Graph Container Interface. A non-\tcode{void} +\tcode{GV} is already permitted to be movable (which likewise makes the graph move-only). \begin{itemdescr} \pnum\mandates @@ -74,11 +86,11 @@ \subsection{compressed\_graph description} \item The \tcode{VV} template argument for a vertex value must be a copyable type or \tcode{void}. \item When the \tcode{GV} template argument for a graph value is not \tcode{void} it can be movable or copyable. It must have a default constructor if it is not passed in a \tcode{compressed_graph} constructor. - \item The \tcode{EProj} template argument must be a projection that returns a value of \tcode{copyable_edge} type - given a value of \tcode{erng}. If the value type of \tcode{ERng} is already a \tcode{copyable_edge} type, + \item The \tcode{EProj} template argument must be a projection that returns a value of \tcode{copyable_edge_t} type + given a value of \tcode{erng}. If the value type of \tcode{ERng} is already a \tcode{copyable_edge_t} type, then \tcode{EProj} can be \tcode{identity}. - \item The \tcode{VProj} template argument must be a projection that returns a value of \tcode{copyable_vertex} type, - given a value of \tcode{vrng}. If the value type of \tcode{Vrng} is already a \tcode{copyable_vertex} type, + \item The \tcode{VProj} template argument must be a projection that returns a value of \tcode{copyable_vertex_t} type, + given a value of \tcode{vrng}. If the value type of \tcode{Vrng} is already a \tcode{copyable_vertex_t} type, then \tcode{VProj} can be \tcode{identity}. \end{itemize} \pnum\preconditions @@ -90,10 +102,9 @@ \subsection{compressed\_graph description} \item The \tcode{EProj} and \tcode{VProj} template arguments must be valid projections. \item The \tcode{partition_start_ids} range includes the starting vertex id for each partition. If it is empty, then the graph is single-partite and the number of partitions is 1. If it is not empty, then the number of partitions is the size of the range, where the first element - must be 0 and all elements are in ascending order. A vertex id in the range must not exceed the number of - vertices in the graph. Any violation of these conditions results in undefined behavior. + must be 0 and all elements are in strictly increasing order. A vertex id in the range must not exceed the number of + vertices in the graph. Any violation of these conditions throws \tcode{graph_error}. \end{itemize} -\phil{If duplicate partition\_start\_ids exist they create an empty partition with no vertices.} \pnum\effects \begin{itemize} @@ -134,23 +145,28 @@ \subsubsection{Using Standard Containers for an Adjacency List} \begin{lstlisting} using G = vector>>; -auto weight = [&g](edge_t& uv) { return get<1>(uv); } G g; -load_graph(g, ...); // load some data +// ... populate g with vertices and weighted edges ... + +// A weight accessor following the f(const graph, descriptor) convention. +// edge value is the second tuple element (the double). +auto weight = [](const G& g, edge_t uv) { return edge_value(g, uv); }; // Using GCI functions -for(auto&& [u] : vertices(g)) { - for(auto&& [vid, uv]: edges(g,u)) { - auto w = weight(uv); +for (auto u : vertices(g)) { // u is a vertex descriptor + for (auto uv : edges(g, u)) { // uv is an edge descriptor + auto vid = target_id(g, uv); // the target id (an int) + auto w = weight(g, uv); // the weight (a double) // do something... } } \end{lstlisting} \tcode{edge_t} is defined as \tcode{tuple} in the example; the only requirement is that the first element -in the \tcode{tuple} is integral and is used as the target\_id. The \tcode{edge_value} function is not defined, as it is -assumed that algorithms will take a lambda to extract the value from the edge, if needed. +in the \tcode{tuple} is integral and is used as the target\_id. The \tcode{edge_value(g,uv)} function is automatically +available for a recognized \tcode{tuple} edge and returns the remaining value (the \tcode{double} here); defining it +explicitly is therefore optional, since algorithms typically accept a value lambda to extract the value from the edge. If all you need is the target\_id without any values, you can use \tcode{G = vector>}. The \tcode{int} is used as the target\_id. Again, the only requirement is that it be \tcode{integral}. @@ -159,20 +175,23 @@ \subsubsection{Using Standard Containers for an Adjacency List} and \tcode{random_access_range>>}. This extends to any range type. For instance, boost::containers can be used just as easily as std containers. -Table \ref{tab:simple_graph} shows how the types are defined for the example above. +Table \ref{tab:simple_graph} shows how the types are defined for the example above. Because this proposal uses +boost-like descriptors, \tcode{vertices(g)} and \tcode{edges(g,u)} yield \emph{descriptors}; the standard-container +elements (\tcode{forward_list<...>}, \tcode{tuple}) are the \emph{underlying} values reached through those +descriptors. \begin{table}[h!] \begin{center} \resizebox{\textwidth}{!} - {\begin{tabular}{l l p{1.5cm} L{7.0cm}} + {\begin{tabular}{l L{9.0cm}} \hline \textbf{Function or Value} & \textbf{Concrete Type} \\ \hline - \tcode{vertices(g)} & \tcode{vector>>} (when \tcode{random_access_range}) \\ - \tcode{u} & \tcode{forward_list>} & \\ - \tcode{edges(g,u)} & \tcode{forward_list>} (when \tcode{random_access_range>}) \\ - \tcode{uv} & \tcode{tuple} & \\ - \tcode{edge_value(g,uv)} & \tcode{tuple} (when \tcode{random_access_range>}) \\ - \tcode{target_id(g,uv)} & \tcode{integral}, when \tcode{uv} is either \tcode{integral} or \tcode{tuple} \\ + \tcode{vertices(g)} & vertex range of descriptors over \tcode{vector>>} \\ + \tcode{u} & a \tcode{vertex_t} descriptor; underlying value \tcode{forward_list>} \\ + \tcode{edges(g,u)} & edge range of descriptors over the vertex's \tcode{forward_list>} \\ + \tcode{uv} & an \tcode{edge_t} descriptor; underlying value \tcode{tuple} \\ + \tcode{target_id(g,uv)} & \tcode{int} --- \tcode{get<0>(uv)} (the integral target\_id) \\ + \tcode{edge_value(g,uv)} & \tcode{double} --- \tcode{get<1>(uv)} (optional; the remaining tuple value) \\ \hline \end{tabular}} \caption{Types When Using Standard Containers} @@ -180,6 +199,114 @@ \subsubsection{Using Standard Containers for an Adjacency List} \end{center} \end{table} +\paragraph{Catalog of recognized standard containers} +The Graph Container Interface proposal specifies the normative patterns that trigger automatic +recognition (the random-access and associative vertex patterns, and the integral/\tcode{pair}/\tcode{tuple} +edge element patterns). This section catalogs the concrete standard containers that match those +patterns and their trade-offs. Detection is concept-based, so non-standard containers (e.g.\ +\tcode{boost} containers) that satisfy the same patterns work identically. + +Vertex containers in the \textbf{random-access} pattern (\tcode{vertex_id_t} is the integral index, +$\mathcal{O}(1)$ \tcode{find_vertex}): +\begin{itemize} + \item \tcode{vector>} --- vertices in a \tcode{vector}, edges as \tcode{int} target ids. + \item \tcode{vector>>} --- vertices in a \tcode{vector}, edges as weighted pairs in a \tcode{list}. + \item \tcode{deque>} --- vertices in a \tcode{deque}, edges in a \tcode{forward_list}. +\end{itemize} + +Vertex containers in the \textbf{associative} pattern (\tcode{vertex_id_t} is the \tcode{key_type}, +\tcode{find_vertex} via \tcode{g.find(uid)}): +\begin{itemize} + \item \tcode{map>} --- sparse vertex ids in a sorted \tcode{map}, edges as target ids. + \item \tcode{unordered_map>>} --- hash-based vertex lookup, weighted edges. +\end{itemize} + +The associative pattern uses the same GCI functions as the random-access pattern, again with no overrides. +The difference is that the vertex id is the map \emph{key} rather than a dense index, so it may be sparse (or a +non-integral type such as \tcode{string}). For \tcode{G = map>>} the inner +\tcode{vector>} is the edge range reached through \tcode{edges(g,u)}, the \tcode{pair.first} is +the target\_id and the \tcode{pair.second} is the edge value. + +\begin{lstlisting} +using G = map>>; // sparse vertex ids, weighted edges + +G g; +// ... populate g, keyed by vertex id ... + +// Same accessor convention as the random-access example. +auto weight = [](const G& g, edge_t uv) { return edge_value(g, uv); }; + +// Using GCI functions -- identical loop shape to the vector-based example. +for (auto u : vertices(g)) { // u is a vertex descriptor + auto uid = vertex_id(g, u); // the map key (a sparse int) + for (auto uv : edges(g, u)) { // uv is an edge descriptor over the mapped value + auto vid = target_id(g, uv); // the target id (pair.first) + auto w = weight(g, uv); // the weight (pair.second) + // do something... + } +} +\end{lstlisting} + +Such a graph models \tcode{adjacency_list} and (because the vertex id is hashable) \tcode{mapped_adjacency_list}, +but \emph{not} \tcode{index_adjacency_list}: \tcode{find_vertex(g,uid)} is $\mathcal{O}(\log |V|)$ for \tcode{map} +or amortized $\mathcal{O}(1)$ for \tcode{unordered_map} rather than $\mathcal{O}(1)$ indexed access. Algorithms in +this proposal that require \tcode{index_adjacency_list} therefore need an index-based container; the associative +form is intended for adapting existing keyed data in place, as described in the Graph Container Interface proposal. + +The inner edge range returned by \tcode{edges(g,u)} may be any \tcode{forward_range} whose element matches a +recognized edge element pattern. Table~\ref{tab:edge_container_patterns} groups the standard containers by +category and summarises their trade-offs as edge containers. + +\begin{table}[h!] +\begin{center} +\resizebox{\textwidth}{!} +{\begin{tabular}{l l L{7.5cm}} +\hline + \textbf{Category} & \textbf{Container} & \textbf{Characteristics} \\ +\hline + Sequence & + \tcode{vector} & + Random-access, cache-friendly, amortized $\mathcal{O}(1)$ push-back. \\ + & + \tcode{deque} & + Random-access, stable references on push-back. \\ + & + \tcode{list} & + Bidirectional, $\mathcal{O}(1)$ splice, stable iterators. \\ + & + \tcode{forward_list} & + Forward-only, minimal overhead, $\mathcal{O}(1)$ push-front. \\ +\hdashline + Ordered associative & + \tcode{set} & + Sorted, automatic deduplication, $\mathcal{O}(\log n)$ lookup; element is the edge itself. \\ + & + \tcode{map} & + Sorted by target id, $\mathcal{O}(\log n)$ lookup, no parallel edges; \tcode{.first} is + target id, \tcode{.second} is edge value. \\ +\hdashline + Unordered associative & + \tcode{unordered_set} & + Hash-based deduplication, amortized $\mathcal{O}(1)$ insert/lookup. \\ + & + \tcode{unordered_map} & + Hash-based by target id, amortized $\mathcal{O}(1)$ lookup, no parallel edges; + \tcode{.first} is target id, \tcode{.second} is edge value. \\ +\hline +\end{tabular}} +\caption{Recognized Edge Container Patterns} +\label{tab:edge_container_patterns} +\end{center} +\end{table} + +These vertex and edge containers combine freely: any recognized vertex container can hold any recognized +edge container, and any recognized edge container can hold any recognized edge element type. For example, +\tcode{vector>>} is a random-access graph with weighted edges, +\tcode{vector>} is a random-access graph with sorted deduplicated unweighted edges, +\tcode{vector>} is a random-access graph with $\mathcal{O}(\log n)$ edge lookup by target, +and \tcode{map>} is an associative graph with unweighted edges. +When the element type does not match a recognized pattern, the user must override the appropriate CPOs for +the graph type, as described next. \subsubsection{Using Other Graph Data Structures} For other graph data structures function overrides are required. Table \ref{tab:cmn_cpo_overrides} shows the @@ -209,7 +336,7 @@ \subsubsection{Using Other Graph Data Structures} \hdashline \tcode{num_partitions(g)} & \\ \tcode{partition_id(g,u)} & \\ - \tcode{vertices(g,u,pid)} & \\ + \tcode{vertices(g,pid)} & \\ \hline \end{tabular}} \caption{Common CPO Function Overrides} @@ -227,35 +354,32 @@ \subsubsection{Using Standard Containers for an Edgelist} using E = std::range_value_t; EL el{{1, 2, 11.1}, {1, 4, 22.2}, {2, 3, 3.33}, {2, 4, 4.44}}; for (auto&& e : el) { - int sid = source_id(e); - int tid = target_id(e); - double val = edge_value(e); + int sid = source_id(el, e); + int tid = target_id(el, e); + double val = edge_value(el, e); } \end{lstlisting} -An alternative is to use the \tcode{edge_info} used in this proposal. Notice that the only difference +An alternative is to use the \tcode{edge_data} used in this proposal. Notice that the only difference is the definition of the edgelist type. All other code is identical to the previous example. \begin{lstlisting} - using EL = vector>; + using EL = vector>; using E = std::range_value_t; EL el{{1, 2, 11.1}, {1, 4, 22.2}, {2, 3, 3.33}, {2, 4, 4.44}}; for (auto&& e : el) { - int sid = source_id(e); - int tid = target_id(e); - double val = edge_value(e); + int sid = source_id(el, e); + int tid = target_id(el, e); + double val = edge_value(el, e); } \end{lstlisting} While these examples show the optional edge\_value that is a \tcode{double}, it can be omitted if the edges do not have values. -Type alias are in the namespace \tcode{std::graph::edgelist} to avoid conflicts with adjacency\_list +Type alias are in the namespace \tcode{std::graph::edge_list} to avoid conflicts with adjacency\_list types. \subsubsection{Using Other Graph Data Structures} If you have different edge type not covered by the standard types, you can override the \tcode{source_id(e)}, \tcode{target_id(e)} and \tcode{edge_value(E)} functions for that type. The functions must be in the same namespace as the edge data structure you want to use. - -\phil{Add example(s) that demonstrate how to have mutable edge values for an undirected graph, where there -are duplicate edges (u,v) and (v,u).} diff --git a/D3131_Containers/tex/revision.tex b/D3131_Containers/tex/revision.tex index f7893c7..e2e587d 100644 --- a/D3131_Containers/tex/revision.tex +++ b/D3131_Containers/tex/revision.tex @@ -36,8 +36,27 @@ \subsection*{\paperno r3} Related changes that affect this paper include: \begin{itemize} \item Rename \tcode{descriptor} structs to \tcode{info} structs for new boost::graph-like descriptors. - \item Remove \tcode{copyable_vertex} and \tcode{copyable_edge} from the \tcode{compressed_graph} \tcode{requires} clause - because descriptors are always copyable. + \item Retain \tcode{copyable_edge} and \tcode{copyable_vertex} in the \tcode{compressed_graph} + \tcode{requires} clauses. These constrain the \emph{result} of the \tcode{EProj}/\tcode{VProj} + projection to be convertible to \tcode{copyable_edge_t} / \tcode{copyable_vertex_t}; + they validate the projected value's type and are unrelated to descriptor copyability. \end{itemize} \end{itemize} + +\subsection*{\paperno r4} +\begin{itemize} + \item Received the std-container catalog from the Graph Container Interface proposal: the concrete + container example lists, the edge-container trade-off table, and the combined examples now live + here; the normative recognition patterns remain in the GCI paper, which this paper references. + \item Consistency fixes against the reference implementation: \tcode{edge_info} renamed to + \tcode{edge_data}; edgelist CPOs shown in 2-argument form \tcode{source_id(el,e)} / + \tcode{target_id(el,e)} / \tcode{edge_value(el,e)}; edgelist namespace corrected to + \tcode{std::graph::edge_list}; \tcode{has_edge(g)} corrected to \tcode{has_edges(g)}; + \tcode{partition_id(g,u)} (descriptor form); \tcode{vertices(g,pid)} override; and the + \tcode{copyable_edge_t} / \tcode{copyable_vertex_t} projection result types. + \item Corrected the \tcode{compressed_graph} partition preconditions: invalid + \tcode{partition_start_ids} (first element not 0, not strictly increasing, or exceeding the + vertex count) throw \tcode{graph_error} rather than being undefined behavior. + \item Updated the standard-container adjacency example and its type table to the descriptor model. +\end{itemize} diff --git a/agents/D3130_review.md b/agents/D3130_review.md index f771b66..0a0512b 100644 --- a/agents/D3130_review.md +++ b/agents/D3130_review.md @@ -3,6 +3,41 @@ Review of `D3130_Container_Interface/tex/container_interface.tex` against the reference implementation in `/home/phil/dev_graph/graph-v3/include/graph/`. +--- + +## Fresh review pass (2026-06-08) + +Re-verified the entire paper against the current code. Concepts, traits, type +aliases, descriptors, CPO defaults, value-function concepts, property map, data +structs (`vertex_data`/`edge_data`/`neighbor_data` + `copyable_*` aliases), and the +edgelist interface all match. Items resolved this pass (author-approved): + +- **[GAP] `ordered_vertex_edges` now documented.** The concept lives in GCI code + (`adjacency_list_concepts.hpp`, `graph::adj_list`) but was undocumented (paper said + "Six concepts"). Added it to the snippet `concepts_adj_list.hpp` and to the + Adjacency List Concepts section as a seventh, orthogonal (semantic-ordering) concept, + cross-referencing triangle counting in the Algorithms paper. +- **[GAP] `vertex_property_map_value_t` + `vertex_property_map_for` now documented.** + Both are public in `vertex_property_map.hpp` (re-exported into `graph`) but the paper + documented only the alias + 4 helpers. Added an `lstlisting` and prose to the Vertex + Property Map section. +- **[GAP] Index-only descriptor types named.** The descriptor section described + index-only vertices (compressed_graph) in prose; now names `index_iterator` + (`iota_view` iterator) and `index_vertex_descriptor`. +- **[LOW] `partition_id` defaults documented.** Vertex Functions table left the default + column blank. Code: `partition_id(g,u)` defaults to `0` (partition 0); there is **no** + `partition_id(g,uid)` default in the reference impl. Filled `0 (partition 0)` for the + descriptor form and added a note that the id form is not yet implemented. +- **[NOTE] `vertex_value(g,uid)` complexity left as "constant"** — consistent with the + other id-form convenience overloads in the same table under the stated index-graph + assumption (find_vertex is O(1)); changing only this row would create an inconsistency. + +PDF rebuilds cleanly (0 undefined refs). Build note: the `lstset` uses `texcl=true`, so +`//` comments inside `lstlisting`/`lstinputlisting` are rendered as LaTeX — underscores +in such comments must be escaped (`\_`). + +--- + Scope reviewed: full GCI paper text — adjacency-list concepts/traits/types/functions, descriptors, vertex property map, value-function concepts, partitions, and the edgelist interface. Focus is clarity, consistency (paper vs. code), and completeness. diff --git a/agents/d3131_review.md b/agents/d3131_review.md new file mode 100644 index 0000000..31200f9 --- /dev/null +++ b/agents/d3131_review.md @@ -0,0 +1,288 @@ +# D3131 Containers — Review Comments + +> **Status (2026-06-09):** All findings below have been APPLIED to the paper and +> both PDFs rebuild cleanly. The D3130/D3131 scope split (Q1) was resolved: +> D3130 keeps the normative recognition *patterns*; D3131 received the concrete +> container *catalog*, trade-off table, and worked examples. The reconcile +> question **1.2** (requires-clause) and scope questions **Q2–Q5** are resolved. +> Per-item status is tagged inline as *(APPLIED)*. + +Review of `D3131_Containers/tex/containers.tex` (plus snippets +`src/compressed_graph.hpp`, `src/compressed_graph_gvoid.hpp` and `revision.tex`) +against the reference implementation in +`/home/phil/dev_graph/graph-v3/include/graph/container/compressed_graph.hpp`. + +Scope per request: only `compressed_graph` is in scope as a concrete container +(the code's `dynamic_graph` and `undirected_adjacency_list` are correctly +omitted). Adjacency lists and edgelists built from standard containers are in +scope and must be thoroughly described with examples. Topics owned by the GCI +paper (D3130) — concepts, CPO defaults, descriptors, the recognized vertex/edge +patterns — are referenced but not re-specified here. + +Legend: **[HIGH]** = code/paper genuinely disagree on the interface; **[MED]** = +documented symbol/behavior absent or different in the reference impl, or an +internal contradiction; **[LOW]** = wording/snippet polish. + +--- + +## 1. Code-vs-paper inconsistencies + +### 1.1 [HIGH] `edge_info` is a stale name — should be `edge_data` *(APPLIED)* +- Paper (Edgelist std-container example): `using EL = vector>;` + and prose "use the `edge_info` used in this proposal." The `gvoid` snippet also + has the comment "initializer list using `edge_info`". +- Code: there is no `edge_info` anywhere in `include/`. The aggregate is + `graph::edge_data` (graph_data.hpp), and the + initializer-list ctor takes `copyable_edge_t` (=`edge_data`). +- Fix: replace `edge_info` with `edge_data` (and use `copyable_edge_t` + where the ctor's element type is meant). This matches D3130, which already uses + `edge_data`/`vertex_data`/`neighbor_data`. Confirmed by author: `edge_info` was + renamed to `edge_data`. + +### 1.2 [HIGH] `requires copyable_edge/copyable_vertex` removed in paper but still in code *(APPLIED — code is source of truth)* +- `revision.tex` r3 states the `copyable_vertex`/`copyable_edge` constraints were + "removed from the `compressed_graph` requires clause because descriptors are + always copyable," and the `compressed_graph.hpp` snippet shows the value + ctors constrained only by `requires convertible_to, VId>`. +- Code: every value/edge ctor still carries + `requires copyable_edge>, VId, EV>` + (and `copyable_vertex<...>` for the vrng overloads) in addition to the + `convertible_to<...,VId>` clause (compressed_graph.hpp lines ~1500–1582, 1626–1652). +- **Resolution (author-confirmed):** the constraint is *kept*; the r3 note was a + misreading. `copyable_edge` is `convertible_to>` + (graph_data.hpp) — it validates that the **`EProj`/`VProj` projection result** is + convertible to the `{source_id,target_id,value}` / `{id,value}` aggregate the ctor + expects. It is a type check on the projected value, unrelated to descriptor + copyability, so descriptor copyability does not make it redundant. +- **Applied:** the full-`GV` `compressed_graph.hpp` snippet now carries the + `copyable_edge`/`copyable_vertex` requires clauses on all six value ctors (matching + the code and the `gvoid` snippet); the r3 revision bullet was rewritten from + "Remove … because descriptors are always copyable" to "Retain … to validate the + projection result type." The `\mandates` prose (already fixed in 1.4) is consistent. + +### 1.3 [HIGH] `has_edge(g)` should be `has_edges(g)` *(APPLIED)* +- Paper summary box: `has_edge(g)` is $O(1)$; `revision.tex` r1 likewise lists + `has_edge(g)`. +- Code/D3130: the CPO is `has_edges(g)` (plural) — `graph_cpo.hpp` defines + `inline constexpr ... has_edges{}`; D3130's Graph Functions table documents + `has_edges(g)`. There is no `has_edge` CPO. +- Fix: rename to `has_edges(g)` in the summary box and revision history. + +### 1.4 [MED] `copyable_edge` is wrong arity in `\mandates` *(APPLIED)* +- Paper `\mandates`: "EProj … must return a value of `copyable_edge` + type"; "VProj … must return a value of `copyable_vertex` type." +- Code: the concept is `copyable_edge` (3 params; `T` is the projected + value), and the *type alias* the projection must produce is + `copyable_edge_t` (= `edge_data`). There is no + `true` template argument on `copyable_edge`. Likewise `copyable_vertex_t`. +- Fix: phrase as "must return a `copyable_edge_t` (resp. + `copyable_vertex_t`)," or reference the concept as + `copyable_edge`. Drop the stray `true`. + +### 1.5 [MED] Edgelist namespace is `edge_list`, not `edgelist` *(APPLIED)* +- Paper: "Type alias are in the namespace `std::graph::edgelist` to avoid + conflicts with adjacency_list types." +- Code/D3130: the namespace is `graph::edge_list` (underscore) — see + `edge_list/edge_list.hpp` (`namespace edge_list`). `edgelist` (no underscore) + is only the *view* (`views::edgelist`), a different thing. +- Fix: `std::graph::edge_list`. + +### 1.6 [MED] Edgelist examples use 1-arg CPOs; the convention is 2-arg `f(el, uv)` *(APPLIED)* +- Paper edgelist examples: + `int sid = source_id(e); int tid = target_id(e); double val = edge_value(e);` +- Code/D3130: edgelist CPOs use the 2-argument form `source_id(el, uv)`, + `target_id(el, uv)`, `edge_value(el, uv)` (mirroring the adjacency-list + convention; "All edgelist CPOs require both the container and the element for + container-specific dispatch," D3130 Edgelist Concepts). +- Fix: `source_id(el, e)`, `target_id(el, e)`, `edge_value(el, e)` in both + edgelist examples. + +### 1.7 [MED] `vertices(g,u,pid)` in the override table should be `vertices(g,pid)` *(APPLIED)* +- Paper Table (Common CPO Function Overrides), partitions group: lists + `vertices(g,u,pid)`. +- Code/D3130: the partition-filtered overload is `vertices(g, pid)` (2-arg) — the + `compressed_graph` friend is `vertices(G&& g, const PId& pid)`. There is no + `vertices(g,u,pid)`. +- Fix: `vertices(g,pid)`. + +### 1.8 [MED] `partition_id(g,uid)` — code only provides the descriptor form *(APPLIED)* +- Paper summary box: `partition_id(g,uid)` is $O(\log(P+1))$. +- Code: the friend/CPO is `partition_id(g, u)` taking a **vertex descriptor** + (compressed_graph.hpp line 1383), complexity $O(\log P)$ via + `upper_bound(partition_.begin(), partition_.end()-1, vid)`. D3130 already notes + the `partition_id(g,uid)` id-form is not yet implemented. +- Fix: use `partition_id(g,u)`. The $\log(P{+}1)$ vs $\log P$ footnote is + defensible (the `end()-1` search range is P elements); align the wording to the + code if you want exactness. + +--- + +## 2. Internal contradictions / correctness + +### 2.1 [MED] Partition preconditions: "undefined behavior" vs code throws *(APPLIED)* +- Paper `\preconditions`: "the first element must be 0 and all elements are in + ascending order… A vertex id … must not exceed the number of vertices… Any + violation of these conditions results in undefined behavior." +- Code (`terminate_partitions`, lines 896–921): violations are **defined** — + `throw graph_error(...)` when `partition_[0] != 0`, when not strictly + increasing (`partition_[i] <= partition_[i-1]`), or when + `partition_.back() > row_index_.size()`. +- Fix: replace "undefined behavior" with "throws `graph_error`," and state + "strictly increasing" rather than "ascending." + +### 2.2 [MED] `\phil` note on duplicate partition ids contradicts the code *(APPLIED — note removed)* +- Paper `\phil{If duplicate partition_start_ids exist they create an empty + partition with no vertices.}` +- Code: duplicates are rejected — `partition_[i] <= partition_[i-1]` throws + `graph_error` ("must be in strictly increasing order"). Duplicates do **not** + create empty partitions. +- Fix: drop/revise the note; empty partitions would require a different (gap) + encoding than the current strict-increasing rule allows. + +### 2.3 [MED] `edge_value(g,uv)` row in `tab:simple_graph` contradicts the prose *(APPLIED)* +- Paper prose (std-container adjacency example): "The `edge_value` function is + **not** defined, as it is assumed that algorithms will take a lambda to extract + the value." +- Same section, Table `tab:simple_graph`: row `edge_value(g,uv)` → + `tuple`. +- Also, per D3130 the default `edge_value`/`inner_value` for a 2-tuple + `tuple` returns `get<1>` (the `double`), not the whole tuple. +- Fix: either remove the `edge_value(g,uv)` row (consistent with "not defined"), + or correct its type. As written it is both self-contradictory and not what the + default CPO yields. + +### 2.4 [LOW] `tab:simple_graph` rows `u` and `uv` reflect the pre-descriptor model *(APPLIED)* +- Paper: `u` → `forward_list>`, `uv` → `tuple`. +- Under the r3 boost-like descriptor design (D3130), `vertices(g)` yields a + `vertex_descriptor` (not the inner range) and `edges(g,u)` yields + `edge_descriptor`s; the raw `forward_list`/`tuple` are the *underlying* values + reached via the descriptor, not `u`/`uv` themselves. +- Fix: relabel as the underlying/inner value (e.g. "underlying value of `u`"), + or note these are the underlying container elements, to stay consistent with + the descriptor abstraction used elsewhere in this proposal. + +--- + +## 3. Snippet fidelity (`src/*.hpp` exposition listings) + +### 3.1 [MED] `compressed_graph_gvoid.hpp` has a malformed class header *(APPLIED)* +- Snippet: + ``` + template > + template + class compressed_graph + public: // Construction/Destruction + ``` + Two stacked `template` headers, no `: public compressed_graph_base<...>`, and a + missing `{` before `public:`. +- Code: a single specialization header + `template + class compressed_graph + : public compressed_graph_base {`. +- Fix: drop the first (defaulted) `template` line, add the base-class clause and + the opening brace so the exposition compiles in a reader's head. + +### 3.2 [LOW] `gvoid` ctor prototypes drop the trailing `;` *(APPLIED)* +- The edge-only and edge+vertex ctor prototypes in `compressed_graph_gvoid.hpp` + end with `)` and no `;` (unlike the full `compressed_graph.hpp` listing, whose + prototypes are terminated with `;`). Add `;` for consistency. + +### 3.3 [LOW] `gvoid` init-list comment uses the stale `edge_info` *(APPLIED)* +- Comment "initializer list using `edge_info`." Code comment + reads `edge_data`. Update to `edge_data` / + `copyable_edge_t` (see 1.1). + +### 3.4 [LOW] Adjacency example code has small slips *(APPLIED)* +- `auto weight = [&g](edge_t& uv) { return get<1>(uv); }` — missing `;`, `edge_t` + should be `edge_t`, and the lambda captures `&g` before `g` is declared + (declare `G g;` first, or capture nothing and pass the graph). Also + `for(auto&& [u] : vertices(g))` decomposes a descriptor as a 1-tuple; with the + descriptor model this should be `for(auto&& u : vertices(g))` (or iterate the + `vertexlist` view to get `[uid, u]`). Tighten so the example compiles. + +--- + +## 4. Confirmed consistent (spot-checked, no action) +- Full-`GV` constructor set in `compressed_graph.hpp` snippet matches the code: + `(alloc)`, `(gv&, alloc)`, `(gv&&, alloc)`; the erng+eproj trio; the + erng+vrng+eproj+vproj trio; and the `initializer_list>` + ctor — all present with the same shapes and `partition_start_ids` defaults. +- Template parameter order/defaults `>` match the code's primary template. +- `compressed_graph` is CSR, immutable structure after construction, values on + vertex/edge/graph mutable — matches (`csr_row_values`/`csr_col_values`, + `graph_value()` accessor; no public add/remove vertex/edge). +- `void` EV/VV/GV → no storage overhead: matches the empty specializations of + `csr_row_values`/`csr_col_values` and the `GV=void` class specialization. +- Summary box: vertex_id contiguous, contiguous vertices/edges ranges, + `num_edges(g)` $O(1)$, `has_edges(g)` $O(1)$, `num_partitions` $O(1)$, + Append vertices/edges = No, Partitions = Yes — all consistent with the code. +- `is_directed` not supported for `compressed_graph` (no specialization in + `compressed_graph.hpp`); undirected use requires inserting both `(u,v)` and + `(v,u)` — matches. +- Memory-size formula + $|V|(sizeof(EIndex)+sizeof(VV)) + |E|(sizeof(VId)+sizeof(EV)) + sizeof(GV)$ is + consistent with the CSR layout (`row_index_`+row values sized to |V|, `col_index_` + + col values sized to |E|). +- Std-container adjacency patterns `random_access_range>` + and `random_access_range>>` with the integral + as target_id — matches D3130's recognized patterns. The "no overrides required" + claim is correct. + +--- + +## 5. Open questions (scope / intent — need author input) + +- **Q1 (duplication with D3130):** D3130 ("Using Existing Data Structures") and + D3131 ("Using Existing Data Structures") both describe std-container adjacency + lists and edgelists. `revision.tex` r0 says this text was *moved from* the GCI + to this paper, yet D3130 retains an extensive "Recognized Vertex/Edge Patterns" + treatment. Which paper owns the canonical description, and how much should be + cross-referenced vs. duplicated? + - **RESOLVED & APPLIED:** D3130 keeps the *normative recognition patterns* (the + structural trigger: random-access / associative vertex patterns and the + integral/`pair`/`tuple` edge element patterns) and points to D3131 for the + catalog. D3131 received the concrete container example lists, the + `tab:edge_container_patterns` trade-off table, and the combined examples. + Both revision histories updated. + +- **Q2 (`operator[]`):** `\phil{Add operator[](vertex_id_t)?}` — the internal + `csr_row_values`/`csr_col_values` expose `operator[]`, but the public + `compressed_graph` does not. Is a public `operator[]` intended for this paper, + or deferred? (Recommend resolving the `\phil` note before publication.) + - **RESOLVED & APPLIED — not added.** `operator[]` would only expose an internal + CSR element that isn't useful on its own, and it would breach the + CPO-only public surface. The `\phil` note was replaced with a short rationale + pointing to `vertex_value(g,uid)` and `out_edges(g,*find_vertex(g,uid))` as the + generic equivalents. + +- **Q3 (movable EV/VV):** `\phil{Is it possible to support movable EV and VV + types?}` — `\mandates` currently requires `EV`/`VV` to be *copyable*. Is this a + hard requirement to keep, or a future relaxation to note? + - **RESOLVED & APPLIED — copyable kept; movable noted as future work.** The + `\phil` note was replaced with a rationale: move-only `EV`/`VV` would disable + the `initializer_list` ctor (const elements), restrict construction to the + rvalue-range overloads, make the graph move-only (deleted copy ctor), and need + a GCI-level relaxation of `copyable_edge`/`copyable_vertex`. `GV` already may be + movable. + +- **Q4 (more examples):** The request asks that std-container adjacency lists be + *thoroughly* described with examples. Currently there are two adjacency + examples (`vector>>` and + `vector>`) and two edgelist examples. Should an + associative/mapped example (e.g. `map>>`) be added + here, or is that owned by D3130's pattern tables? (Ties to Q1.) + - **RESOLVED & APPLIED — added here.** A worked `map>>` + associative example now follows the catalog's associative bullet list: same GCI + loop shape, vertex id is the map key (`vertex_id(g,u)`), `pair.first` is the + target id, `pair.second` is the edge value. A note explains it models + `mapped_adjacency_list` but not `index_adjacency_list` (so `find_vertex` is + `O(log|V|)` / amortized `O(1)`), with the normative concept details left to D3130. + +- **Q5 (undirected mutable-edge example):** `\phil{Add example(s) … mutable edge + values for an undirected graph with duplicate (u,v)/(v,u) edges.}` — open TODO; + confirm whether it belongs in this paper. + - **RESOLVED & APPLIED — note removed.** Per author: undirected graphs need more + attention but a different approach will be used, so the placeholder example is + not pursued here. The `\phil` TODO was removed from the paper.