Skip to content

Commit 0c08429

Browse files
committed
alg traversal doc
1 parent 9de28ca commit 0c08429

8 files changed

Lines changed: 319 additions & 177 deletions

File tree

docs/gl/algorithms/pathfinding.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,8 @@ std::cout << "Path: "
6666
> [!INFO] Algorithmic Complexity
6767
>
6868
> The time complexity depends heavily on the underlying representation of GraphType and the priority queue overhead:
69-
> - **Adjacency List Representations:** $\mathcal{O}((|V| + |E|) \log |V|)$. The queue operations scale with the number of edges, making this optimal for sparse graphs.
70-
> - **Adjacency Matrix Representations:** $\mathcal{O}(|V|^2 + |E| \log |V|)$. Iterating over adjacent vertices requires scanning the entire $|V|$-length matrix row, shifting the bottleneck to row traversal.
69+
> - **Adjacency List Representations:** $O((|V| + |E|) \log |V|)$. The queue operations scale with the number of edges, making this optimal for sparse graphs.
70+
> - **Adjacency Matrix Representations:** $O(|V|^2 + |E| \log |V|)$. Iterating over adjacent vertices requires scanning the entire $|V|$-length matrix row, shifting the bottleneck to row traversal.
7171
7272
---
7373

docs/gl/algorithms/topological_algs.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@ else {
4848
> [!INFO] Algorithmic Complexity
4949
>
5050
> The time complexity depends on the underlying representation of `GraphType`:
51-
> - **Adjacency List Representations:** $\mathcal{O}(|V| + |E|)$.
52-
> - **Adjacency Matrix Representations:** $\mathcal{O}(|V|^2)$. Iterating over adjacent vertices requires scanning the entire $|V|$-length matrix row.
51+
> - **Adjacency List Representations:** $O(|V| + |E|)$.
52+
> - **Adjacency Matrix Representations:** $O(|V|^2)$. Iterating over adjacent vertices requires scanning the entire $|V|$-length matrix row.
5353
5454
---
5555

@@ -87,5 +87,5 @@ else {
8787
> [!INFO] Algorithmic Complexity
8888
>
8989
> The time complexity depends on the underlying representation of `GraphType`:
90-
> - **Adjacency List Representations:** $\mathcal{O}(|V| + |E|)$.
91-
> - **Adjacency Matrix Representations:** $\mathcal{O}(|V|^2)$. Iterating over adjacent vertices requires scanning the entire $|V|$-length matrix row.
90+
> - **Adjacency List Representations:** $O(|V| + |E|)$.
91+
> - **Adjacency Matrix Representations:** $O(|V|^2)$. Iterating over adjacent vertices requires scanning the entire $|V|$-length matrix row.

docs/gl/algorithms/traversal.md

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,6 @@ gl::algorithm::breadth_first_search<gl::algorithm::noret>( // (2)!
5151
3. Traverses all vertices in the graph, automatically jumping to new roots if disconnected components are found.
5252
4. A custom PreVisitCallback executed exactly when a vertex is marked as visited.
5353
54-
<!-- TODO: Diagram -->
55-
5654
---
5755
5856
## Depth-First Search (DFS)
@@ -110,6 +108,6 @@ The following diagram illustrates the fundamental difference in exploration orde
110108
111109
The time complexity for all three traversals (BFS, Iterative DFS, and Recursive DFS) depends entirely on the underlying representation of `GraphType`:
112110
113-
- **Adjacency List Representations:** $\mathcal{O}(|V| + |E|)$. The algorithm strictly evaluates existing edges, making this optimal for sparse graphs.
111+
- **Adjacency List Representations:** $O(|V| + |E|)$. The algorithm strictly evaluates existing edges, making this optimal for sparse graphs.
114112
115-
- **Adjacency Matrix Representations:** $\mathcal{O}(|V|^2)$. To find adjacent unvisited vertices, the algorithm must scan the entire $|V|$-length matrix row for every visited vertex, shifting the bottleneck from edge evaluation to row traversal.
113+
- **Adjacency Matrix Representations:** $O(|V|^2)$. To find adjacent unvisited vertices, the algorithm must scan the entire $|V|$-length matrix row for every visited vertex, shifting the bottleneck from edge evaluation to row traversal.

docs/hgl/algorithms/overview.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,5 +79,5 @@ Explore the specific layers of the algorithm module below:
7979

8080
- [**The Generic Templates**](templates.md): A detailed explanation of the core traversal engines and their two-step execution sequence.
8181
- [**Standard Traversals**](traversal.md#standard-traversals): Unrestricted wrappers for basic component discovery across any hypergraph topology (`breadth_first_search`, `depth_first_search`).
82-
- [**Backward Searches**](traversal.md#backward-search): Traversals enforcing strict B-reachability semantics on BF-directed hypergraphs (`backward_bfs`, `backward_dfs`), where a hyperedge is only traversed once *all* of its tail vertices are visited.
83-
- [**Forward Searches**](traversal.md#forward-search): Traversals enforcing strict F-reachability semantics on BF-directed hypergraphs (`forward_bfs`, `forward_dfs`), where a hyperedge is only traversed once *all* of its head vertices are visited.
82+
- [**Backward Searches**](traversal.md#backward-searches-b-reachability): Traversals enforcing strict B-reachability semantics on BF-directed hypergraphs (`backward_bfs`, `backward_dfs`), where a hyperedge is only traversed once *all* of its tail vertices are visited.
83+
- [**Forward Searches**](traversal.md#forward-searches-f-reachability): Traversals enforcing strict F-reachability semantics on BF-directed hypergraphs (`forward_bfs`, `forward_dfs`), where a hyperedge is only traversed once *all* of its head vertices are visited.

docs/hgl/algorithms/traversal.md

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,121 @@
11
# Concrete Traversals
2+
3+
While the [Generic Templates](templates.md) provide immense power and flexibility, manually setting up the `visited` arrays, initialization ranges, and multi-step expansion predicates for simple tasks can be overly verbose.
4+
5+
To solve this, HGL provides concrete traversal algorithms: **Breadth-First Search (BFS)** and **Depth-First Search (DFS)**, alongside specialized directional reachability wrappers. These functions act as simple wrappers around the core generic engines. They automatically allocate and manage the necessary state-tracking containers for both vertices and hyperedges, while still allowing you to inject custom logic via `pre_visit` and `post_visit` callbacks.
6+
7+
---
8+
9+
## Shared Behaviors and Mechanics
10+
11+
All concrete traversals in the HGL module share a unified design philosophy regarding return types and component handling, mirroring the GL module.
12+
13+
### The Result Discriminator
14+
15+
By default, concrete traversals are designed to build a traversal tree. However, sometimes you only want to traverse a hypergraph for its side effects (e.g., modifying vertex properties) without paying the memory allocation cost of building a predecessor map.
16+
17+
You can explicitly control this using the template's `Result` discriminator:
18+
19+
- [**`hgl::algorithm::ret`**](../../cpp-gl/group__HGL-Algorithm.md#enum-result_discriminator) (Default): The algorithm allocates and populates an [**hgl::algorithm::search_tree<H>**](../../cpp-gl/group__HGL-Algorithm.md#typedef-search_tree). This structure records both the predecessor vertex and the specific hyperedge that was traversed to discover each vertex.
20+
- [**`hgl::algorithm::noret`**](../../cpp-gl/group__HGL-Algorithm.md#enum-result_discriminator): The algorithm executes purely for side effects. The search tree allocation is completely optimized away at compile time, and the function returns `void`.
21+
22+
### Handling Disconnected Components
23+
24+
Hypergraphs are rarely fully connected. Standard traversal wrappers gracefully handle this using the `root_vertex_id` parameter:
25+
26+
- **Specific Root:** If you pass a valid `vertex_id`, the algorithm will explore *only* the connected component reachable from that specific root.
27+
- [**`hgl::algorithm::no_root`**](../../cpp-gl/group__HGL-Algorithm.md#variable-no_root) (Default): If you omit the root ID, the algorithm automatically iterates through the entire hypergraph, sequentially triggering searches on unvisited vertices to ensure that *every disconnected component* is fully traversed.
28+
29+
---
30+
31+
## Standard Traversals
32+
33+
The standard [**`breadth_first_search`**](../../cpp-gl/group__HGL-Algorithm.md#function-breadth_first_search) and [**`depth_first_search`**](../../cpp-gl/group__HGL-Algorithm.md#function-depth_first_search) algorithms provide unrestricted exploration of a hypergraph's topology.
34+
35+
For **undirected hypergraphs**, these algorithms simply flood through all incident hyperedges and vertices.
36+
37+
For **BF-directed hypergraphs**, they execute a naive directional traversal: a hyperedge is traversed immediately upon discovering *any* of its tail vertices, instantly reaching all of its head vertices.
38+
39+
### Example Usage
40+
41+
```cpp
42+
#include <hgl/algorithm/traversal/breadth_first_search.hpp>
43+
#include <hgl/algorithm/traversal/depth_first_search.hpp>
44+
#include <iostream>
45+
46+
auto search_tree = hgl::algorithm::breadth_first_search(hypergraph, start_id); // (1)!
47+
hgl::algorithm::depth_first_search<hgl::algorithm::noret>( // (2)!
48+
hypergraph,
49+
hgl::algorithm::no_root,
50+
[](const auto& node) { // (3)!
51+
std::cout << "Discovered vertex: " << node.vertex_id << '\n';
52+
}
53+
);
54+
```
55+
56+
1. Standard execution. Returns a search_tree representing the traversal from start_id.
57+
2. Execution purely for side-effects over the entire hypergraph.
58+
3. The `pre_visit` callback logs the discovery/visitation of a vertex to the console.
59+
60+
---
61+
62+
## Backward Searches (B-Reachability)
63+
64+
In many domains (such as chemical reaction networks or logic circuits), reaching a single input is not enough to trigger an event. The HGL module provides [**`backward_bfs`**](../../cpp-gl/group__HGL-Algorithm.md#function-backward_bfs) and [**`backward_dfs`**](../../cpp-gl/group__HGL-Algorithm.md#function-backward_dfs) to enforce strict **B-reachability** semantics on BF-directed hypergraphs.
65+
66+
This search travels forward through the network (using the forward star / outgoing hyperedges). Unlike standard traversals, a backward search utilizes a blocking predicate: **A hyperedge is only fully traversed (and its head vertices enqueued) after *all* of its tail (source) vertices have been visited.** This is exceptionally useful for forward propagation or cascading activation, determining the complete set of subsequent states or products that can be successfully activated given a specific set of initial conditions.
67+
68+
Because complex reachability often requires multiple initial inputs, these algorithms accept a `RootRange` of initial vertices instead of a single root.
69+
70+
### Example Usage
71+
72+
```cpp
73+
#include <hgl/algorithm/traversal/backward_search.hpp>
74+
75+
std::vector<hgl::size_type> roots = { start_id_1, start_id_2 }; // (1)!
76+
auto b_reachability_tree = hgl::algorithm::backward_bfs(hypergraph, roots); // (2)!
77+
```
78+
79+
1. Supply multiple initial conditions (or inputs) to the network.
80+
2. The algorithm blocks at hyperedges until all tail dependencies are met.
81+
82+
---
83+
84+
## Forward Searches (F-Reachability)
85+
86+
Conversely, [**`forward_bfs`**](../../cpp-gl/group__HGL-Algorithm.md#function-forward_bfs) and [**`forward_dfs`**](../../cpp-gl/group__HGL-Algorithm.md#function-forward_dfs) enforce strict **F-reachability** semantics on BF-directed hypergraphs.
87+
88+
This search travels in reverse through the network (using the backward star / incoming hyperedges). **A hyperedge is only traversed (and its tail vertices enqueued) after *all* of its head (destination) vertices have been visited.** This is exceptionally useful for backward chaining, determining what minimal set of inputs is strictly required to achieve a specific target state.
89+
90+
### Example Usage
91+
92+
```cpp
93+
#include <hgl/algorithm/traversal/forward_search.hpp>
94+
95+
std::vector<hgl::size_type> targets = { target_id_1 }; // (1)!
96+
auto f_reachability_tree = hgl::algorithm::forward_dfs(hypergraph, targets);
97+
```
98+
99+
1. Define the target outputs we want to trace back from.
100+
101+
---
102+
103+
## Algorithmic Complexity and Layout Impact
104+
105+
Traversing a hypergraph involves evaluating vertices, hyperedges, and the full span of hyperedge incidences ($I$, defined as the total number of connections across the entire hypergraph: $I = \sum deg(v) = \sum |e|$).
106+
107+
Because the HGL generic templates utilize a two-step expansion (querying incident hyperedges, then querying adjacent vertices), **the algorithmic complexity of your traversal is drastically impacted by your chosen Representation Model and Layout Tag.**
108+
109+
### 1. Incidence Lists (`list_t` / `flat_list_t`)
110+
111+
- **`bidirectional_t` Layout (Optimal):** $O(|V| + |E| + I)$. Because the bidirectional layout natively supports $O(1)$ iteration from a vertex to its hyperedges AND from a hyperedge to its vertices, the engine traverses the topology effortlessly. **This is the only incidence list layout recommended for traversals.**
112+
- **`vertex_major_t` Layout:** $O(|V| + |E| \cdot (|V| + I))$. Finding hyperedges from a vertex is fast, but step 2 (finding vertices within that hyperedge) requires a full-structure scan. Traversal performance is severely degraded.
113+
- **`hyperedge_major_t` Layout:** $O(|E| + |V| \cdot (|E| + I))$. Finding vertices from a hyperedge is fast, but step 1 (finding incident hyperedges from a vertex) requires a full-structure scan. Traversal performance is severely degraded.
114+
115+
> [!WARNING] Asymmetric Layout Traversals
116+
>
117+
> If you are using a strictly single-major layout (`vertex_major_t` or `hyperedge_major_t`) due to memory constraints, executing a native BFS or DFS over the hypergraph will be excessively slow due to the full-structure scans required to jump between major and minor elements. If traversal is required, consider converting to a `bidirectional_t` list or using an Incidence Graph Projection via the GL module.
118+
119+
### 2. Incidence Matrices (`matrix_t` / `flat_matrix_t`)
120+
121+
- **Any Layout:** $O(|V| \cdot |E|)$. To find incident hyperedges, the algorithm must scan across $|E|$ elements for a given vertex. To find adjacent vertices, it must scan across $|V|$ elements for a given hyperedge.

docs/hgl/architecture.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -328,9 +328,9 @@ The complexities of topological queries is identical for standard (`matrix_t`) a
328328
329329
| Mutation Operation | `matrix_t` (Standard) | `flat_matrix_t` (Flat) |
330330
| :--- | :--- | :--- |
331-
| **Add Vertex** | $O(\vert E \vert)$ amortized if `vertex_major_t`<br>$O(\vert V \vert \times \vert E \vert)$ if `hyperedge_major_t` | $O(\vert V \vert \times \vert E \vert)$ |
332-
| **Add Hyperedge** | $O(\vert V \vert \times \vert E \vert)$ if `vertex_major_t`<br>$O(\vert V \vert)$ amortized if `hyperedge_major_t` | $O(\vert V \vert \times \vert E \vert)$ |
333-
| **Remove Vertex / Hyperedge** | $O(\vert V \vert \times \vert E \vert)$ (Shift rows/cols) | $O(\vert V \vert \times \vert E \vert)$ |
331+
| **Add Vertex** | $O(\vert E \vert)$ amortized if `vertex_major_t`<br>$O(\vert V \vert \cdot \vert E \vert)$ if `hyperedge_major_t` | $O(\vert V \vert \cdot \vert E \vert)$ |
332+
| **Add Hyperedge** | $O(\vert V \vert \cdot \vert E \vert)$ if `vertex_major_t`<br>$O(\vert V \vert)$ amortized if `hyperedge_major_t` | $O(\vert V \vert \cdot \vert E \vert)$ |
333+
| **Remove Vertex / Hyperedge** | $O(\vert V \vert \cdot \vert E \vert)$ (Shift rows/cols) | $O(\vert V \vert \cdot \vert E \vert)$ |
334334
| **Add / Remove Incidence** (Bind/Unbind) | $O(1)$ | $O(1)$ |
335335
336336
> [!NOTE] Matrix Layouts and Cache Locality

0 commit comments

Comments
 (0)