@@ -114,47 +114,65 @@ struct to_impl;
114114// / ### Key Features
115115// / - **Policy-based design**: Behavior and representation are determined by `GraphTraits`.
116116// / - **Flexible directionality**: Support for both directed and undirected graphs.
117- // / - **Multiple implementations**: Choose between adjacency list, flattened adjacency list, adjacency matrix, or flattened adjacency matrix representations.
117+ // / - **Multiple representations**: Choose the underlying memory model that best suits your algorithmic and cache-locality needs:
118+ // / - @ref gl::impl::list_t "list_t": Standard adjacency list.
119+ // / - @ref gl::impl::flat_list_t "flat_list_t": Flattened adjacency list.
120+ // / - @ref gl::impl::matrix_t "matrix_t": Standard adjacency matrix.
121+ // / - @ref gl::impl::flat_matrix_t "flat_matrix_t": Flattened adjacency matrix.
118122// / - **Property support**: Vertices and edges can carry arbitrary properties.
119123// / - **Unified API**: Consistent interface regardless of the underlying implementation.
120124// /
121- // / ### Mathematical Definitions
125+ // / ### Basic Definitions
122126// / A graph \f$G = (V, E)\f$ consists of a set of vertices \f$V\f$ and a set of edges \f$E\f$.
123- // / - For undirected graphs, edges are unordered pairs \f$\{u, v\}\f$.
124- // / - For directed graphs, edges are ordered pairs \f$(u, v)\f$.
127+ // /
128+ // / - For undirected graphs, edges are unordered pairs \f$\{u, v\}\f$ where \f$u, v \in V\f$.
129+ // / - For directed graphs, edges are ordered pairs \f$(u, v)\f$ where \f$u, v \in V\f$.
125130// /
126131// / ### Example Usage
127132// / ```cpp
128133// / #include <gl/graph.hpp>
134+ // /
129135// / #include <iostream>
130136// /
131137// / int main() {
132- // / // Create a directed graph with no properties
133- // / gl::directed_graph<> g;
138+ // / gl::directed_graph<> g; // (1)!
134139// /
135- // / // Add vertices
136- // / auto v0 = g.add_vertex();
140+ // / auto v0 = g.add_vertex(); // (2)!
137141// / auto v1 = g.add_vertex();
138142// / auto v2 = g.add_vertex();
139143// /
140- // / // Add edges
141- // / auto e01 = g.add_edge(v0, v1);
144+ // / auto e01 = g.add_edge(v0, v1); // (3)!
142145// / auto e12 = g.add_edge(v1, v2);
143146// / auto e20 = g.add_edge(v2, v0);
144147// /
145- // / // Query graph properties
148+ // / // (4)!
146149// / std::cout << "Vertices: " << g.n_vertices() << '\n';
147150// / std::cout << "Edges: " << g.n_edges() << '\n';
148151// /
149- // / // Iterate over neighbors
150- // / for (auto neighbor : g.neighbors(v0.id())) {
151- // / std::cout << "Neighbor of v0: " << neighbor.id() << '\n';
152- // / }
152+ // / for (auto neighbor : g.neighbors(v0)) // (5)!
153+ // / process(neighbor);
153154// /
154155// / return 0;
155156// / }
156157// / ```
157158// /
159+ // / 1\. Create a directed graph with no properties.
160+ // /
161+ // / 2\. Add vertices to the graph. **NOTE:** This is safe because the graph has no vertex properties and the `add_vertex` operation does not invalidate IDs.
162+ // /
163+ // / 3\. Add edges to the graph. **NOTE:** This is safe because the graph has no edge properties and the `add_edge` operation does not invalidate IDs.
164+ // /
165+ // / 4\. Query the graph's properties.
166+ // /
167+ // / 5\. Iterate over neighbors of `v0`.
168+ // /
169+ // / ### API Design: IDs vs. Descriptors
170+ // / The graph exposes a dual API to accommodate different performance and ergonomic needs:
171+ // /
172+ // / - **Inputs**: Most query methods are overloaded to accept either a raw `id_type` or a `vertex_type`/`edge_type` descriptor. They are functionally identical.
173+ // / - **Outputs**: Methods ending in `_ids` (e.g., `neighbor_ids`) return views of raw integral IDs. Methods without this suffix (e.g., `neighbors`) automatically map those IDs to the proper descriptor objects.
174+ // / - **Performance**: Descriptor-returning methods incur a slight overhead if the graph utilizes rich properties, as the descriptor must fetch the property payload. If you only need topology, prefer the `_ids` variants.
175+ // /
158176// / ### Descriptor Invalidation Behavior
159177// /
160178// / The graph maintains the following invalidation semantics:
@@ -468,16 +486,29 @@ class graph final {
468486 return std::views::iota (initial_id_v<id_type>, this ->_n_vertices );
469487 }
470488
471- // / @brief Retrieves the neighbor vertex descriptors for a specific vertex.
489+ // / @brief Retrieves the neighbor vertex IDs for a specific vertex.
490+ // /
491+ // / ### Formal Definition
492+ // / The neighborhood \f$N(v)\f$ of a vertex \f$v\f$ is the set of all its adjacent vertices:
493+ // /
494+ // / \f$
495+ // / N(v) =
496+ // / \begin{cases}
497+ // / \{u \in V : \{u, v\} \in E\} & \text{if } G \text{ is undirected}
498+ // / \\\\ \{u \in V : (u, v) \in E(G) \lor (v, u) \in E(G)\} & \text{if } G \text{ is directed}
499+ // / \end{cases}
500+ // / \f$
501+ // /
472502 // / @param vertex_id The ID of the source vertex.
473- // / @return A view of all adjacent vertex descriptors .
503+ // / @return A view of all adjacent vertex IDs .
474504 // / @throws std::out_of_range If the vertex ID is invalid.
475505 [[nodiscard]] gl_attr_force_inline auto neighbors (const id_type vertex_id) const {
476506 return this ->neighbor_ids (vertex_id)
477507 | std::views::transform (this ->_create_vertex_descriptor ());
478508 }
479509
480510 // / @brief Retrieves the neighbor vertex descriptors for a specific vertex.
511+ // / @copydetails neighbors(const id_type) const
481512 // / @param vertex The source vertex descriptor.
482513 // / @return A view of all adjacent vertex descriptors.
483514 // / @throws std::out_of_range If the vertex descriptor is invalid.
@@ -502,9 +533,21 @@ class graph final {
502533 return this ->neighbor_ids (vertex.id ());
503534 }
504535
505- // / @brief Retrieves the predecessor vertex descriptors (incoming edges) for a vertex.
536+ // / @brief Retrieves the predecessor vertex IDs for a vertex.
537+ // /
538+ // / ### Formal Definition
539+ // / The set of predecessors (in-neighborhood) \f$N_{in}(v)\f$ is defined as:
540+ // /
541+ // / \f$
542+ // / N_{in}(v) =
543+ // / \begin{cases}
544+ // / N(v) & \text{if } G \text{ is undirected}
545+ // / \\\\ \{u \in V(G) : (u, v) \in E(G)\} & \text{if } G \text{ is directed}
546+ // / \end{cases}
547+ // / \f$
548+ // /
506549 // / @param vertex_id The ID of the target vertex.
507- // / @return A view of all predecessor vertex descriptors .
550+ // / @return A view of all predecessor vertex IDs .
508551 // / @throws std::out_of_range If the vertex ID is invalid.
509552 [[nodiscard]] gl_attr_force_inline auto predecessors (const id_type vertex_id) const {
510553 return this ->predecessor_ids (vertex_id)
@@ -536,9 +579,21 @@ class graph final {
536579 return this ->predecessor_ids (vertex.id ());
537580 }
538581
539- // / @brief Retrieves the successor vertex descriptors (outgoing edges) for a vertex.
582+ // / @brief Retrieves the successor vertex IDs for a vertex.
583+ // /
584+ // / ### Formal Definition
585+ // / The set of successors (out-neighborhood) \f$N_{out}(v)\f$ is defined as:
586+ // /
587+ // / \f$
588+ // / N_{out}(v) =
589+ // / \begin{cases}
590+ // / N(v) & \text{if } G \text{ is undirected}
591+ // / \\\\ \{u \in V(G) : (v, u) \in E(G)\} & \text{if } G \text{ is directed}
592+ // / \end{cases}
593+ // / \f$
594+ // /
540595 // / @param vertex_id The ID of the source vertex.
541- // / @return A view of all successor vertex descriptors .
596+ // / @return A view of all successor vertex IDs .
542597 // / @throws std::out_of_range If the vertex ID is invalid.
543598 [[nodiscard]] gl_attr_force_inline auto successors (const id_type vertex_id) const {
544599 return this ->successor_ids (vertex_id)
@@ -631,6 +686,19 @@ class graph final {
631686 }
632687
633688 // / @brief Calculates the in-degree (incoming edges) for a vertex.
689+ // /
690+ // / The in-degree is the number of edges directed into the vertex.
691+ // /
692+ // / ### Formal Definition
693+ // /
694+ // / \f$
695+ // / deg_{in}(v) =
696+ // / \begin{cases}
697+ // / deg(v) & \text{if } G \text{ is undirected}
698+ // / \\\\ |E_{in}(v)| = |\{u \in V : (u, v) \in E\}| & \text{if } G \text{ is directed}
699+ // / \end{cases}
700+ // / \f$
701+ // /
634702 // / @param vertex_id The ID of the vertex.
635703 // / @return The in-degree.
636704 // / @throws std::out_of_range If the vertex ID is invalid.
@@ -654,6 +722,19 @@ class graph final {
654722 }
655723
656724 // / @brief Calculates the out-degree (outgoing edges) for a vertex.
725+ // /
726+ // / The out-degree is the number of edges directed out of the vertex.
727+ // /
728+ // / ### Formal Definition
729+ // /
730+ // / \f$
731+ // / deg_{out}(v) =
732+ // / \begin{cases}
733+ // / deg(v) & \text{if } G \text{ is undirected}
734+ // / \\\\ |E_{out}(v)| = |\{u \in V : (v, u) \in E\}| & \text{if } G \text{ is directed}
735+ // / \end{cases}
736+ // / \f$
737+ // /
657738 // / @param vertex_id The ID of the vertex.
658739 // / @return The out-degree.
659740 // / @throws std::out_of_range If the vertex ID is invalid.
@@ -823,6 +904,8 @@ class graph final {
823904 // / > - References to edge properties obtained from `edge_properties_map()`.
824905 // / >
825906 // / > Vertex descriptors and IDs remain valid.
907+ // /
908+ // / @throws std::invalid_argument If the edge descriptor is invalid;
826909 void remove_edge (const edge_type& edge) {
827910 this ->_verify_edge (edge);
828911 if constexpr (traits::c_non_empty_properties<edge_properties_type>)
@@ -842,6 +925,11 @@ class graph final {
842925 // / > - References to edge properties obtained from `edge_properties_map()`.
843926 // / >
844927 // / > Vertex descriptors and IDs remain valid.
928+ // /
929+ // / > [!NOTE] Operation Safety
930+ // / >
931+ // / > If the edges list is empty or contains no valid (in the context of the graph instance),
932+ // / > the operation has no effect on the graph's structure.
845933 void remove_edges (const traits::c_range_of<edge_type> auto & edges) {
846934 const auto removed_edge_ids = this ->_impl .remove_edges (edges);
847935 this ->_n_edges -= removed_edge_ids.size ();
@@ -1030,7 +1118,14 @@ class graph final {
10301118 // /
10311119 // / This method checks for a connection between the given vertices in either direction:
10321120 // / - For undirected graphs this is equivalent to `has_edge(first_id, second_id)`
1033- // / - For directed graphs the result is true if \f$ TODO\f$
1121+ // / - For directed graphs the result is true if either `has_edge(first_id, second_id)` or
1122+ // / `has_edge(second_id, first_id)` is true.
1123+ // /
1124+ // / ### Formal definition
1125+ // / Vertices $u$ and $v$ are adjacent if there exists an edge connecting them in the graph:
1126+ // /
1127+ // / - For undirected graphs: $\{u, v\} in E$
1128+ // / - For directed graphs: $(u, v) \in E \lor (v, u) \in E$
10341129 // /
10351130 // / @param first_id The ID of the first vertex.
10361131 // / @param second_id The ID of the second vertex.
@@ -1054,6 +1149,10 @@ class graph final {
10541149 }
10551150
10561151 // / @brief Checks if two distinct edges share at least one incident vertex.
1152+ // /
1153+ // / ### Formal definition
1154+ // / Edges $e$ and $f$ are adjacent if they share at least one endpoint: \f$e \cap f \ne \emptyset\f$.
1155+ // /
10571156 // / @param edge_1 The first edge descriptor.
10581157 // / @param edge_2 The second edge descriptor.
10591158 // / @return `true` if they are adjacent (share a vertex), `false` otherwise.
@@ -1067,19 +1166,31 @@ class graph final {
10671166 }
10681167
10691168 // / @brief Checks if a vertex forms one of the endpoints of an edge.
1169+ // /
1170+ // / ### Formal Definition
1171+ // / A vertex \f$v\f$ is incident to an edge \f$e\f$ if \f$v\f$ is an element of the endpoint set of \f$e\f$ (\f$v \in e\f$).
1172+ // / Assuming \f$e\f$ connects vertices \f$u\f$ and \f$w\f$: \f$v = u \lor v = w\f$
1173+ // /
10701174 // / @param vertex The vertex descriptor.
10711175 // / @param edge The edge descriptor.
10721176 // / @return `true` if the vertex is incident to the edge, `false` otherwise.
1177+ // / @throws std::out_of_range If the vertex descriptor is invalid.
1178+ // / @throws std::invalid_argument If the edge descriptor is invalid.
10731179 [[nodiscard]] bool are_incident (vertex_type vertex, const edge_type& edge) const {
10741180 this ->_verify_vertex_id (vertex.id ());
10751181 this ->_verify_edge (edge);
10761182 return edge.is_incident_with (vertex.id ());
10771183 }
10781184
10791185 // / @brief Checks if a vertex forms one of the endpoints of an edge.
1186+ // /
1187+ // / A convenience overload of the `are_adjacent` method. It is equivalent to `are_incident(vertex, edge)`
1188+ // /
10801189 // / @param edge The edge descriptor.
10811190 // / @param vertex The vertex descriptor.
10821191 // / @return `true` if the vertex is incident to the edge, `false` otherwise.
1192+ // / @throws std::out_of_range If the vertex descriptor is invalid.
1193+ // / @throws std::invalid_argument If the edge descriptor is invalid.
10831194 [[nodiscard]] gl_attr_force_inline bool are_incident (const edge_type& edge, vertex_type vertex)
10841195 const {
10851196 return this ->are_incident (vertex, edge);
0 commit comments