Skip to content

Commit e9d438c

Browse files
committed
graph docs
1 parent cec9b39 commit e9d438c

2 files changed

Lines changed: 137 additions & 22 deletions

File tree

include/gl/graph.hpp

Lines changed: 133 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -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);

include/hgl/hypergraph.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ template <typename H>
5656
concept c_matrix_hypergraph =
5757
c_hypergraph<H> and c_hypergraph_matrix_impl<typename H::implementation_tag>;
5858

59+
template <typename H>
60+
concept c_flat_matrix_hypergraph =
61+
c_hypergraph<H> and c_hypergraph_flat_matrix_impl<typename H::implementation_tag>;
62+
5963
template <typename H>
6064
concept c_incidence_matrix_hypergraph =
6165
c_hypergraph<H> and c_hypergraph_incidence_matrix_impl<typename H::implementation_tag>;

0 commit comments

Comments
 (0)