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
10 changes: 10 additions & 0 deletions D3130_Container_Interface/src/concepts_adj_list.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,13 @@ template <class G>
concept mapped_bidirectional_adjacency_list =
bidirectional_adjacency_list<G> &&
mapped_vertex_range<G>;

// 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 <class G>
concept ordered_vertex_edges =
adjacency_list<G> &&
requires(G& g, vertex_t<G> u) {
requires forward_iterator<decltype(begin(out_edges(g, u)))>;
};
156 changes: 69 additions & 87 deletions D3130_Container_Interface/tex/container_interface.tex
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,11 @@ \subsubsection{Adjacency List Concepts}
\item \tcode{mapped_bidirectional_adjacency_list<G>} — \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<G>} — 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.
Expand Down Expand Up @@ -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<T> yields T (value\_type); unordered\_map<K,V> yields V (mapped\_type)
template <class Container>
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 <class M, class G>
concept vertex_property_map_for =
requires(M& m, const vertex_id_t<G>& uid) {
{ m[uid] } -> convertible_to<vertex_property_map_value_t<M>>;
};
\end{lstlisting}

\tcode{vertex_property_map_value_t<Container>} 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<M,G>} 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<G>}, since algorithms subscript such maps by vertex id rather than
iterating them as a range. Both \tcode{vector<T>} (index graphs) and
\tcode{unordered_map<vertex_id_t<G>,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.
Expand Down Expand Up @@ -430,8 +462,8 @@ \subsection{Functions}
\tcode{in_degree(g,u)} & \tcode{integral} & constant & \tcode{size(in\_edges(g,u))} if \tcode{sized_range<in_edge_range_t<G>>} \\
\tcode{in_degree(g,uid)} & \tcode{integral} & constant & \tcode{size(in\_edges(g,uid))} if \tcode{sized_range<in_edge_range_t<G>>} \\
\hdashline
\tcode{partition_id(g,u)} & \tcode{partition_id_t<G>} & constant & \\
\tcode{partition_id(g,uid)} & \tcode{partition_id_t<G>} & constant & \\
\tcode{partition_id(g,u)} & \tcode{partition_id_t<G>} & constant & \tcode{0} (partition 0) \\
\tcode{partition_id(g,uid)} & \tcode{partition_id_t<G>} & constant & \tcode{partition_id(g,*find_vertex(g,uid))} \\
\hline
\hline
\end{tabular}}
Expand All @@ -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}{!}
Expand Down Expand Up @@ -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<size_t,size_t>}
(a random-access iterator that yields indices by value, with no backing element), and
\tcode{index_vertex_descriptor} is \tcode{vertex_descriptor<index_iterator>}. 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
Expand Down Expand Up @@ -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}
Expand All @@ -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<vector<int>>} --- vertices in a \tcode{vector}, edges as \tcode{int} target ids.
\item \tcode{vector<list<pair<int,double>>>} --- vertices in a \tcode{vector}, edges as weighted pairs in a \tcode{list}.
\item \tcode{deque<forward_list<int>>} --- 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
Expand All @@ -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<int, vector<int>>} --- sparse vertex ids in a sorted \tcode{map}, edges as target ids.
\item \tcode{unordered_map<int, vector<pair<int,double>>>} --- 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<E>} &
Random-access, cache-friendly, amortized $\mathcal{O}(1)$ push-back. \\
&
\tcode{deque<E>} &
Random-access, stable references on push-back. \\
&
\tcode{list<E>} &
Bidirectional, $\mathcal{O}(1)$ splice, stable iterators. \\
&
\tcode{forward_list<E>} &
Forward-only, minimal overhead, $\mathcal{O}(1)$ push-front. \\
\hdashline
Ordered associative &
\tcode{set<E>} &
Sorted, automatic deduplication, $\mathcal{O}(\log n)$ lookup; element is the edge itself. \\
&
\tcode{map<VId, EV>} &
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<E>} &
Hash-based deduplication, amortized $\mathcal{O}(1)$ insert/lookup. \\
&
\tcode{unordered_map<VId, EV>} &
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<const Key, Mapped>}
(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<const Key, Mapped>}
(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
Expand Down Expand Up @@ -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<vector<pair<int,double>>>} is a random-access graph with weighted edges,
\tcode{vector<set<int>>} is a random-access graph with sorted deduplicated unweighted edges,
\tcode{vector<map<int,double>>} is a random-access graph with $\mathcal{O}(\log n)$ edge lookup by target,
and \tcode{map<string, list<int>>} 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.
4 changes: 4 additions & 0 deletions D3130_Container_Interface/tex/revision.tex
Original file line number Diff line number Diff line change
Expand Up @@ -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<EL>} 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}
21 changes: 15 additions & 6 deletions D3131_Containers/src/compressed_graph.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,25 @@ class compressed_graph {
// compressed\_graph(gv\&\&, erng, eprojection, alloc)

template <forward_range ERng, forward_range PartRng, class EProj = identity>
requires convertible_to<range_value_t<PartRng>, VId>
requires copyable_edge<invoke_result_t<EProj, range_value_t<ERng>>, VId, EV> &&
convertible_to<range_value_t<PartRng>, VId>
constexpr compressed_graph(const ERng& erng,
EProj eprojection,
const PartRng& partition_start_ids = vector<VId>(),
const Alloc& alloc = Alloc());

template <forward_range ERng, forward_range PartRng, class EProj = identity>
requires convertible_to<range_value_t<PartRng>, VId>
requires copyable_edge<invoke_result_t<EProj, range_value_t<ERng>>, VId, EV> &&
convertible_to<range_value_t<PartRng>, VId>
constexpr compressed_graph(const graph_value_type& value,
const ERng& erng,
EProj eprojection,
const PartRng& partition_start_ids = vector<VId>(),
const Alloc& alloc = Alloc());

template <forward_range ERng, forward_range PartRng, class EProj = identity>
requires convertible_to<range_value_t<PartRng>, VId>
requires copyable_edge<invoke_result_t<EProj, range_value_t<ERng>>, VId, EV> &&
convertible_to<range_value_t<PartRng>, VId>
constexpr compressed_graph(graph_value_type&& value,
const ERng& erng,
EProj eprojection,
Expand All @@ -58,7 +61,9 @@ class compressed_graph {
forward_range PartRng,
class EProj = identity,
class VProj = identity>
requires convertible_to<range_value_t<PartRng>, VId>
requires copyable_edge<invoke_result_t<EProj, range_value_t<ERng>>, VId, EV> &&
copyable_vertex<invoke_result_t<VProj, range_value_t<VRng>>, VId, VV> &&
convertible_to<range_value_t<PartRng>, VId>
constexpr compressed_graph(const ERng& erng,
const VRng& vrng,
EProj eprojection = {},
Expand All @@ -71,7 +76,9 @@ class compressed_graph {
forward_range PartRng,
class EProj = identity,
class VProj = identity>
requires convertible_to<range_value_t<PartRng>, VId>
requires copyable_edge<invoke_result_t<EProj, range_value_t<ERng>>, VId, EV> &&
copyable_vertex<invoke_result_t<VProj, range_value_t<VRng>>, VId, VV> &&
convertible_to<range_value_t<PartRng>, VId>
constexpr compressed_graph(const graph_value_type& value,
const ERng& erng,
const VRng& vrng,
Expand All @@ -85,7 +92,9 @@ class compressed_graph {
forward_range PartRng,
class EProj = identity,
class VProj = identity>
requires convertible_to<range_value_t<PartRng>, VId>
requires copyable_edge<invoke_result_t<EProj, range_value_t<ERng>>, VId, EV> &&
copyable_vertex<invoke_result_t<VProj, range_value_t<VRng>>, VId, VV> &&
convertible_to<range_value_t<PartRng>, VId>
constexpr compressed_graph(graph_value_type&& value,
const ERng& erng,
const VRng& vrng,
Expand Down
19 changes: 9 additions & 10 deletions D3131_Containers/src/compressed_graph_gvoid.hpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
template <class EV,
class VV,
integral VId=uint32_t,
integral EIndex=uint32_t,
class Alloc=allocator<VId>>
template <class EV, class VV, integral VId, integral EIndex, class Alloc>
class compressed_graph<EV, VV, void, VId, EIndex, Alloc>
: public compressed_graph_base<EV, VV, void, VId, EIndex, Alloc> {
public: // Construction/Destruction
constexpr compressed_graph() = default;
constexpr compressed_graph(const compressed_graph&) = default;
Expand All @@ -22,22 +18,25 @@ class compressed_graph<EV, VV, void, VId, EIndex, Alloc>
constexpr compressed_graph(const ERng& erng,
EProj eprojection = identity(),
const PartRng& partition_start_ids = vector<VId>(),
const Alloc& alloc = Alloc())
const Alloc& alloc = Alloc());

// edge and vertex value construction
template <forward_range ERng,
forward_range VRng,
forward_range PartRng,
class EProj = identity,
class VProj = identity>
class EProj = identity,
class VProj = identity,
forward_range PartRng = vector<VId>>
requires copyable_edge<invoke_result_t<EProj, range_value_t<ERng>>, VId, EV> &&
copyable_vertex<invoke_result_t<VProj, range_value_t<VRng>>, VId, VV> &&
convertible_to<range_value_t<PartRng>, VId>
constexpr compressed_graph(const ERng& erng,
const VRng& vrng,
EProj eprojection = {},
VProj vprojection = {},
const PartRng& partition_start_ids = vector<VId>(),
const Alloc& alloc = Alloc());

// initializer list using edge\_info<VId,true,void,EV>
// initializer list using edge\_data<VId,true,void,EV>
constexpr compressed_graph(const initializer_list<copyable_edge_t<VId, EV>>& ilist,
const Alloc& alloc = Alloc());
};
Loading
Loading