From 7857b1051ced8d69b3af5a0f734fcf2cb5e9e90f Mon Sep 17 00:00:00 2001 From: Phil Ratzloff Date: Sat, 21 Feb 2026 16:13:04 -0500 Subject: [PATCH 1/8] Add incoming edges design document --- agents/incoming_edges_design.md | 570 ++++++++++++++++++++++++++++++++ 1 file changed, 570 insertions(+) create mode 100644 agents/incoming_edges_design.md diff --git a/agents/incoming_edges_design.md b/agents/incoming_edges_design.md new file mode 100644 index 0000000..fd28a2f --- /dev/null +++ b/agents/incoming_edges_design.md @@ -0,0 +1,570 @@ +# Incoming Edge Support — Design Document + +## 1. Problem Statement + +The graph-v3 library currently provides **outgoing edge access only**. All edge CPOs +(`edges()`, `degree()`, `target_id()`), views (`incidence`, `edgelist`, `neighbors`), +concepts (`adjacency_list`, `vertex_edge_range`), and algorithms are implicitly outgoing. +There is no way to query "which edges point **to** a vertex." + +This document proposes extending the library to support **incoming edges** while +preserving full backward compatibility with existing code. + +--- + +## 2. Design Principles + +1. **Mirror, don't replace.** Every new incoming-edge facility mirrors an existing + outgoing-edge facility with a consistent naming convention. +2. **Backward compatible.** Existing code compiles unchanged. The current `edges()`, + `degree()`, etc. continue to mean "outgoing." +3. **Opt-in.** Graphs that don't store reverse adjacency data simply don't model the + new concepts — algorithms gracefully fall back to outgoing-only behavior. +4. **Consistent naming.** Incoming counterparts use an `in_` prefix; where ambiguity + arises, outgoing counterparts gain an `out_` alias. + +--- + +## 3. Naming Convention + +### 3.1 Existing names (no rename required) + +The current names remain as-is and **continue to mean "outgoing edges"**: + +| Current Name | Stays | Meaning | +|---|---|---| +| `edges(g, u)` | Yes | Outgoing edges from vertex `u` | +| `degree(g, u)` | Yes | Out-degree of `u` | +| `target_id(g, uv)` | Yes | Target vertex of edge `uv` | +| `source_id(g, uv)` | Yes | Source vertex of edge `uv` | +| `target(g, uv)` | Yes | Target vertex descriptor | +| `source(g, uv)` | Yes | Source vertex descriptor | +| `num_edges(g)` | Yes | Total edge count (direction-agnostic) | +| `find_vertex_edge(g, u, v)` | Yes | Find outgoing edge from `u` to `v` | +| `contains_edge(g, u, v)` | Yes | Check outgoing edge from `u` to `v` | +| `has_edge(g, uid, vid)` | Yes | Check outgoing edge from `uid` to `vid` | +| `edge_value(g, uv)` | Yes | Edge property (direction-agnostic) | + +### 3.2 New outgoing aliases (optional clarity) + +For code that wants to be explicit, introduce **aliases** that forward to the +existing CPOs. These are convenience only — not required: + +| New Alias | Forwards To | +|---|---| +| `out_edges(g, u)` | `edges(g, u)` | +| `out_degree(g, u)` | `degree(g, u)` | + +These are defined as inline constexpr CPO aliases or thin wrapper functions in +`graph::adj_list` and re-exported to `graph::`. + +### 3.3 New incoming names + +| New Name | Meaning | +|---|---| +| `in_edges(g, u)` / `in_edges(g, uid)` | Incoming edges to vertex `u` | +| `in_degree(g, u)` / `in_degree(g, uid)` | In-degree of `u` | +| `find_vertex_in_edge(g, u, v)` | Find incoming edge from `v` to `u` | +| `contains_in_edge(g, u, v)` | Check incoming edge from `v` to `u` | +| `has_in_edge(g, uid, vid)` | Check incoming edge from `vid` to `uid` | + +--- + +## 4. New CPOs + +### 4.1 `in_edges(g, u)` / `in_edges(g, uid)` + +**File:** `include/graph/adj_list/detail/graph_cpo.hpp` (new section) + +**Resolution order** (mirrors `edges()`): + +| Priority | Strategy | Expression | +|---|---|---| +| 1 | `_vertex_member` | `u.inner_value(g).in_edges()` | +| 2 | `_adl` | ADL `in_edges(g, u)` | +| 3 | `_edge_value_pattern` | (not applicable — no implicit reverse structure) | + +For the `(g, uid)` overload: + +| Priority | Strategy | Expression | +|---|---|---| +| 1 | `_adl` | ADL `in_edges(g, uid)` | +| 2 | `_default` | `in_edges(g, *find_vertex(g, uid))` | + +**Return type:** `in_vertex_edge_range_t` — an `edge_descriptor_view` wrapping +the reverse edge container's iterators. + +### 4.2 `in_degree(g, u)` / `in_degree(g, uid)` + +**File:** `include/graph/adj_list/detail/graph_cpo.hpp` (new section) + +**Resolution order** (mirrors `degree()`): + +| Priority | Strategy | Expression | +|---|---|---| +| 1 | `_member` | `g.in_degree(u)` | +| 2 | `_adl` | ADL `in_degree(g, u)` | +| 3 | `_default` | `size(in_edges(g, u))` or `distance(in_edges(g, u))` | + +### 4.3 `out_edges` / `out_degree` (aliases) + +**File:** `include/graph/adj_list/detail/graph_cpo.hpp` + +```cpp +inline constexpr auto& out_edges = edges; +inline constexpr auto& out_degree = degree; +``` + +### 4.4 `find_vertex_in_edge`, `contains_in_edge`, `has_in_edge` + +These mirror `find_vertex_edge` / `contains_edge` / `has_edge` but operate on +the reverse adjacency structure. Implementation follows the same member → ADL → default +cascade pattern. + +--- + +## 5. New Type Aliases + +**File:** `include/graph/adj_list/detail/graph_cpo.hpp` (after `in_edges` CPO) + +| Alias | Definition | +|---|---| +| `in_vertex_edge_range_t` | `decltype(in_edges(g, vertex_t))` | +| `in_vertex_edge_iterator_t` | `ranges::iterator_t>` | +| `in_edge_t` | `ranges::range_value_t>` | + +Also add outgoing aliases for explicitness: + +| Alias | Definition | +|---|---| +| `out_vertex_edge_range_t` | Same as `vertex_edge_range_t` | +| `out_vertex_edge_iterator_t` | Same as `vertex_edge_iterator_t` | +| `out_edge_t` | Same as `edge_t` | + +--- + +## 6. New Concepts + +**File:** `include/graph/adj_list/adjacency_list_concepts.hpp` + +### 6.1 `in_vertex_edge_range` + +```cpp +template +concept in_vertex_edge_range = + std::ranges::forward_range && edge>; +``` + +Identical constraint to `vertex_edge_range` — the distinction is semantic (incoming +vs outgoing), enforced by which CPO returns the range. + +### 6.2 `bidirectional_adjacency_list` + +```cpp +template +concept bidirectional_adjacency_list = + adjacency_list && + requires(G& g, vertex_t u) { + { in_edges(g, u) } -> in_vertex_edge_range; + }; +``` + +A graph that models `bidirectional_adjacency_list` supports both `edges(g, u)` +(outgoing) and `in_edges(g, u)` (incoming). + +### 6.3 `index_bidirectional_adjacency_list` + +```cpp +template +concept index_bidirectional_adjacency_list = + bidirectional_adjacency_list && index_vertex_range; +``` + +--- + +## 7. New Traits + +**File:** `include/graph/adj_list/adjacency_list_traits.hpp` + +| Trait | Constraint | +|---|---| +| `has_in_degree` | `in_degree(g, u)` and `in_degree(g, uid)` both return integral | +| `has_in_degree_v` | `bool` shorthand | +| `has_find_vertex_in_edge` | All `find_vertex_in_edge` overloads return `in_edge_t` | +| `has_find_vertex_in_edge_v` | `bool` shorthand | +| `has_contains_in_edge` | `contains_in_edge(g, u, v)` returns bool | +| `has_contains_in_edge_v` | `bool` shorthand | + +--- + +## 8. New Views + +### 8.1 `in_incidence` view + +**File:** `include/graph/views/in_incidence.hpp` (new file) + +Mirrors `incidence_view` from `include/graph/views/incidence.hpp`. + +| Class | Yields | Description | +|---|---|---| +| `in_incidence_view` | `edge_info{sid, uv}` | Iterates incoming edges to a vertex, yielding **source_id** + edge descriptor | +| `in_incidence_view` | `edge_info{sid, uv, val}` | Same, with value function | +| `basic_in_incidence_view` | `edge_info{sid}` | Source ID only | +| `basic_in_incidence_view` | `edge_info{sid, val}` | Source ID + value function | + +**Factory functions:** +```cpp +namespace graph::views { + in_incidence(g, u) // → in_incidence_view + in_incidence(g, uid) // → in_incidence_view + in_incidence(g, u, evf) // → in_incidence_view + in_incidence(g, uid, evf) // → in_incidence_view + basic_in_incidence(g, uid) // → basic_in_incidence_view + basic_in_incidence(g, uid, evf) // → basic_in_incidence_view +} +``` + +**Key difference from `incidence_view`:** The standard `incidence_view` iterates +`edges(g, u)` and yields `target_id` per edge. The `in_incidence_view` iterates +`in_edges(g, u)` and yields `source_id` per edge (the vertex the edge comes from). + +### 8.2 `in_neighbors` view + +**File:** `include/graph/views/in_neighbors.hpp` (new file) + +Mirrors `neighbors_view` from `include/graph/views/neighbors.hpp`. + +| Class | Yields | Description | +|---|---|---| +| `in_neighbors_view` | `neighbor_info{sid, n}` | Iterates source vertices of incoming edges | +| `in_neighbors_view` | `neighbor_info{sid, n, val}` | Same, with vertex value function | +| `basic_in_neighbors_view` | `neighbor_info{sid}` | Source ID only | +| `basic_in_neighbors_view` | `neighbor_info{sid, val}` | Source ID + value function | + +### 8.3 Outgoing aliases (optional) + +For symmetry, add aliases in the `graph::views` namespace: + +```cpp +namespace graph::views { + inline constexpr auto& out_incidence = incidence; + inline constexpr auto& basic_out_incidence = basic_incidence; + inline constexpr auto& out_neighbors = neighbors; + inline constexpr auto& basic_out_neighbors = basic_neighbors; +} +``` + +--- + +## 9. BFS/DFS Parameterization + +### Problem + +`bfs.hpp` and `dfs.hpp` hardcode `adj_list::edges(g, vertex)` in ~6 locations each. +To support reverse traversal (following incoming edges), they need parameterization. + +### Solution + +Add an **edge accessor** template parameter defaulting to the current outgoing behavior: + +```cpp +// Default edge accessor — current behavior +struct out_edges_accessor { + template + constexpr auto operator()(G& g, vertex_t u) const { + return adj_list::edges(g, u); + } +}; + +// Reverse edge accessor +struct in_edges_accessor { + template + constexpr auto operator()(G& g, vertex_t u) const { + return adj_list::in_edges(g, u); + } +}; + +template +class vertices_bfs_view { ... }; + +template +class edges_bfs_view { ... }; +``` + +Factory functions add overloads accepting the accessor: +```cpp +// Existing (unchanged) +vertices_bfs(g, seed) +vertices_bfs(g, seed, vvf) + +// New +vertices_bfs(g, seed, vvf, in_edges_accessor{}) // reverse BFS +edges_bfs(g, seed, evf, in_edges_accessor{}) // reverse BFS +``` + +Same pattern applies to DFS and topological sort views. + +--- + +## 10. Container Support + +### 10.1 `dynamic_graph` — Reverse Adjacency Storage + +The simplest approach is a **separate reverse adjacency list** stored alongside the +forward list. This is a well-known pattern (e.g., Boost.Graph's `bidirectionalS`). + +**Option A: Built-in template parameter** + +Add a `bool Bidirectional = false` template parameter to `dynamic_graph_base`: + +```cpp +template +class dynamic_graph_base; +``` + +When `Bidirectional = true`: +- Each vertex stores two edge containers: `out_edges_` and `in_edges_` +- `create_edge(u, v)` inserts into `u.out_edges_` and `v.in_edges_` +- `erase_edge` removes from both lists +- The `edges(g, u)` CPO returns `u.out_edges_` (unchanged) +- The `in_edges(g, u)` CPO returns `u.in_edges_` + +When `Bidirectional = false`: +- Behavior is identical to today (no storage overhead) + +**Option B: External reverse index wrapper** + +A `reverse_adjacency` adaptor that wraps an existing graph and provides +`in_edges()` via a separately-constructed reverse index. This avoids modifying +`dynamic_graph` at all but adds a construction cost: + +```cpp +auto rev = graph::reverse_adjacency(g); +for (auto [sid, uv] : views::in_incidence(rev, target_vertex)) { ... } +``` + +**Recommendation:** Option A for tight integration. Option B as an additional +utility for read-only reverse queries on existing graphs. + +### 10.2 `compressed_graph` — CSC (Compressed Sparse Column) Format + +The CSR format stores outgoing edges efficiently. The CSC (Compressed Sparse Column) +format stores incoming edges. A bidirectional `compressed_graph` would store both: + +- `row_index_` + `col_index_` (CSR, outgoing — existing) +- `col_ptr_` + `row_index_csc_` (CSC, incoming — new) + +This is standard in sparse matrix libraries (e.g., Eigen stores both CSR and CSC). + +**Implementation:** Add a `bool Bidirectional = false` template parameter. When enabled, +the constructor builds the CSC representation from the input edges alongside the CSR. + +### 10.3 `undirected_adjacency_list` — Already bidirectional + +The `undirected_adjacency_list` already stores edges in both endpoint vertex lists +via `inward_list`/`outward_list` links. For undirected graphs, `edges(g, u)` and +`in_edges(g, u)` return the **same** range — every edge is both incoming and outgoing. + +**Implementation:** Provide `in_edges()` as an ADL friend that returns the same +`edge_descriptor_view` as `edges()`. Similarly, `in_degree()` returns the same value +as `degree()`. This allows undirected graphs to model `bidirectional_adjacency_list` +at zero cost. + +### 10.4 New trait type combinations for `dynamic_graph` + +For each existing trait file (26 total in `include/graph/container/traits/`), a +corresponding `b` (bidirectional) variant may be added: + +| Existing | Bidirectional Variant | Description | +|---|---|---| +| `vov_graph_traits` | `bvov_graph_traits` | Bidirectional vector-of-vectors | +| `vol_graph_traits` | `bvol_graph_traits` | Bidirectional vector-of-lists | +| ... | ... | ... | + +Alternatively, a single set of traits with `Bidirectional` as a template parameter +avoids the combinatorial explosion. + +--- + +## 11. Algorithm Updates + +Most algorithms only need outgoing edges and require **no changes**. The following +would benefit from incoming edge support: + +| Algorithm | Benefit | +|---|---| +| **Kosaraju's SCC** (`connected_components.hpp`) | Currently simulates reverse graph by rebuilding edges. With `in_edges()`, the second DFS pass can use `in_edges_accessor` directly. | +| **PageRank** (future) | Requires iterating incoming edges for each vertex. | +| **Reverse BFS/DFS** | Enable with `in_edges_accessor` parameter (§9). | +| **Transpose graph** | Can be implemented as a zero-cost view that swaps `edges()`↔`in_edges()`. | +| **Dominator trees** | Require reverse control flow graph (incoming edges). | + +No existing algorithm needs to be **renamed**. They all operate on outgoing +edges by default and remain unchanged. + +--- + +## 12. Namespace & Re-export Updates + +**File:** `include/graph/graph.hpp` + +Add to the `graph::` re-exports: + +```cpp +// New incoming-edge CPOs +using adj_list::in_edges; +using adj_list::in_degree; +using adj_list::out_edges; +using adj_list::out_degree; +using adj_list::find_vertex_in_edge; +using adj_list::contains_in_edge; +using adj_list::has_in_edge; + +// New concepts +using adj_list::in_vertex_edge_range; +using adj_list::bidirectional_adjacency_list; +using adj_list::index_bidirectional_adjacency_list; + +// New traits +using adj_list::has_in_degree; +using adj_list::has_in_degree_v; + +// New type aliases +using adj_list::in_vertex_edge_range_t; +using adj_list::in_vertex_edge_iterator_t; +using adj_list::in_edge_t; +using adj_list::out_vertex_edge_range_t; +using adj_list::out_vertex_edge_iterator_t; +using adj_list::out_edge_t; +``` + +**File:** `include/graph/graph.hpp` — add includes: + +```cpp +#include +#include +``` + +--- + +## 13. Documentation Updates + +### 13.1 Existing docs to update + +| File | Change | +|---|---| +| `docs/index.md` | Add "Bidirectional edge access" to feature list | +| `docs/getting-started.md` | Add section on incoming edges; update "outgoing edges" mentions to be explicit | +| `docs/user-guide/views.md` | Add `in_incidence` and `in_neighbors` views; add `out_incidence`/`out_neighbors` aliases | +| `docs/user-guide/algorithms.md` | Note which algorithms benefit from bidirectional access | +| `docs/user-guide/containers.md` | Document bidirectional dynamic_graph and compressed_graph | +| `docs/reference/concepts.md` | Add `bidirectional_adjacency_list`, `in_vertex_edge_range` | +| `docs/reference/cpo-reference.md` | Add `in_edges`, `in_degree`, `out_edges`, `out_degree`, `find_vertex_in_edge`, `contains_in_edge`, `has_in_edge` | +| `docs/reference/type-aliases.md` | Add `in_vertex_edge_range_t`, `in_edge_t`, etc. | +| `docs/reference/adjacency-list-interface.md` | Add incoming edge section | +| `docs/contributing/cpo-implementation.md` | Add `in_edges` as an example of the CPO pattern | +| `README.md` | Update feature highlights with bidirectional support | + +### 13.2 New docs + +| File | Content | +|---|---| +| `docs/user-guide/bidirectional-access.md` | Tutorial-style guide on using incoming edges | + +--- + +## 14. Implementation Phases + +### Phase 1: Core CPOs & Concepts (~2-3 days) + +1. Implement `in_edges` CPO in `graph_cpo.hpp` (mirror `edges` CPO) +2. Implement `in_degree` CPO (mirror `degree` CPO) +3. Add `out_edges` / `out_degree` aliases +4. Define `in_vertex_edge_range_t`, `in_edge_t`, etc. +5. Define `bidirectional_adjacency_list` concept +6. Add `has_in_degree` trait +7. Update `graph.hpp` re-exports +8. Add unit tests for CPO resolution (stub graph with `in_edges()` member) + +### Phase 2: Views (~2 days) + +1. Create `in_incidence.hpp` (copy+adapt `incidence.hpp`) +2. Create `in_neighbors.hpp` (copy+adapt `neighbors.hpp`) +3. Add `out_incidence` / `out_neighbors` aliases +4. Unit tests for all new views + +### Phase 3: Container Support (~3-4 days) + +1. Add `Bidirectional` parameter to `dynamic_graph_base` +2. Update `create_edge` / `erase_edge` / `clear` to maintain reverse lists +3. Add ADL `in_edges()` friend to bidirectional dynamic_graph +4. Add CSC support to `compressed_graph` +5. Add `in_edges()` to `undirected_adjacency_list` (returns same as `edges()`) +6. Unit tests for all three containers + +### Phase 4: BFS/DFS Parameterization (~1-2 days) + +1. Add `EdgeAccessor` template parameter to BFS/DFS/topological sort views +2. Define `out_edges_accessor` and `in_edges_accessor` functors +3. Add factory function overloads +4. Unit tests for reverse BFS/DFS + +### Phase 5: Algorithm Updates (~1-2 days) + +1. Refactor Kosaraju's SCC to use `in_edges()` when available +2. Add `transpose_view` (zero-cost view swapping `edges()`↔`in_edges()`) +3. Unit tests + +### Phase 6: Documentation (~2 days) + +1. Update all docs listed in §13.1 +2. Create `bidirectional-access.md` tutorial +3. Update CHANGELOG.md + +--- + +## 15. Summary of All Changes + +| Category | New | Modified | Deleted | +|---|---|---|---| +| **CPOs** | `in_edges`, `in_degree`, `out_edges` (alias), `out_degree` (alias), `find_vertex_in_edge`, `contains_in_edge`, `has_in_edge` | — | — | +| **Concepts** | `in_vertex_edge_range`, `bidirectional_adjacency_list`, `index_bidirectional_adjacency_list` | — | — | +| **Traits** | `has_in_degree`, `has_in_degree_v`, `has_find_vertex_in_edge`, `has_contains_in_edge` | — | — | +| **Type Aliases** | `in_vertex_edge_range_t`, `in_vertex_edge_iterator_t`, `in_edge_t`, `out_vertex_edge_range_t`, `out_vertex_edge_iterator_t`, `out_edge_t` | — | — | +| **Views** | `in_incidence.hpp`, `in_neighbors.hpp`, `out_edges_accessor`, `in_edges_accessor` | `bfs.hpp`, `dfs.hpp`, `topological_sort.hpp` (add `EdgeAccessor` param) | — | +| **Containers** | — | `dynamic_graph.hpp` (add `Bidirectional`), `compressed_graph.hpp` (add CSC), `undirected_adjacency_list.hpp` (add `in_edges` friend) | — | +| **Algorithms** | `transpose_view` | `connected_components.hpp` (Kosaraju optimization) | — | +| **Headers** | — | `graph.hpp` (new includes & re-exports) | — | +| **Docs** | `bidirectional-access.md` | 11 existing docs (§13.1) | — | +| **Tests** | CPO tests, view tests, container tests, BFS/DFS reverse tests | — | — | + +**Estimated effort:** 11–15 days + +--- + +## Appendix A: Full CPO Resolution Table + +| CPO | Tier 1 | Tier 2 | Tier 3 | +|---|---|---|---| +| `edges(g, u)` | `u.inner_value(g).edges()` | ADL `edges(g, u)` | inner_value is forward_range → wrap | +| `in_edges(g, u)` | `u.inner_value(g).in_edges()` | ADL `in_edges(g, u)` | *(no default)* | +| `degree(g, u)` | `g.degree(u)` | ADL `degree(g, u)` | `size(edges(g, u))` | +| `in_degree(g, u)` | `g.in_degree(u)` | ADL `in_degree(g, u)` | `size(in_edges(g, u))` | +| `out_edges(g, u)` | → `edges(g, u)` | | | +| `out_degree(g, u)` | → `degree(g, u)` | | | + +## Appendix B: No-Rename Justification + +Several graph libraries (Boost.Graph, NetworkX, LEDA) distinguish `out_edges` / +`in_edges` explicitly. In graph-v3, `edges()` has **always** meant outgoing (this is +documented and all algorithms depend on it). Renaming `edges()` to `out_edges()` would: + +- Break every existing user's code +- Require renaming `vertex_edge_range_t` → `out_vertex_edge_range_t` across 50+ files +- Create churn in all 14 algorithms + +Instead we keep `edges()` as the primary outgoing CPO (matching the "default is outgoing" +convention) and add `out_edges` as a convenience alias for codebases that want explicit +directionality. From cf6126d2f0fb19f69770e0fd4e68ca314b664d13 Mon Sep 17 00:00:00 2001 From: Phil Ratzloff Date: Sat, 21 Feb 2026 18:23:18 -0500 Subject: [PATCH 2/8] Update incoming edges design: independent in/out edge types, relaxed concepts, find_out_edge alias --- agents/incoming_edges_design.md | 99 +++++++++++++++++++++++++++------ 1 file changed, 81 insertions(+), 18 deletions(-) diff --git a/agents/incoming_edges_design.md b/agents/incoming_edges_design.md index fd28a2f..71435db 100644 --- a/agents/incoming_edges_design.md +++ b/agents/incoming_edges_design.md @@ -22,6 +22,12 @@ preserving full backward compatibility with existing code. new concepts — algorithms gracefully fall back to outgoing-only behavior. 4. **Consistent naming.** Incoming counterparts use an `in_` prefix; where ambiguity arises, outgoing counterparts gain an `out_` alias. +5. **Independent edge types.** `in_edge_t` and `edge_t` (i.e. `out_edge_t`) + are independently deduced from their respective ranges. They are commonly the same + type but this is **not** required. For example, in a distributed graph the outgoing + edges may carry full property data while incoming edges are lightweight back-pointers + (just a source vertex ID). In a CSR+CSC compressed graph, the CSC incoming entries + may store only a column index plus a back-reference into the CSR value array. --- @@ -54,6 +60,7 @@ existing CPOs. These are convenience only — not required: |---|---| | `out_edges(g, u)` | `edges(g, u)` | | `out_degree(g, u)` | `degree(g, u)` | +| `find_out_edge(g, u, v)` | `find_vertex_edge(g, u, v)` | These are defined as inline constexpr CPO aliases or thin wrapper functions in `graph::adj_list` and re-exported to `graph::`. @@ -64,7 +71,7 @@ These are defined as inline constexpr CPO aliases or thin wrapper functions in |---|---| | `in_edges(g, u)` / `in_edges(g, uid)` | Incoming edges to vertex `u` | | `in_degree(g, u)` / `in_degree(g, uid)` | In-degree of `u` | -| `find_vertex_in_edge(g, u, v)` | Find incoming edge from `v` to `u` | +| `find_in_edge(g, u, v)` | Find incoming edge from `v` to `u` | | `contains_in_edge(g, u, v)` | Check incoming edge from `v` to `u` | | `has_in_edge(g, uid, vid)` | Check incoming edge from `vid` to `uid` | @@ -106,20 +113,21 @@ the reverse edge container's iterators. | 2 | `_adl` | ADL `in_degree(g, u)` | | 3 | `_default` | `size(in_edges(g, u))` or `distance(in_edges(g, u))` | -### 4.3 `out_edges` / `out_degree` (aliases) +### 4.3 `out_edges` / `out_degree` / `find_out_edge` (aliases) **File:** `include/graph/adj_list/detail/graph_cpo.hpp` ```cpp -inline constexpr auto& out_edges = edges; -inline constexpr auto& out_degree = degree; +inline constexpr auto& out_edges = edges; +inline constexpr auto& out_degree = degree; +inline constexpr auto& find_out_edge = find_vertex_edge; ``` -### 4.4 `find_vertex_in_edge`, `contains_in_edge`, `has_in_edge` +### 4.4 `find_in_edge`, `contains_in_edge`, `has_in_edge` -These mirror `find_vertex_edge` / `contains_edge` / `has_edge` but operate on -the reverse adjacency structure. Implementation follows the same member → ADL → default -cascade pattern. +These mirror `find_vertex_edge` (`find_out_edge`) / `contains_edge` / `has_edge` but +operate on the reverse adjacency structure. Implementation follows the same +member → ADL → default cascade pattern. --- @@ -133,6 +141,10 @@ cascade pattern. | `in_vertex_edge_iterator_t` | `ranges::iterator_t>` | | `in_edge_t` | `ranges::range_value_t>` | +`in_edge_t` is **independently deduced** from the `in_edges()` return range. +It is commonly the same type as `edge_t` but this is not required. See +Design Principle 5 and Appendix C for rationale. + Also add outgoing aliases for explicitness: | Alias | Definition | @@ -152,11 +164,17 @@ Also add outgoing aliases for explicitness: ```cpp template concept in_vertex_edge_range = - std::ranges::forward_range && edge>; + std::ranges::forward_range; ``` -Identical constraint to `vertex_edge_range` — the distinction is semantic (incoming -vs outgoing), enforced by which CPO returns the range. +Note: this is intentionally **less constrained** than `vertex_edge_range` (which +requires `edge>`). Incoming edges need only be iterable — +their element type (`in_edge_t`) may be a lightweight back-pointer that does +not satisfy the full `edge` concept. The minimum requirement is that +`source_id(g, ie)` can extract the neighbor vertex ID (see §6.2). + +When `in_edge_t` and `edge_t` are the same type, the range will naturally +model `vertex_edge_range` as well. ### 6.2 `bidirectional_adjacency_list` @@ -164,13 +182,21 @@ vs outgoing), enforced by which CPO returns the range. template concept bidirectional_adjacency_list = adjacency_list && - requires(G& g, vertex_t u) { + requires(G& g, vertex_t u, in_edge_t ie) { { in_edges(g, u) } -> in_vertex_edge_range; + { source_id(g, ie) } -> std::convertible_to>; + // Note: no edge_value() requirement on incoming edges }; ``` A graph that models `bidirectional_adjacency_list` supports both `edges(g, u)` -(outgoing) and `in_edges(g, u)` (incoming). +(outgoing) and `in_edges(g, u)` (incoming). The only requirement on incoming +edge elements is that the neighbor vertex ID (the edge's source) can be extracted +via `source_id()`. Notably, `edge_value(g, ie)` is **not** required — incoming +edges may be valueless back-pointers. + +When `in_edge_t == edge_t`, `edge_value()` will work on incoming edges +automatically. ### 6.3 `index_bidirectional_adjacency_list` @@ -190,8 +216,8 @@ concept index_bidirectional_adjacency_list = |---|---| | `has_in_degree` | `in_degree(g, u)` and `in_degree(g, uid)` both return integral | | `has_in_degree_v` | `bool` shorthand | -| `has_find_vertex_in_edge` | All `find_vertex_in_edge` overloads return `in_edge_t` | -| `has_find_vertex_in_edge_v` | `bool` shorthand | +| `has_find_in_edge` | All `find_in_edge` overloads return `in_edge_t` | +| `has_find_in_edge_v` | `bool` shorthand | | `has_contains_in_edge` | `contains_in_edge(g, u, v)` returns bool | | `has_contains_in_edge_v` | `bool` shorthand | @@ -228,6 +254,10 @@ namespace graph::views { `edges(g, u)` and yields `target_id` per edge. The `in_incidence_view` iterates `in_edges(g, u)` and yields `source_id` per edge (the vertex the edge comes from). +**Edge type note:** The `EVF` value function receives `in_edge_t`, which may +differ from `edge_t`. When incoming edges are valueless back-pointers, the +`void` (no value function) variants are the natural choice. + ### 8.2 `in_neighbors` view **File:** `include/graph/views/in_neighbors.hpp` (new file) @@ -331,6 +361,11 @@ When `Bidirectional = true`: - The `edges(g, u)` CPO returns `u.out_edges_` (unchanged) - The `in_edges(g, u)` CPO returns `u.in_edges_` +The in-edge container element type may differ from the out-edge element type. +The default stores the same `EV` (so `in_edge_t == edge_t`), but an +additional template parameter (e.g., `InEV = EV`) can allow lightweight +back-pointer in-edges (e.g., just a source vertex ID) for reduced storage. + When `Bidirectional = false`: - Behavior is identical to today (no storage overhead) @@ -418,7 +453,8 @@ using adj_list::in_edges; using adj_list::in_degree; using adj_list::out_edges; using adj_list::out_degree; -using adj_list::find_vertex_in_edge; +using adj_list::find_out_edge; +using adj_list::find_in_edge; using adj_list::contains_in_edge; using adj_list::has_in_edge; @@ -529,9 +565,9 @@ using adj_list::out_edge_t; | Category | New | Modified | Deleted | |---|---|---|---| -| **CPOs** | `in_edges`, `in_degree`, `out_edges` (alias), `out_degree` (alias), `find_vertex_in_edge`, `contains_in_edge`, `has_in_edge` | — | — | +| **CPOs** | `in_edges`, `in_degree`, `out_edges` (alias), `out_degree` (alias), `find_out_edge` (alias), `find_in_edge`, `contains_in_edge`, `has_in_edge` | — | — | | **Concepts** | `in_vertex_edge_range`, `bidirectional_adjacency_list`, `index_bidirectional_adjacency_list` | — | — | -| **Traits** | `has_in_degree`, `has_in_degree_v`, `has_find_vertex_in_edge`, `has_contains_in_edge` | — | — | +| **Traits** | `has_in_degree`, `has_in_degree_v`, `has_find_in_edge`, `has_contains_in_edge` | — | — | | **Type Aliases** | `in_vertex_edge_range_t`, `in_vertex_edge_iterator_t`, `in_edge_t`, `out_vertex_edge_range_t`, `out_vertex_edge_iterator_t`, `out_edge_t` | — | — | | **Views** | `in_incidence.hpp`, `in_neighbors.hpp`, `out_edges_accessor`, `in_edges_accessor` | `bfs.hpp`, `dfs.hpp`, `topological_sort.hpp` (add `EdgeAccessor` param) | — | | **Containers** | — | `dynamic_graph.hpp` (add `Bidirectional`), `compressed_graph.hpp` (add CSC), `undirected_adjacency_list.hpp` (add `in_edges` friend) | — | @@ -568,3 +604,30 @@ documented and all algorithms depend on it). Renaming `edges()` to `out_edges()` Instead we keep `edges()` as the primary outgoing CPO (matching the "default is outgoing" convention) and add `out_edges` as a convenience alias for codebases that want explicit directionality. + +## Appendix C: Independent In/Out Edge Types + +`in_edge_t` and `edge_t` are independently deduced: + +```cpp +template using edge_t = ranges::range_value_t>; // from edges(g,u) +template using in_edge_t = ranges::range_value_t>; // from in_edges(g,u) +``` + +They are commonly the same type, but this is **not required**. Scenarios where +they differ: + +| Scenario | `edge_t` | `in_edge_t` | +|---|---|---| +| Symmetric (common case) | `pair` | `pair` (same) | +| Distributed graph | `pair` | `VId` (back-pointer only) | +| CSR + CSC compressed | Full edge with value | Column index + CSR back-ref | +| Lightweight reverse index | `pair` | `VId` (source ID only) | + +The `bidirectional_adjacency_list` concept (§6.2) only requires `source_id(g, ie)` +on incoming edges — not `edge_value()`. This means: +- Algorithms that only need the graph structure (BFS, DFS, SCC) work with any + `in_edge_t`. +- Algorithms that need edge properties on incoming edges (rare) can add their own + `requires edge_value(g, in_edge_t{})` constraint. +- The undirected case (`in_edge_t == edge_t`) is the zero-cost happy path. From 899c672e8696e8e94f6616e37560faf343beac76 Mon Sep 17 00:00:00 2001 From: Phil Ratzloff Date: Sat, 21 Feb 2026 18:44:47 -0500 Subject: [PATCH 3/8] docs: add code coverage report (95.8% lines, 92.0% functions)\n\n- Add docs/status/coverage.md with per-file coverage breakdown\n- Link coverage report from docs/index.md\n- Add coverage metrics to docs/status/metrics.md\n- Generated from 4261 tests (100% pass) using linux-gcc-coverage preset --- docs/index.md | 1 + docs/status/coverage.md | 126 ++++++++++++++++++++++++++++++++++++++++ docs/status/metrics.md | 2 + 3 files changed, 129 insertions(+) create mode 100644 docs/status/coverage.md diff --git a/docs/index.md b/docs/index.md index 897c9bd..be433d5 100644 --- a/docs/index.md +++ b/docs/index.md @@ -39,3 +39,4 @@ - [FAQ](FAQ.md) — common questions and answers - [Migration from v2](migration-from-v2.md) — what changed from graph-v2 and how to migrate - [Status & Metrics](status/metrics.md) — canonical counts and implementation matrix +- [Code Coverage](status/coverage.md) — line and function coverage report (95.8% lines, 92.0% functions) diff --git a/docs/status/coverage.md b/docs/status/coverage.md new file mode 100644 index 0000000..9872b8d --- /dev/null +++ b/docs/status/coverage.md @@ -0,0 +1,126 @@ +# Code Coverage Report + +> **Generated:** 2026-02-21 | **Compiler:** GCC 15.1.0 | **Preset:** `linux-gcc-coverage` +> **Tests:** 4261 passed, 0 failed (100% pass rate) +> **Overall line coverage:** 95.8% (3300 / 3446 lines) +> **Overall function coverage:** 92.0% (27193 / 29567 functions) + +--- + +## Summary by Category + +| Category | Lines Hit | Lines Total | Line Coverage | +|----------|-----------|-------------|---------------| +| Adjacency list infrastructure | 321 | 321 | **100.0%** | +| Algorithms | 739 | 788 | **93.8%** | +| Containers | 902 | 962 | **93.8%** | +| Detail / CPOs | 21 | 21 | **100.0%** | +| Edge list | 15 | 16 | **93.8%** | +| Views | 1302 | 1336 | **97.5%** | + +--- + +## Detailed File Coverage + +### Adjacency List Infrastructure (`adj_list/`) + +All adjacency list descriptor and CPO support headers reach **100% line coverage**. + +| File | Lines Hit / Total | Line % | Funcs Hit / Total | Func % | +|------|-------------------|--------|-------------------|--------| +| `descriptor_traits.hpp` | 10 / 10 | 100.0% | 8 / 8 | 100.0% | +| `detail/graph_cpo.hpp` | 135 / 135 | 100.0% | 4250 / 4253 | 99.9% | +| `edge_descriptor.hpp` | 74 / 74 | 100.0% | 1205 / 1205 | 100.0% | +| `edge_descriptor_view.hpp` | 37 / 37 | 100.0% | 2291 / 2292 | 100.0% | +| `vertex_descriptor.hpp` | 35 / 35 | 100.0% | 1305 / 1306 | 99.9% | +| `vertex_descriptor_view.hpp` | 30 / 30 | 100.0% | 2321 / 2390 | 97.1% | + +### Algorithms (`algorithm/`) + +| File | Lines Hit / Total | Line % | Funcs Hit / Total | Func % | +|------|-------------------|--------|-------------------|--------| +| `articulation_points.hpp` | 53 / 53 | 100.0% | 2 / 2 | 100.0% | +| `bellman_ford_shortest_paths.hpp` | 54 / 63 | 85.7% | 31 / 31 | 100.0% | +| `biconnected_components.hpp` | 68 / 68 | 100.0% | 4 / 4 | 100.0% | +| `breadth_first_search.hpp` | 29 / 29 | 100.0% | 12 / 12 | 100.0% | +| `connected_components.hpp` | 123 / 137 | 89.8% | 10 / 10 | 100.0% | +| `depth_first_search.hpp` | 38 / 38 | 100.0% | 5 / 5 | 100.0% | +| `dijkstra_shortest_paths.hpp` | 54 / 65 | 83.1% | 32 / 36 | 88.9% | +| `jaccard.hpp` | 24 / 24 | 100.0% | 5 / 5 | 100.0% | +| `label_propagation.hpp` | 66 / 69 | 95.7% | 3 / 3 | 100.0% | +| `mis.hpp` | 25 / 28 | 89.3% | 1 / 1 | 100.0% | +| `mst.hpp` | 117 / 126 | 92.9% | 24 / 24 | 100.0% | +| `tc.hpp` | 27 / 27 | 100.0% | 2 / 2 | 100.0% | +| `topological_sort.hpp` | 52 / 52 | 100.0% | 7 / 7 | 100.0% | +| `traversal_common.hpp` | 9 / 9 | 100.0% | 3 / 3 | 100.0% | + +### Containers (`container/`) + +| File | Lines Hit / Total | Line % | Funcs Hit / Total | Func % | +|------|-------------------|--------|-------------------|--------| +| `compressed_graph.hpp` | 198 / 223 | 88.8% | 580 / 606 | 95.7% | +| `container_utility.hpp` | 6 / 6 | 100.0% | 382 / 382 | 100.0% | +| `detail/undirected_adjacency_list_impl.hpp` | 312 / 321 | 97.2% | 158 / 163 | 96.9% | +| `dynamic_graph.hpp` | 286 / 311 | 92.0% | 11429 / 13626 | 83.9% | +| `undirected_adjacency_list.hpp` | 100 / 101 | 99.0% | 126 / 128 | 98.4% | + +### Views (`views/`) + +| File | Lines Hit / Total | Line % | Funcs Hit / Total | Func % | +|------|-------------------|--------|-------------------|--------| +| `adaptors.hpp` | 109 / 109 | 100.0% | 85 / 85 | 100.0% | +| `bfs.hpp` | 211 / 227 | 93.0% | 146 / 157 | 93.0% | +| `dfs.hpp` | 225 / 239 | 94.1% | 257 / 268 | 95.9% | +| `edgelist.hpp` | 219 / 221 | 99.1% | 556 / 578 | 96.2% | +| `incidence.hpp` | 89 / 89 | 100.0% | 463 / 469 | 98.7% | +| `neighbors.hpp` | 102 / 102 | 100.0% | 282 / 286 | 98.6% | +| `search_base.hpp` | 5 / 5 | 100.0% | 11 / 11 | 100.0% | +| `topological_sort.hpp` | 230 / 232 | 99.1% | 248 / 256 | 96.9% | +| `vertexlist.hpp` | 112 / 112 | 100.0% | 467 / 469 | 99.6% | + +### Other + +| File | Lines Hit / Total | Line % | Funcs Hit / Total | Func % | +|------|-------------------|--------|-------------------|--------| +| `detail/edge_cpo.hpp` | 21 / 21 | 100.0% | 461 / 461 | 100.0% | +| `edge_list/edge_list_descriptor.hpp` | 15 / 16 | 93.8% | 21 / 21 | 100.0% | +| `graph_info.hpp` | 0 / 2 | 0.0% | 0 / 2 | 0.0% | + +--- + +## Coverage Gaps + +Files below 90% line coverage, ranked by gap size: + +| File | Line % | Lines Missed | Notes | +|------|--------|--------------|-------| +| `graph_info.hpp` | 0.0% | 2 | Metadata-only header; no executable paths exercised | +| `dijkstra_shortest_paths.hpp` | 83.1% | 11 | Some overload / error-path branches not reached | +| `bellman_ford_shortest_paths.hpp` | 85.7% | 9 | Negative-cycle detection path not fully exercised | +| `compressed_graph.hpp` | 88.8% | 25 | Some construction / accessor overloads untested | +| `connected_components.hpp` | 89.8% | 14 | Sparse-graph and edge-case branches | +| `mis.hpp` | 89.3% | 3 | Minor branch not reached | + +--- + +## How to Reproduce + +```bash +# Configure with coverage preset +cmake --preset linux-gcc-coverage + +# Build +cmake --build --preset linux-gcc-coverage + +# Run tests +ctest --preset linux-gcc-coverage + +# Generate HTML report (output in build/linux-gcc-coverage/coverage_html/) +cd build/linux-gcc-coverage +lcov --directory . --capture --output-file coverage.info --ignore-errors mismatch +lcov --remove coverage.info '/usr/*' '/opt/*' '*/tests/*' '*/build/*' '*/_deps/*' \ + --output-file coverage.info --ignore-errors mismatch +genhtml coverage.info --output-directory coverage_html --ignore-errors mismatch +``` + +Open `build/linux-gcc-coverage/coverage_html/index.html` in a browser for the full interactive report. diff --git a/docs/status/metrics.md b/docs/status/metrics.md index 53191ab..25551b5 100644 --- a/docs/status/metrics.md +++ b/docs/status/metrics.md @@ -13,6 +13,8 @@ | Trait combinations | 26 | `include/graph/container/traits/` | | Test count | 4261 | `ctest --preset linux-gcc-debug` (100% pass, 2026-02-19) | | C++ standard | C++20 | `CMakeLists.txt` line 26 | +| Line coverage | 95.8% (3300 / 3446) | `docs/status/coverage.md` (2026-02-21) | +| Function coverage | 92.0% (27193 / 29567) | `docs/status/coverage.md` (2026-02-21) | | License | BSL-1.0 | `LICENSE` | | CMake minimum | 3.20 | `CMakeLists.txt` line 1 | | Consumer target | `graph::graph3` | `cmake/InstallConfig.cmake` (`find_package(graph3)`) | From ef00d35c6c7838849391ab0162e281ae66a6b822 Mon Sep 17 00:00:00 2001 From: Phil Ratzloff Date: Sat, 21 Feb 2026 19:36:41 -0500 Subject: [PATCH 4/8] Reorder undirected_adjacency_list template params to to match dynamic_graph and compressed_graph - Swap VV,EV order to EV,VV in undirected_adjacency_list and all related classes - Update impl, api headers, tests, and documentation - Update incoming_edges_design.md with alias retention decision (Appendix D) --- agents/doc_refactor_strategy.md | 2 +- agents/incoming_edges_design.md | 49 + docs/user-guide/containers.md | 9 +- .../detail/undirected_adjacency_list_api.hpp | 660 +++++----- .../detail/undirected_adjacency_list_impl.hpp | 1064 ++++++++--------- .../container/undirected_adjacency_list.hpp | 254 ++-- .../test_undirected_adjacency_list.cpp | 4 +- 7 files changed, 1044 insertions(+), 998 deletions(-) diff --git a/agents/doc_refactor_strategy.md b/agents/doc_refactor_strategy.md index a94fe44..3ec0b8b 100644 --- a/agents/doc_refactor_strategy.md +++ b/agents/doc_refactor_strategy.md @@ -251,7 +251,7 @@ The `docs/user-guide/containers.md` page must include: |-----------|---------|------------|----------| | `dynamic_graph` | Traits-configured vertex and edge containers | Mutable | General purpose, flexible container choice | | `compressed_graph` | CSR (compressed sparse row) | Immutable after construction | Read-only, high-performance, memory-compact | -| `undirected_adjacency_list` | Dual doubly-linked lists per edge | Mutable, O(1) edge removal | Undirected graphs, frequent edge insertion/removal | +| `undirected_adjacency_list` | Dual doubly-linked lists per edge | Mutable, O(1) edge removal | Undirected graphs, frequent edge insertion/removal | **Full `dynamic_graph` container matrix (26 combinations):** diff --git a/agents/incoming_edges_design.md b/agents/incoming_edges_design.md index 71435db..8c61a98 100644 --- a/agents/incoming_edges_design.md +++ b/agents/incoming_edges_design.md @@ -65,6 +65,10 @@ existing CPOs. These are convenience only — not required: These are defined as inline constexpr CPO aliases or thin wrapper functions in `graph::adj_list` and re-exported to `graph::`. +> **Design decision (2026-02-21):** These aliases are **retained**. See +> [Appendix D](#appendix-d-out_-alias-retention-decision) for the full trade-off +> analysis. + ### 3.3 New incoming names | New Name | Meaning | @@ -631,3 +635,48 @@ on incoming edges — not `edge_value()`. This means: - Algorithms that need edge properties on incoming edges (rare) can add their own `requires edge_value(g, in_edge_t{})` constraint. - The undirected case (`in_edge_t == edge_t`) is the zero-cost happy path. + +## Appendix D: `out_` Alias Retention Decision + +**Date:** 2026-02-21 + +### Question + +Should the library provide `out_edges`, `out_degree`, `find_out_edge`, +`out_incidence`, `out_neighbors` (and their `basic_` variants) as aliases for the +existing outgoing CPOs/views, or should it omit them entirely? + +### Arguments for removing the aliases + +| # | Argument | +|---|---| +| R1 | `edges()` already means "outgoing" and always has — adding `out_edges()` is redundant and inflates the API surface. | +| R2 | Two spellings for the same operation create ambiguity: users must learn that `out_edges(g, u)` and `edges(g, u)` are identical. | +| R3 | Aliases clutter autocomplete, documentation tables, and error messages. | +| R4 | No existing user code ever spells `out_edges()` today, so removing the aliases breaks nobody. | +| R5 | If a future rename is ever desired (`edges` → `out_edges`), aliases make that rename harder because both names are already established. | + +### Arguments for keeping the aliases + +| # | Argument | +|---|---| +| K1 | **Symmetry with `in_edges`:** When a codebase uses `in_edges()` alongside `edges()`, the lack of an `out_` counterpart is visually jarring. `out_edges` / `in_edges` reads as a matched pair. | +| K2 | **Self-documenting code:** `out_edges(g, u)` makes directionality explicit at the call site; `edges(g, u)` requires the reader to know the convention. | +| K3 | **Familiar vocabulary:** Boost.Graph, NetworkX, LEDA, and the P1709 proposal all provide an `out_edges` name. Users migrating from those libraries expect it. | +| K4 | **Zero runtime cost:** The aliases are `inline constexpr` references to the existing CPO objects — no code duplication, no template bloat, no additional overload resolution. | +| K5 | **Grep-ability:** Searching a codebase for `out_edges` immediately reveals all outgoing-edge access; searching for `edges` produces false positives from `in_edges`, `num_edges`, `has_edge`, etc. | +| K6 | **Non-breaking:** Aliases are purely additive. Users who prefer `edges()` continue to use it unchanged. | + +### Resolution + +**Keep all `out_` aliases** (CPOs and view factory functions) as designed in §3.2, +§4.3, and §8.3. + +The symmetry (K1), self-documentation (K2), and familiarity (K3) benefits outweigh +the API-surface concern (R1–R3). The aliases are zero-cost (K4) and non-breaking (K6). + +To mitigate confusion (R2), documentation will: +- Note that `out_edges(g, u)` is an alias for `edges(g, u)` wherever it appears. +- Use `edges()` as the primary spelling in algorithm implementations. +- Use `out_edges()` in examples that also use `in_edges()`, to keep the pairing + visually clear. diff --git a/docs/user-guide/containers.md b/docs/user-guide/containers.md index 4d67993..c532df3 100644 --- a/docs/user-guide/containers.md +++ b/docs/user-guide/containers.md @@ -362,8 +362,8 @@ for (auto&& u : graph::vertices(g)) { #include namespace graph::container { -template class VContainer = std::vector, @@ -405,16 +405,13 @@ endpoint). Use `edges_size() / 2` for the unique edge count. | Parameter | Default | Description | |-----------|---------|-------------| -| `VV` | `void` | Vertex value type | | `EV` | `void` | Edge value type | +| `VV` | `void` | Vertex value type | | `GV` | `void` | Graph value type | | `VId` | `uint32_t` | Vertex ID type (integral) | | `VContainer` | `std::vector` | Vertex storage container template | | `Alloc` | `std::allocator` | Allocator | -> **Note:** The template parameter order is `VV, EV, GV` (vertex-first), which -> differs from `dynamic_graph` and `compressed_graph` (`EV, VV, GV`). - --- ## 4. Range-of-Ranges Graphs (No Library Graph Container Required) diff --git a/include/graph/container/detail/undirected_adjacency_list_api.hpp b/include/graph/container/detail/undirected_adjacency_list_api.hpp index 822d9bf..0508748 100644 --- a/include/graph/container/detail/undirected_adjacency_list_api.hpp +++ b/include/graph/container/detail/undirected_adjacency_list_api.hpp @@ -71,54 +71,54 @@ using const_neighbor_range_t = typename G::const_neighbor_range; // // Uniform API: Common functions (accepts graph, vertex and edge) // -template class VContainer, typename Alloc> -constexpr auto graph_value(const undirected_adjacency_list& g) - -> const graph_value_t>& { +constexpr auto graph_value(const undirected_adjacency_list& g) + -> const graph_value_t>& { return user_value(g); } // // API vertex functions // -template class VContainer, typename Alloc> -constexpr auto vertex_id(const undirected_adjacency_list& g, - const_vertex_iterator_t> u) - -> vertex_id_t> { - return static_cast>>(u - +constexpr auto vertex_id(const undirected_adjacency_list& g, + const_vertex_iterator_t> u) + -> vertex_id_t> { + return static_cast>>(u - g.vertices().begin()); } -template class VContainer, typename Alloc> -constexpr auto vertex_value(undirected_adjacency_list& g, - vertex_iterator_t> u) - -> vertex_value_t>& { +constexpr auto vertex_value(undirected_adjacency_list& g, + vertex_iterator_t> u) + -> vertex_value_t>& { return user_value(*u); } -template class VContainer, typename Alloc> -constexpr auto vertex_value(const undirected_adjacency_list& g, - const_vertex_iterator_t> u) - -> const vertex_value_t>& { +constexpr auto vertex_value(const undirected_adjacency_list& g, + const_vertex_iterator_t> u) + -> const vertex_value_t>& { return user_value(*u); } @@ -128,128 +128,128 @@ constexpr auto vertex_value(const undirected_adjacency_list class VContainer, typename Alloc> -constexpr auto edge_id(const undirected_adjacency_list& g, - const_edge_iterator_t> uv) - -> edge_id_t> { +constexpr auto edge_id(const undirected_adjacency_list& g, + const_edge_iterator_t> uv) + -> edge_id_t> { return uv.edge_id(g); } -template class VContainer, typename Alloc> -constexpr auto edge_id(const undirected_adjacency_list& g, - const_vertex_iterator_t> u, - const_vertex_iterator_t> v) - -> edge_id_t> { - return edge_id_t>(vertex_id(g, u), vertex_id(g, v)); +constexpr auto edge_id(const undirected_adjacency_list& g, + const_vertex_iterator_t> u, + const_vertex_iterator_t> v) + -> edge_id_t> { + return edge_id_t>(vertex_id(g, u), vertex_id(g, v)); } -template class VContainer, typename Alloc> -constexpr auto edge_value(undirected_adjacency_list& g, - edge_iterator_t> uv) - -> edge_value_t>& { +constexpr auto edge_value(undirected_adjacency_list& g, + edge_iterator_t> uv) + -> edge_value_t>& { return user_value(*uv); } -template class VContainer, typename Alloc> -constexpr auto edge_value(const undirected_adjacency_list& g, - const_edge_iterator_t> uv) - -> const edge_value_t>& { +constexpr auto edge_value(const undirected_adjacency_list& g, + const_edge_iterator_t> uv) + -> const edge_value_t>& { return user_value(*uv); } -template class VContainer, typename Alloc> -constexpr auto vertex(undirected_adjacency_list& g, - edge_iterator_t> uv, - const_vertex_iterator_t> source) - -> vertex_iterator_t> { +constexpr auto vertex(undirected_adjacency_list& g, + edge_iterator_t> uv, + const_vertex_iterator_t> source) + -> vertex_iterator_t> { return uv->other_vertex(g, source); } -template class VContainer, typename Alloc> -constexpr auto vertex(const undirected_adjacency_list& g, - const_edge_iterator_t> uv, - const_vertex_iterator_t> source) - -> const_vertex_iterator_t> { +constexpr auto vertex(const undirected_adjacency_list& g, + const_edge_iterator_t> uv, + const_vertex_iterator_t> source) + -> const_vertex_iterator_t> { return uv->other_vertex(g, source); } -template class VContainer, typename Alloc> -constexpr auto vertex(undirected_adjacency_list& g, - edge_iterator_t> uv, - vertex_id_t> source_id) - -> vertex_iterator_t> { +constexpr auto vertex(undirected_adjacency_list& g, + edge_iterator_t> uv, + vertex_id_t> source_id) + -> vertex_iterator_t> { return uv->other_vertex(g, source_id); } -template class VContainer, typename Alloc> -constexpr auto vertex(const undirected_adjacency_list& g, - const_edge_iterator_t> uv, - vertex_id_t> source_id) - -> const_vertex_iterator_t> { +constexpr auto vertex(const undirected_adjacency_list& g, + const_edge_iterator_t> uv, + vertex_id_t> source_id) + -> const_vertex_iterator_t> { return uv->other_vertex(g, source_id); } -template class VContainer, typename Alloc> -constexpr auto vertex_id(const undirected_adjacency_list& g, - const_edge_iterator_t> uv, - const_vertex_iterator_t> source) - -> vertex_id_t> { +constexpr auto vertex_id(const undirected_adjacency_list& g, + const_edge_iterator_t> uv, + const_vertex_iterator_t> source) + -> vertex_id_t> { return uv->other_vertex_id(g, source); } -template class VContainer, typename Alloc> -constexpr auto vertex_id(const undirected_adjacency_list& g, - const_edge_iterator_t> uv, - vertex_id_t> source_id) - -> vertex_id_t> { +constexpr auto vertex_id(const undirected_adjacency_list& g, + const_edge_iterator_t> uv, + vertex_id_t> source_id) + -> vertex_id_t> { return uv->other_vertex_id(g, source_id); } @@ -259,83 +259,83 @@ constexpr auto vertex_id(const undirected_adjacency_list class VContainer, typename Alloc> -constexpr auto vertices(undirected_adjacency_list& g) - -> vertex_range_t> { +constexpr auto vertices(undirected_adjacency_list& g) + -> vertex_range_t> { return g.vertices(); } -template class VContainer, typename Alloc> -constexpr auto vertices(const undirected_adjacency_list& g) - -> const_vertex_range_t> { +constexpr auto vertices(const undirected_adjacency_list& g) + -> const_vertex_range_t> { return g.vertices(); } -template class VContainer, typename Alloc> -constexpr auto try_find_vertex(undirected_adjacency_list& g, - vertex_id_t> id) - -> vertex_iterator_t> { +constexpr auto try_find_vertex(undirected_adjacency_list& g, + vertex_id_t> id) + -> vertex_iterator_t> { return g.try_find_vertex(id); } -template class VContainer, typename Alloc> -constexpr auto try_find_vertex(const undirected_adjacency_list& g, - vertex_id_t> id) - -> const_vertex_iterator_t> { +constexpr auto try_find_vertex(const undirected_adjacency_list& g, + vertex_id_t> id) + -> const_vertex_iterator_t> { return g.try_find_vertex(id); } -template class VContainer, typename Alloc> -void reserve_vertices(undirected_adjacency_list& g, - vertex_size_t> n) { +void reserve_vertices(undirected_adjacency_list& g, + vertex_size_t> n) { g.reserve_vertices(n); } -template class VContainer, typename Alloc> -void resize_vertices(undirected_adjacency_list& g, - vertex_size_t> n) { +void resize_vertices(undirected_adjacency_list& g, + vertex_size_t> n) { g.resize(n); } -template class VContainer, typename Alloc> -void resize_vertices(undirected_adjacency_list& g, - vertex_size_t> n, - const vertex_value_t>& val) { +void resize_vertices(undirected_adjacency_list& g, + vertex_size_t> n, + const vertex_value_t>& val) { g.resize(n, val); } @@ -344,38 +344,38 @@ void resize_vertices(undirected_adjacency_list class VContainer, typename Alloc> -constexpr auto edges(undirected_adjacency_list& g) - -> edge_range_t> { +constexpr auto edges(undirected_adjacency_list& g) + -> edge_range_t> { return g.edges(); } -template class VContainer, typename Alloc> -constexpr auto edges(const undirected_adjacency_list& g) - -> const_edge_range_t> { +constexpr auto edges(const undirected_adjacency_list& g) + -> const_edge_range_t> { return g.edges(); } -template class VContainer, typename Alloc> -constexpr auto find_edge(undirected_adjacency_list& g, - vertex_iterator_t> u, - vertex_iterator_t> v) - -> edge_iterator_t> { +constexpr auto find_edge(undirected_adjacency_list& g, + vertex_iterator_t> u, + vertex_iterator_t> v) + -> edge_iterator_t> { auto e = edges(g); for (auto uv = e.begin(); uv != e.end(); ++uv) if (vertex(g, uv, u) == v) @@ -383,16 +383,16 @@ constexpr auto find_edge(undirected_adjacency_list class VContainer, typename Alloc> -constexpr auto find_edge(const undirected_adjacency_list& g, - const_vertex_iterator_t> u, - const_vertex_iterator_t> v) - -> const_edge_iterator_t> { +constexpr auto find_edge(const undirected_adjacency_list& g, + const_vertex_iterator_t> u, + const_vertex_iterator_t> v) + -> const_edge_iterator_t> { auto e = edges(g); for (auto uv = e.begin(); uv != e.end(); ++uv) if (vertex(g, uv, u) == v) @@ -400,29 +400,29 @@ constexpr auto find_edge(const undirected_adjacency_list class VContainer, typename Alloc> -constexpr auto find_edge(undirected_adjacency_list& g, - vertex_id_t> uid, - vertex_id_t> vid) - -> edge_iterator_t> { +constexpr auto find_edge(undirected_adjacency_list& g, + vertex_id_t> uid, + vertex_id_t> vid) + -> edge_iterator_t> { return find_edge(g, try_find_vertex(g, uid), try_find_vertex(g, vid)); } -template class VContainer, typename Alloc> -constexpr auto find_edge(const undirected_adjacency_list& g, - vertex_id_t> uid, - vertex_id_t> vid) - -> const_edge_iterator_t> { +constexpr auto find_edge(const undirected_adjacency_list& g, + vertex_id_t> uid, + vertex_id_t> vid) + -> const_edge_iterator_t> { return find_edge(g, try_find_vertex(g, uid), try_find_vertex(g, vid)); } @@ -431,141 +431,141 @@ constexpr auto find_edge(const undirected_adjacency_list class VContainer, typename Alloc> -constexpr auto vertex(undirected_adjacency_list& g, - vertex_edge_iterator_t> uv, - const_vertex_iterator_t> source) - -> vertex_iterator_t> { +constexpr auto vertex(undirected_adjacency_list& g, + vertex_edge_iterator_t> uv, + const_vertex_iterator_t> source) + -> vertex_iterator_t> { return uv->other_vertex(g, source); } -template class VContainer, typename Alloc> -constexpr auto vertex(const undirected_adjacency_list& g, - const_vertex_edge_iterator_t> uv, - const_vertex_iterator_t> source) - -> const_vertex_iterator_t> { +constexpr auto vertex(const undirected_adjacency_list& g, + const_vertex_edge_iterator_t> uv, + const_vertex_iterator_t> source) + -> const_vertex_iterator_t> { return uv->other_vertex(g, source); } -template class VContainer, typename Alloc> -constexpr auto vertex(undirected_adjacency_list& g, - vertex_edge_iterator_t> uv, - vertex_id_t> source_id) - -> vertex_iterator_t> { +constexpr auto vertex(undirected_adjacency_list& g, + vertex_edge_iterator_t> uv, + vertex_id_t> source_id) + -> vertex_iterator_t> { return uv->other_vertex(g, source_id); } -template class VContainer, typename Alloc> -constexpr auto vertex(const undirected_adjacency_list& g, - const_vertex_edge_iterator_t> uv, - vertex_id_t> source_id) - -> const_vertex_iterator_t> { +constexpr auto vertex(const undirected_adjacency_list& g, + const_vertex_edge_iterator_t> uv, + vertex_id_t> source_id) + -> const_vertex_iterator_t> { return uv->other_vertex(g, source_id); } -template class VContainer, typename Alloc> -constexpr auto vertex_id(const undirected_adjacency_list& g, - const_vertex_edge_iterator_t> uv, - const_vertex_iterator_t> source) - -> vertex_id_t> { +constexpr auto vertex_id(const undirected_adjacency_list& g, + const_vertex_edge_iterator_t> uv, + const_vertex_iterator_t> source) + -> vertex_id_t> { return uv->other_vertex_id(g, source); } -template class VContainer, typename Alloc> -constexpr auto vertex_id(const undirected_adjacency_list& g, - const_vertex_edge_iterator_t> uv, - vertex_id_t> source_id) - -> vertex_id_t> { +constexpr auto vertex_id(const undirected_adjacency_list& g, + const_vertex_edge_iterator_t> uv, + vertex_id_t> source_id) + -> vertex_id_t> { return uv->other_vertex_id(g, source_id); } -template class VContainer, typename Alloc> -constexpr auto edge_value(undirected_adjacency_list& g, - vertex_edge_iterator_t> uv) - -> edge_value_t>& { +constexpr auto edge_value(undirected_adjacency_list& g, + vertex_edge_iterator_t> uv) + -> edge_value_t>& { return user_value(*uv); } -template class VContainer, typename Alloc> constexpr auto -edge_value(const undirected_adjacency_list& g, - const_vertex_edge_iterator_t> uv) - -> const edge_value_t>& { +edge_value(const undirected_adjacency_list& g, + const_vertex_edge_iterator_t> uv) + -> const edge_value_t>& { return user_value(*uv); } -template class VContainer, typename Alloc> -constexpr auto edges(undirected_adjacency_list& g, - vertex_iterator_t> u) - -> vertex_edge_range_t> { +constexpr auto edges(undirected_adjacency_list& g, + vertex_iterator_t> u) + -> vertex_edge_range_t> { return u->edges(g, vertex_id(g, u)); } -template class VContainer, typename Alloc> -constexpr auto edges(const undirected_adjacency_list& g, - const_vertex_iterator_t> u) - -> const_vertex_edge_range_t> { +constexpr auto edges(const undirected_adjacency_list& g, + const_vertex_iterator_t> u) + -> const_vertex_edge_range_t> { return u->edges(g, vertex_id(g, u)); } -template class VContainer, typename Alloc> -constexpr auto try_find_vertex_edge(undirected_adjacency_list& g, - vertex_iterator_t> u, - vertex_iterator_t> v) - -> vertex_edge_iterator_t> { +constexpr auto try_find_vertex_edge(undirected_adjacency_list& g, + vertex_iterator_t> u, + vertex_iterator_t> v) + -> vertex_edge_iterator_t> { auto e = edges(g, u); for (auto uv = begin(e); uv != end(e); ++uv) if (vertex(g, uv, u) == v) @@ -573,17 +573,17 @@ constexpr auto try_find_vertex_edge(undirected_adjacency_list class VContainer, typename Alloc> constexpr auto -try_find_vertex_edge(const undirected_adjacency_list& g, - const vertex_iterator_t>& u, - const vertex_iterator_t>& v) - -> const_vertex_edge_iterator_t> { +try_find_vertex_edge(const undirected_adjacency_list& g, + const vertex_iterator_t>& u, + const vertex_iterator_t>& v) + -> const_vertex_edge_iterator_t> { auto e = edges(g, u); for (auto uv = begin(e); uv != end(e); ++uv) if (vertex(g, uv, u) == v) @@ -591,72 +591,72 @@ try_find_vertex_edge(const undirected_adjacency_list class VContainer, typename Alloc> -constexpr auto try_find_vertex_edge(undirected_adjacency_list& g, - vertex_id_t> uid, - vertex_id_t> vid) - -> vertex_edge_iterator_t> { +constexpr auto try_find_vertex_edge(undirected_adjacency_list& g, + vertex_id_t> uid, + vertex_id_t> vid) + -> vertex_edge_iterator_t> { return try_find_vertex_edge(g, try_find_vertex(g, uid), try_find_vertex(g, vid)); } -template class VContainer, typename Alloc> -constexpr auto try_find_vertex_edge(const undirected_adjacency_list& g, - vertex_id_t> uid, - vertex_id_t> vid) - -> const_vertex_edge_iterator_t> { +constexpr auto try_find_vertex_edge(const undirected_adjacency_list& g, + vertex_id_t> uid, + vertex_id_t> vid) + -> const_vertex_edge_iterator_t> { return try_find_vertex_edge(g, try_find_vertex(g, uid), try_find_vertex(g, vid)); } -template class VContainer, typename Alloc> -constexpr auto erase_edge(undirected_adjacency_list& g, - vertex_edge_iterator_t> uv) - -> vertex_edge_iterator_t> { - edge_t>* uv_ptr = &*uv; +constexpr auto erase_edge(undirected_adjacency_list& g, + vertex_edge_iterator_t> uv) + -> vertex_edge_iterator_t> { + edge_t>* uv_ptr = &*uv; ++uv; delete uv_ptr; return uv; } -template class VContainer, typename Alloc> -constexpr auto erase_edges(undirected_adjacency_list& g, - vertex_edge_range_t> uv_rng) - -> vertex_edge_iterator_t> { +constexpr auto erase_edges(undirected_adjacency_list& g, + vertex_edge_range_t> uv_rng) + -> vertex_edge_iterator_t> { - vertex_edge_iterator_t> uv = ranges::begin(uv_rng); + vertex_edge_iterator_t> uv = ranges::begin(uv_rng); while (uv != ranges::end(uv_rng)) uv = erase_edge(g, uv); return uv; } -template class VContainer, typename Alloc> -constexpr void clear_edges(undirected_adjacency_list& g, - vertex_iterator_t> u) { +constexpr void clear_edges(undirected_adjacency_list& g, + vertex_iterator_t> u) { u->clear_edges(g); } @@ -664,39 +664,39 @@ constexpr void clear_edges(undirected_adjacency_list class VContainer, typename Alloc> -constexpr auto vertex_id(const undirected_adjacency_list& g, - const_neighbor_iterator_t> u) - -> vertex_id_t> { +constexpr auto vertex_id(const undirected_adjacency_list& g, + const_neighbor_iterator_t> u) + -> vertex_id_t> { return u.other_vertex_id(); } -template class VContainer, typename Alloc> -constexpr auto vertices(undirected_adjacency_list& g, - vertex_iterator_t> u) - -> neighbor_range_t> { +constexpr auto vertices(undirected_adjacency_list& g, + vertex_iterator_t> u) + -> neighbor_range_t> { return u->vertices(g, vertex_id(g, u)); } -template class VContainer, typename Alloc> -constexpr auto vertices(const undirected_adjacency_list& g, - const_vertex_iterator_t> u) - -> const_neighbor_range_t> { +constexpr auto vertices(const undirected_adjacency_list& g, + const_vertex_iterator_t> u) + -> const_neighbor_range_t> { return u->vertices(g, vertex_id(g, u)); } @@ -709,152 +709,152 @@ constexpr auto vertices(const undirected_adjacency_list class VContainer, typename Alloc> -constexpr auto target_vertex(undirected_adjacency_list& g, - edge_iterator_t> uv) - -> vertex_iterator_t> { +constexpr auto target_vertex(undirected_adjacency_list& g, + edge_iterator_t> uv) + -> vertex_iterator_t> { return uv->target_vertex(g); } -template class VContainer, typename Alloc> -constexpr auto target_vertex(const undirected_adjacency_list& g, - const_edge_iterator_t> uv) - -> const_vertex_iterator_t> { +constexpr auto target_vertex(const undirected_adjacency_list& g, + const_edge_iterator_t> uv) + -> const_vertex_iterator_t> { return uv->target_vertex(g); } -template class VContainer, typename Alloc> -constexpr auto target_vertex(undirected_adjacency_list& g, - vertex_edge_iterator_t> uv) - -> vertex_iterator_t> { +constexpr auto target_vertex(undirected_adjacency_list& g, + vertex_edge_iterator_t> uv) + -> vertex_iterator_t> { return uv->target_vertex(g); } -template class VContainer, typename Alloc> constexpr auto -target_vertex(const undirected_adjacency_list& g, - const_vertex_edge_iterator_t> uv) - -> const_vertex_iterator_t> { +target_vertex(const undirected_adjacency_list& g, + const_vertex_edge_iterator_t> uv) + -> const_vertex_iterator_t> { return uv->target_vertex(g); } -template class VContainer, typename Alloc> -constexpr auto target_vertex_id(const undirected_adjacency_list& g, - const_edge_iterator_t> uv) - -> vertex_id_t> { +constexpr auto target_vertex_id(const undirected_adjacency_list& g, + const_edge_iterator_t> uv) + -> vertex_id_t> { return uv->target_vertex_id(g); } -template class VContainer, typename Alloc> constexpr auto -target_vertex_id(const undirected_adjacency_list& g, - const_vertex_edge_iterator_t> uv) - -> vertex_id_t> { +target_vertex_id(const undirected_adjacency_list& g, + const_vertex_edge_iterator_t> uv) + -> vertex_id_t> { return uv->target_vertex_id(g); } -template class VContainer, typename Alloc> -constexpr auto source_vertex(undirected_adjacency_list& g, - edge_iterator_t> uv) - -> vertex_iterator_t> { +constexpr auto source_vertex(undirected_adjacency_list& g, + edge_iterator_t> uv) + -> vertex_iterator_t> { return uv->source_vertex(g); } -template class VContainer, typename Alloc> -constexpr auto source_vertex(const undirected_adjacency_list& g, - const_edge_iterator_t> uv) - -> const_vertex_iterator_t> { +constexpr auto source_vertex(const undirected_adjacency_list& g, + const_edge_iterator_t> uv) + -> const_vertex_iterator_t> { return uv->source_vertex(g); } -template class VContainer, typename Alloc> -constexpr auto source_vertex(undirected_adjacency_list& g, - vertex_edge_iterator_t> uv) - -> vertex_iterator_t> { +constexpr auto source_vertex(undirected_adjacency_list& g, + vertex_edge_iterator_t> uv) + -> vertex_iterator_t> { return uv->source_vertex(g); } -template class VContainer, typename Alloc> constexpr auto -source_vertex(const undirected_adjacency_list& g, - const_vertex_edge_iterator_t> uv) - -> const_vertex_iterator_t> { +source_vertex(const undirected_adjacency_list& g, + const_vertex_edge_iterator_t> uv) + -> const_vertex_iterator_t> { return uv->source_vertex(g); } -template class VContainer, typename Alloc> -constexpr auto source_vertex_id(const undirected_adjacency_list& g, - const_edge_iterator_t> uv) - -> vertex_id_t> { +constexpr auto source_vertex_id(const undirected_adjacency_list& g, + const_edge_iterator_t> uv) + -> vertex_id_t> { return uv->list_owner_id(); } -template class VContainer, typename Alloc> constexpr auto -source_vertex_id(const undirected_adjacency_list& g, - const_vertex_edge_iterator_t> uv) - -> vertex_id_t> { +source_vertex_id(const undirected_adjacency_list& g, + const_vertex_edge_iterator_t> uv) + -> vertex_id_t> { return uv->list_owner_id(); } @@ -867,23 +867,23 @@ source_vertex_id(const undirected_adjacency_list -constexpr auto vertex(undirected_adjacency_list& g, vertex_edge_iterator_t> uv) - -> vertex_iterator_t> { +template +constexpr auto vertex(undirected_adjacency_list& g, vertex_edge_iterator_t> uv) + -> vertex_iterator_t> { return uv->target_vertex(g); } -template -constexpr auto vertex(const undirected_adjacency_list& g, - const_vertex_edge_t> uv) - -> const_vertex_iterator_t> { +template +constexpr auto vertex(const undirected_adjacency_list& g, + const_vertex_edge_t> uv) + -> const_vertex_iterator_t> { return uv->target_vertex(g); } -template -constexpr auto vertex_id(const undirected_adjacency_list& g, - const_vertex_edge_t> uv) - -> vertex_id_t> { +template +constexpr auto vertex_id(const undirected_adjacency_list& g, + const_vertex_edge_t> uv) + -> vertex_id_t> { return uv->target_vertex_id(g); } # endif @@ -893,24 +893,24 @@ constexpr auto vertex_id(const undirected_adjacency_list& // API graph functions // -template class VContainer, typename Alloc> -bool contains_vertex(undirected_adjacency_list& g, - vertex_id_t> uid) { +bool contains_vertex(undirected_adjacency_list& g, + vertex_id_t> uid) { return uid >= 0 && uid < g.vertices().size(); } -template class VContainer, typename Alloc> -void clear(undirected_adjacency_list& g) { +void clear(undirected_adjacency_list& g) { g.clear(); } diff --git a/include/graph/container/detail/undirected_adjacency_list_impl.hpp b/include/graph/container/detail/undirected_adjacency_list_impl.hpp index 0055136..12c2ae0 100644 --- a/include/graph/container/detail/undirected_adjacency_list_impl.hpp +++ b/include/graph/container/detail/undirected_adjacency_list_impl.hpp @@ -14,85 +14,85 @@ namespace graph::container { /// // ual_vertex_edge_list::const_iterator -template class VContainer, typename Alloc> -typename ual_vertex_edge_list::const_iterator::reference -ual_vertex_edge_list::const_iterator::operator*() const { +typename ual_vertex_edge_list::const_iterator::reference +ual_vertex_edge_list::const_iterator::operator*() const { return *edge_; } -template class VContainer, typename Alloc> -typename ual_vertex_edge_list::const_iterator::pointer -ual_vertex_edge_list::const_iterator::operator->() const { +typename ual_vertex_edge_list::const_iterator::pointer +ual_vertex_edge_list::const_iterator::operator->() const { return edge_; } -template class VContainer, typename Alloc> -typename ual_vertex_edge_list::const_iterator& -ual_vertex_edge_list::const_iterator::operator++() { +typename ual_vertex_edge_list::const_iterator& +ual_vertex_edge_list::const_iterator::operator++() { advance(); return *this; } -template class VContainer, typename Alloc> -typename ual_vertex_edge_list::const_iterator -ual_vertex_edge_list::const_iterator::operator++(int) { +typename ual_vertex_edge_list::const_iterator +ual_vertex_edge_list::const_iterator::operator++(int) { const_iterator tmp(*this); ++*this; return tmp; } -template class VContainer, typename Alloc> -typename ual_vertex_edge_list::const_iterator& -ual_vertex_edge_list::const_iterator::operator--() { +typename ual_vertex_edge_list::const_iterator& +ual_vertex_edge_list::const_iterator::operator--() { retreat(); return *this; } -template class VContainer, typename Alloc> -typename ual_vertex_edge_list::const_iterator -ual_vertex_edge_list::const_iterator::operator--(int) { +typename ual_vertex_edge_list::const_iterator +ual_vertex_edge_list::const_iterator::operator--(int) { const_iterator tmp(*this); --*this; return tmp; } -template class VContainer, typename Alloc> -void ual_vertex_edge_list::const_iterator::advance() { +void ual_vertex_edge_list::const_iterator::advance() { edge_type* start_edge = edge_; // Remember where we started for cycle detection vertex_edge_list_inward_link_type& inward_link = *edge_; // in.vertex_id_ == this->vertex_id_; @@ -110,13 +110,13 @@ void ual_vertex_edge_list::const_iterator::a } } -template class VContainer, typename Alloc> -void ual_vertex_edge_list::const_iterator::retreat() { +void ual_vertex_edge_list::const_iterator::retreat() { if (edge_) { vertex_edge_list_inward_link_type& inward_link = *edge_; // in.vertex_id_ == this->vertex_id_; vertex_edge_list_outward_link_type& outward_link = *edge_; @@ -132,174 +132,174 @@ void ual_vertex_edge_list::const_iterator::r } } -template class VContainer, typename Alloc> -bool ual_vertex_edge_list::const_iterator::operator==( +bool ual_vertex_edge_list::const_iterator::operator==( const const_iterator& rhs) const noexcept { return edge_ == rhs.edge_; } -template class VContainer, typename Alloc> -bool ual_vertex_edge_list::const_iterator::operator!=( +bool ual_vertex_edge_list::const_iterator::operator!=( const const_iterator& rhs) const noexcept { return !(*this == rhs); } // ual_vertex_edge_list::iterator -template class VContainer, typename Alloc> -typename ual_vertex_edge_list::reference -ual_vertex_edge_list::iterator::operator*() const { +typename ual_vertex_edge_list::reference +ual_vertex_edge_list::iterator::operator*() const { return *this->edge_; } -template class VContainer, typename Alloc> -typename ual_vertex_edge_list::pointer -ual_vertex_edge_list::iterator::operator->() const { +typename ual_vertex_edge_list::pointer +ual_vertex_edge_list::iterator::operator->() const { return this->edge_; } -template class VContainer, typename Alloc> -typename ual_vertex_edge_list::iterator& -ual_vertex_edge_list::iterator::operator++() { +typename ual_vertex_edge_list::iterator& +ual_vertex_edge_list::iterator::operator++() { this->advance(); return *this; } -template class VContainer, typename Alloc> -typename ual_vertex_edge_list::iterator -ual_vertex_edge_list::iterator::operator++(int) { +typename ual_vertex_edge_list::iterator +ual_vertex_edge_list::iterator::operator++(int) { iterator tmp(*this); ++*this; return tmp; } -template class VContainer, typename Alloc> -typename ual_vertex_edge_list::iterator& -ual_vertex_edge_list::iterator::operator--() { +typename ual_vertex_edge_list::iterator& +ual_vertex_edge_list::iterator::operator--() { this->retreat(); return *this; } -template class VContainer, typename Alloc> -typename ual_vertex_edge_list::iterator -ual_vertex_edge_list::iterator::operator--(int) { +typename ual_vertex_edge_list::iterator +ual_vertex_edge_list::iterator::operator--(int) { iterator tmp(*this); --*this; return tmp; } // ual_vertex_edge_list -template class VContainer, typename Alloc> -typename ual_vertex_edge_list::size_type -ual_vertex_edge_list::size() const noexcept { +typename ual_vertex_edge_list::size_type +ual_vertex_edge_list::size() const noexcept { return size_; } -template class VContainer, typename Alloc> -bool ual_vertex_edge_list::empty() const noexcept { +bool ual_vertex_edge_list::empty() const noexcept { return size_ == 0; } -template class VContainer, typename Alloc> -typename ual_vertex_edge_list::edge_type& -ual_vertex_edge_list::front() { +typename ual_vertex_edge_list::edge_type& +ual_vertex_edge_list::front() { return *head_; } -template class VContainer, typename Alloc> -const typename ual_vertex_edge_list::edge_type& -ual_vertex_edge_list::front() const { +const typename ual_vertex_edge_list::edge_type& +ual_vertex_edge_list::front() const { return *head_; } -template class VContainer, typename Alloc> -typename ual_vertex_edge_list::edge_type& -ual_vertex_edge_list::back() { +typename ual_vertex_edge_list::edge_type& +ual_vertex_edge_list::back() { return *tail_; } -template class VContainer, typename Alloc> -const typename ual_vertex_edge_list::edge_type& -ual_vertex_edge_list::back() const { +const typename ual_vertex_edge_list::edge_type& +ual_vertex_edge_list::back() const { return *tail_; } -template class VContainer, typename Alloc> template -void ual_vertex_edge_list::link_front( - edge_type& uv, ual_vertex_edge_list_link& uv_link) { - using link_t = ual_vertex_edge_list_link; +void ual_vertex_edge_list::link_front( + edge_type& uv, ual_vertex_edge_list_link& uv_link) { + using link_t = ual_vertex_edge_list_link; if (head_) { link_t& head_link = *static_cast(head_); uv_link.next_ = head_; @@ -311,15 +311,15 @@ void ual_vertex_edge_list::link_front( ++size_; } -template class VContainer, typename Alloc> template -void ual_vertex_edge_list::link_back( - edge_type& uv, ual_vertex_edge_list_link& uv_link) { +void ual_vertex_edge_list::link_back( + edge_type& uv, ual_vertex_edge_list_link& uv_link) { if (tail_) { vertex_edge_list_inward_link_type& tail_inward_link = static_cast(*tail_); vertex_edge_list_outward_link_type& tail_outward_link = static_cast(*tail_); @@ -339,15 +339,15 @@ void ual_vertex_edge_list::link_back( ++size_; } -template class VContainer, typename Alloc> template -void ual_vertex_edge_list::unlink( - edge_type& uv, ual_vertex_edge_list_link& uv_link) { +void ual_vertex_edge_list::unlink( + edge_type& uv, ual_vertex_edge_list_link& uv_link) { if (uv_link.prev_) { vertex_edge_list_inward_link_type& prev_inward_link = @@ -392,89 +392,89 @@ void ual_vertex_edge_list::unlink( // don't hold during partial unlink. The invariant is restored after both unlinks. } -template class VContainer, typename Alloc> -typename ual_vertex_edge_list::iterator -ual_vertex_edge_list::begin(graph_type& g, vertex_id_type uid) noexcept { +typename ual_vertex_edge_list::iterator +ual_vertex_edge_list::begin(graph_type& g, vertex_id_type uid) noexcept { return iterator(g, uid, head_); } -template class VContainer, typename Alloc> -typename ual_vertex_edge_list::const_iterator -ual_vertex_edge_list::begin(const graph_type& g, +typename ual_vertex_edge_list::const_iterator +ual_vertex_edge_list::begin(const graph_type& g, vertex_id_type uid) const noexcept { return const_iterator(g, uid, head_); } -template class VContainer, typename Alloc> -typename ual_vertex_edge_list::const_iterator -ual_vertex_edge_list::cbegin(const graph_type& g, +typename ual_vertex_edge_list::const_iterator +ual_vertex_edge_list::cbegin(const graph_type& g, vertex_id_type uid) const noexcept { return const_iterator(g, uid, head_); } -template class VContainer, typename Alloc> -typename ual_vertex_edge_list::iterator -ual_vertex_edge_list::end(graph_type& g, vertex_id_type uid) noexcept { +typename ual_vertex_edge_list::iterator +ual_vertex_edge_list::end(graph_type& g, vertex_id_type uid) noexcept { return iterator(g, uid, nullptr); } -template class VContainer, typename Alloc> -typename ual_vertex_edge_list::const_iterator -ual_vertex_edge_list::end(const graph_type& g, vertex_id_type uid) const noexcept { +typename ual_vertex_edge_list::const_iterator +ual_vertex_edge_list::end(const graph_type& g, vertex_id_type uid) const noexcept { return const_iterator(g, uid, nullptr); } -template class VContainer, typename Alloc> -typename ual_vertex_edge_list::const_iterator -ual_vertex_edge_list::cend(const graph_type& g, vertex_id_type uid) const noexcept { +typename ual_vertex_edge_list::const_iterator +ual_vertex_edge_list::cend(const graph_type& g, vertex_id_type uid) const noexcept { return const_iterator(g, uid, nullptr); } -template class VContainer, typename Alloc> -typename ual_vertex_edge_list::edge_range -ual_vertex_edge_list::edges(graph_type& g, vertex_id_type uid) noexcept { +typename ual_vertex_edge_list::edge_range +ual_vertex_edge_list::edges(graph_type& g, vertex_id_type uid) noexcept { return {iterator(g, uid, head_), iterator(g, uid, nullptr), size_}; } -template class VContainer, typename Alloc> -typename ual_vertex_edge_list::const_edge_range -ual_vertex_edge_list::edges(const graph_type& g, +typename ual_vertex_edge_list::const_edge_range +ual_vertex_edge_list::edges(const graph_type& g, vertex_id_type uid) const noexcept { return {const_iterator(g, uid, head_), const_iterator(g, uid, nullptr), size_}; } @@ -482,24 +482,24 @@ ual_vertex_edge_list::edges(const graph_type ///------------------------------------------------------------------------------------- /// ual_edge /// -template class VContainer, typename Alloc> -ual_edge::ual_edge(graph_type& g, vertex_id_type uid, vertex_id_type vid) noexcept +ual_edge::ual_edge(graph_type& g, vertex_id_type uid, vertex_id_type vid) noexcept : base_value_type(), vertex_edge_list_inward_link_type(uid), vertex_edge_list_outward_link_type(vid) { link_back(g.vertices()[uid], g.vertices()[vid]); } -template class VContainer, typename Alloc> -ual_edge::ual_edge(graph_type& g, +ual_edge::ual_edge(graph_type& g, vertex_id_type uid, vertex_id_type vid, const edge_value_type& val) noexcept @@ -507,13 +507,13 @@ ual_edge::ual_edge(graph_type& g, link_back(g.vertices()[uid], g.vertices()[vid]); } -template class VContainer, typename Alloc> -ual_edge::ual_edge(graph_type& g, +ual_edge::ual_edge(graph_type& g, vertex_id_type uid, vertex_id_type vid, edge_value_type&& val) noexcept @@ -521,26 +521,26 @@ ual_edge::ual_edge(graph_type& g, link_back(g.vertices()[uid], g.vertices()[vid]); } -template class VContainer, typename Alloc> -ual_edge::ual_edge(graph_type& g, vertex_iterator ui, vertex_iterator vi) noexcept +ual_edge::ual_edge(graph_type& g, vertex_iterator ui, vertex_iterator vi) noexcept : base_value_type() , vertex_edge_list_inward_link_type(static_cast(ui - g.vertices().begin())) , vertex_edge_list_outward_link_type(static_cast(vi - g.vertices().begin())) { link_back(*ui, *vi); } -template class VContainer, typename Alloc> -ual_edge::ual_edge(graph_type& g, +ual_edge::ual_edge(graph_type& g, vertex_iterator ui, vertex_iterator vi, const edge_value_type& val) noexcept @@ -550,13 +550,13 @@ ual_edge::ual_edge(graph_type& g, link_back(*ui, *vi); } -template class VContainer, typename Alloc> -ual_edge::ual_edge(graph_type& g, +ual_edge::ual_edge(graph_type& g, vertex_iterator ui, vertex_iterator vi, edge_value_type&& val) noexcept @@ -566,13 +566,13 @@ ual_edge::ual_edge(graph_type& g, link_back(*ui, *vi); } -template class VContainer, typename Alloc> -ual_edge::~ual_edge() noexcept { +ual_edge::~ual_edge() noexcept { vertex_edge_list_outward_link_type& outward_link = *static_cast(this); assert(outward_link.prev() == nullptr && outward_link.next() == nullptr); // has edge been unlinked? @@ -580,184 +580,184 @@ ual_edge::~ual_edge() noexcept { assert(inward_link.prev() == nullptr && inward_link.next() == nullptr); // has edge been unlinked? } -template class VContainer, typename Alloc> -void ual_edge::link_front(vertex_type& u, vertex_type& v) noexcept { +void ual_edge::link_front(vertex_type& u, vertex_type& v) noexcept { u.edges_.link_front(*this, *static_cast(this)); v.edges_.link_front(*this, *static_cast(this)); } -template class VContainer, typename Alloc> -void ual_edge::link_back(vertex_type& u, vertex_type& v) noexcept { +void ual_edge::link_back(vertex_type& u, vertex_type& v) noexcept { u.edges_.link_back(*this, *static_cast(this)); v.edges_.link_back(*this, *static_cast(this)); } -template class VContainer, typename Alloc> -void ual_edge::unlink(vertex_type& u, vertex_type& v) noexcept { +void ual_edge::unlink(vertex_type& u, vertex_type& v) noexcept { u.edges_.unlink(*this, *static_cast(this)); v.edges_.unlink(*this, *static_cast(this)); } -template class VContainer, typename Alloc> -typename ual_edge::vertex_iterator -ual_edge::source(graph_type& g) noexcept { +typename ual_edge::vertex_iterator +ual_edge::source(graph_type& g) noexcept { return g.vertices().begin() + list_owner_id(); } -template class VContainer, typename Alloc> -typename ual_edge::const_vertex_iterator -ual_edge::source(const graph_type& g) const noexcept { +typename ual_edge::const_vertex_iterator +ual_edge::source(const graph_type& g) const noexcept { return static_cast(this)->vertex(g); } -template class VContainer, typename Alloc> -typename ual_edge::vertex_id_type -ual_edge::list_owner_id() const noexcept { +typename ual_edge::vertex_id_type +ual_edge::list_owner_id() const noexcept { return static_cast(this)->vertex_id(); } -template class VContainer, typename Alloc> -typename ual_edge::vertex_iterator -ual_edge::target(graph_type& g) noexcept { +typename ual_edge::vertex_iterator +ual_edge::target(graph_type& g) noexcept { return g.vertices().begin() + list_target_id(); } -template class VContainer, typename Alloc> -typename ual_edge::const_vertex_iterator -ual_edge::target(const graph_type& g) const noexcept { +typename ual_edge::const_vertex_iterator +ual_edge::target(const graph_type& g) const noexcept { return static_cast(this)->vertex(g); } -template class VContainer, typename Alloc> -typename ual_edge::vertex_id_type -ual_edge::list_target_id() const noexcept { +typename ual_edge::vertex_id_type +ual_edge::list_target_id() const noexcept { return static_cast(this)->vertex_id(); } -template class VContainer, typename Alloc> -typename ual_edge::vertex_iterator -ual_edge::other_vertex(graph_type& g, const_vertex_iterator other) noexcept { +typename ual_edge::vertex_iterator +ual_edge::other_vertex(graph_type& g, const_vertex_iterator other) noexcept { return other != source(g) ? source(g) : target(g); } -template class VContainer, typename Alloc> -typename ual_edge::const_vertex_iterator -ual_edge::other_vertex(const graph_type& g, +typename ual_edge::const_vertex_iterator +ual_edge::other_vertex(const graph_type& g, const_vertex_iterator other) const noexcept { return other != source(g) ? source(g) : target(g); } -template class VContainer, typename Alloc> -typename ual_edge::vertex_iterator -ual_edge::other_vertex(graph_type& g, vertex_id_type other_id) noexcept { +typename ual_edge::vertex_iterator +ual_edge::other_vertex(graph_type& g, vertex_id_type other_id) noexcept { return other_id != list_owner_id() ? source(g) : target(g); } -template class VContainer, typename Alloc> -typename ual_edge::const_vertex_iterator -ual_edge::other_vertex(const graph_type& g, +typename ual_edge::const_vertex_iterator +ual_edge::other_vertex(const graph_type& g, vertex_id_type other_id) const noexcept { return other_id != list_owner_id() ? source(g) : target(g); } -template class VContainer, typename Alloc> -typename ual_edge::vertex_id_type -ual_edge::other_vertex_id(const graph_type& g, +typename ual_edge::vertex_id_type +ual_edge::other_vertex_id(const graph_type& g, const_vertex_iterator other) const noexcept { return other != source(g) ? list_owner_id() : list_target_id(); } -template class VContainer, typename Alloc> -typename ual_edge::vertex_id_type -ual_edge::other_vertex_id([[maybe_unused]] const graph_type& g, +typename ual_edge::vertex_id_type +ual_edge::other_vertex_id([[maybe_unused]] const graph_type& g, vertex_id_type other_id) const noexcept { return other_id != list_owner_id() ? list_owner_id() : list_target_id(); } -template class VContainer, typename Alloc> -typename ual_edge::edge_id_type -ual_edge::edge_id(const graph_type& g) const noexcept { +typename ual_edge::edge_id_type +ual_edge::edge_id(const graph_type& g) const noexcept { return unordered_pair(list_owner_id(), list_target_id()); } @@ -765,201 +765,201 @@ ual_edge::edge_id(const graph_type& g) const ///------------------------------------------------------------------------------------- /// ual_vertex /// -template class VContainer, typename Alloc> -ual_vertex::ual_vertex([[maybe_unused]] vertex_set& vertices, +ual_vertex::ual_vertex([[maybe_unused]] vertex_set& vertices, [[maybe_unused]] vertex_index index) {} -template class VContainer, typename Alloc> -ual_vertex::ual_vertex([[maybe_unused]] vertex_set& vertices, +ual_vertex::ual_vertex([[maybe_unused]] vertex_set& vertices, [[maybe_unused]] vertex_index index, const vertex_value_type& val) : base_value_type(val) {} -template class VContainer, typename Alloc> -ual_vertex::ual_vertex([[maybe_unused]] vertex_set& vertices, +ual_vertex::ual_vertex([[maybe_unused]] vertex_set& vertices, [[maybe_unused]] vertex_index index, vertex_value_type&& val) noexcept : base_value_type(move(val)) {} -template class VContainer, typename Alloc> -typename ual_vertex::vertex_edge_iterator -ual_vertex::edges_begin(graph_type& g, vertex_id_type uid) noexcept { +typename ual_vertex::vertex_edge_iterator +ual_vertex::edges_begin(graph_type& g, vertex_id_type uid) noexcept { return edges_.begin(g, uid); } -template class VContainer, typename Alloc> -typename ual_vertex::const_vertex_edge_iterator -ual_vertex::edges_begin(const graph_type& g, vertex_id_type uid) const noexcept { +typename ual_vertex::const_vertex_edge_iterator +ual_vertex::edges_begin(const graph_type& g, vertex_id_type uid) const noexcept { return edges_.begin(g, uid); } -template class VContainer, typename Alloc> -typename ual_vertex::const_vertex_edge_iterator -ual_vertex::edges_cbegin(const graph_type& g, vertex_id_type uid) const noexcept { +typename ual_vertex::const_vertex_edge_iterator +ual_vertex::edges_cbegin(const graph_type& g, vertex_id_type uid) const noexcept { return edges_.cbegin(g, uid); } // Removed: e_begin implementation - legacy method using const_cast, replaced by edges_begin -template class VContainer, typename Alloc> -typename ual_vertex::vertex_edge_iterator -ual_vertex::edges_end(graph_type& g, vertex_id_type uid) noexcept { +typename ual_vertex::vertex_edge_iterator +ual_vertex::edges_end(graph_type& g, vertex_id_type uid) noexcept { return edges_.end(g, uid); } -template class VContainer, typename Alloc> -typename ual_vertex::const_vertex_edge_iterator -ual_vertex::edges_end(const graph_type& g, vertex_id_type uid) const noexcept { +typename ual_vertex::const_vertex_edge_iterator +ual_vertex::edges_end(const graph_type& g, vertex_id_type uid) const noexcept { return edges_.end(g, uid); } -template class VContainer, typename Alloc> -typename ual_vertex::const_vertex_edge_iterator -ual_vertex::edges_cend(const graph_type& g, vertex_id_type uid) const noexcept { +typename ual_vertex::const_vertex_edge_iterator +ual_vertex::edges_cend(const graph_type& g, vertex_id_type uid) const noexcept { return edges_.cend(g, uid); } // Removed: e_end implementation - legacy method using const_cast, replaced by edges_end -template class VContainer, typename Alloc> -typename ual_vertex::vertex_edge_range -ual_vertex::edges(graph_type& g, vertex_id_type uid) { +typename ual_vertex::vertex_edge_range +ual_vertex::edges(graph_type& g, vertex_id_type uid) { return edges_.edges(g, uid); } -template class VContainer, typename Alloc> -typename ual_vertex::const_vertex_edge_range -ual_vertex::edges(const graph_type& g, vertex_id_type uid) const { +typename ual_vertex::const_vertex_edge_range +ual_vertex::edges(const graph_type& g, vertex_id_type uid) const { return edges_.edges(g, uid); } #if 0 -template class VContainer, typename Alloc> -typename ual_vertex::vertex_id_type -ual_vertex::vertex_id(const graph_type& g) const noexcept { +template class VContainer, typename Alloc> +typename ual_vertex::vertex_id_type +ual_vertex::vertex_id(const graph_type& g) const noexcept { return static_cast(this - g.vertices().data()); } #endif -template class VContainer, typename Alloc> -typename ual_vertex::edge_type& -ual_vertex::edge_front(graph_type& g) noexcept { +typename ual_vertex::edge_type& +ual_vertex::edge_front(graph_type& g) noexcept { return edges_.front(g, *this); } -template class VContainer, typename Alloc> -const typename ual_vertex::edge_type& -ual_vertex::edge_front(const graph_type& g) const noexcept { +const typename ual_vertex::edge_type& +ual_vertex::edge_front(const graph_type& g) const noexcept { return edges_.front(g, *this); } -template class VContainer, typename Alloc> -typename ual_vertex::edge_type& -ual_vertex::edge_back(graph_type& g) noexcept { +typename ual_vertex::edge_type& +ual_vertex::edge_back(graph_type& g) noexcept { return edges_.back(); } -template class VContainer, typename Alloc> -const typename ual_vertex::edge_type& -ual_vertex::edge_back(const graph_type& g) const noexcept { +const typename ual_vertex::edge_type& +ual_vertex::edge_back(const graph_type& g) const noexcept { return edges_.back(); } -template class VContainer, typename Alloc> -typename ual_vertex::vertex_edge_size_type -ual_vertex::num_edges() const { +typename ual_vertex::vertex_edge_size_type +ual_vertex::num_edges() const { return edges_.size(); } -template class VContainer, typename Alloc> -void ual_vertex::erase_edge(graph_type& g, edge_type* uv) { +void ual_vertex::erase_edge(graph_type& g, edge_type* uv) { vertex_type& u = g.vertices()[uv->list_owner_id()]; vertex_type& v = g.vertices()[uv->list_target_id()]; uv->unlink(u, v); @@ -969,40 +969,40 @@ void ual_vertex::erase_edge(graph_type& g, e --g.edges_size_; } -template class VContainer, typename Alloc> -void ual_vertex::clear_edges(graph_type& g) { +void ual_vertex::clear_edges(graph_type& g) { while (!edges_.empty()) { erase_edge(g, &edges_.front()); } } -template class VContainer, typename Alloc> -typename ual_vertex::vertex_edge_iterator -ual_vertex::erase_edge(graph_type& g, vertex_edge_iterator uvi) { +typename ual_vertex::vertex_edge_iterator +ual_vertex::erase_edge(graph_type& g, vertex_edge_iterator uvi) { edge_type* uv = &*uvi; ++uvi; erase_edge(g, uv); return uvi; } -template class VContainer, typename Alloc> -typename ual_vertex::vertex_edge_iterator -ual_vertex::erase_edge(graph_type& g, +typename ual_vertex::vertex_edge_iterator +ual_vertex::erase_edge(graph_type& g, vertex_edge_iterator first, vertex_edge_iterator last) { while (first != last) @@ -1011,27 +1011,27 @@ ual_vertex::erase_edge(graph_type& } #if 0 -template class VContainer, typename Alloc> -typename ual_vertex::vertex_edge_iterator -ual_vertex::create_edge(graph_type& g, vertex_type& v) { +template class VContainer, typename Alloc> +typename ual_vertex::vertex_edge_iterator +ual_vertex::create_edge(graph_type& g, vertex_type& v) { edge_type* uv = g.edge_alloc_.allocate(1); new (uv) edge_type(g, *this, v); ++g.edges_size_; return vertex_edge_iterator(g, *this, uv); } -template class VContainer, typename Alloc> -typename ual_vertex::vertex_edge_iterator -ual_vertex::create_edge(graph_type& g, vertex_type& v, edge_value_type&& val) { +template class VContainer, typename Alloc> +typename ual_vertex::vertex_edge_iterator +ual_vertex::create_edge(graph_type& g, vertex_type& v, edge_value_type&& val) { edge_type* uv = g.edge_alloc_.allocate(1); new (uv) edge_type(g, *this, v, move(val)); ++g.edges_size_; return vertex_edge_iterator(g, *this, uv); } -template class VContainer, typename Alloc> -typename ual_vertex::vertex_edge_iterator -ual_vertex::create_edge(graph_type& g, vertex_type& v, const edge_value_type& val) { +template class VContainer, typename Alloc> +typename ual_vertex::vertex_edge_iterator +ual_vertex::create_edge(graph_type& g, vertex_type& v, const edge_value_type& val) { edge_type* uv = g.edge_alloc_.allocate(1); new (uv) edge_type(g, *this, v, val); ++g.edges_size_; @@ -1039,35 +1039,35 @@ ual_vertex::create_edge(graph_type& g, verte } #endif -template class VContainer, typename Alloc> -typename ual_vertex::neighbor_size_type -ual_vertex::neighbors_size(const graph_type& g) const { +typename ual_vertex::neighbor_size_type +ual_vertex::neighbors_size(const graph_type& g) const { return size(edges(g)); } -template class VContainer, typename Alloc> -typename ual_vertex::neighbor_range -ual_vertex::neighbors(graph_type& g, vertex_id_type uid) { +typename ual_vertex::neighbor_range +ual_vertex::neighbors(graph_type& g, vertex_id_type uid) { return {neighbor_iterator(edges_.begin(g, uid)), neighbor_iterator(edges_.end(g, uid)), edges_.size()}; } -template class VContainer, typename Alloc> -typename ual_vertex::const_neighbor_range -ual_vertex::neighbors(const graph_type& g, vertex_id_type uid) const { +typename ual_vertex::const_neighbor_range +ual_vertex::neighbors(const graph_type& g, vertex_id_type uid) const { return {const_neighbor_iterator(edges_.begin(g, uid)), const_neighbor_iterator(edges_.end(g, uid)), edges_.size()}; } @@ -1077,13 +1077,13 @@ ual_vertex::neighbors(const graph_type& g, v /// // Copy constructor - copies vertices and edges -template class VContainer, typename Alloc> -base_undirected_adjacency_list::base_undirected_adjacency_list( +base_undirected_adjacency_list::base_undirected_adjacency_list( const base_undirected_adjacency_list& other) { // Copy base class members edge_alloc_ = other.edge_alloc_; @@ -1123,8 +1123,8 @@ base_undirected_adjacency_list::base_undirec } // Range constructor -template class VContainer, @@ -1133,7 +1133,7 @@ template requires ranges::forward_range && ranges::input_range && std::regular_invocable> && std::regular_invocable> -base_undirected_adjacency_list::base_undirected_adjacency_list( +base_undirected_adjacency_list::base_undirected_adjacency_list( const ERng& erng, const VRng& vrng, const EProj& eproj, const VProj& vproj, const allocator_type& alloc) : vertices_(alloc), edge_alloc_(alloc) { // Handle empty case - no vertices or edges to create @@ -1181,13 +1181,13 @@ base_undirected_adjacency_list::base_undirec } // Initializer list constructor with edge values -template class VContainer, typename Alloc> -base_undirected_adjacency_list::base_undirected_adjacency_list( +base_undirected_adjacency_list::base_undirected_adjacency_list( const initializer_list>& ilist, const allocator_type& alloc) : vertices_(alloc), edge_alloc_(alloc) { @@ -1210,13 +1210,13 @@ base_undirected_adjacency_list::base_undirec } // Initializer list constructor without edge values -template class VContainer, typename Alloc> -base_undirected_adjacency_list::base_undirected_adjacency_list( +base_undirected_adjacency_list::base_undirected_adjacency_list( const initializer_list>& ilist, const allocator_type& alloc) : vertices_(alloc), edge_alloc_(alloc) { // Evaluate max vertex id needed @@ -1239,27 +1239,27 @@ base_undirected_adjacency_list::base_undirec // Destructor -template class VContainer, typename Alloc> -base_undirected_adjacency_list::~base_undirected_adjacency_list() { +base_undirected_adjacency_list::~base_undirected_adjacency_list() { // Downcast to graph_type to access clear() method auto& g = static_cast(*this); g.clear(); // assure edges are deleted using edge_alloc_ } // Copy assignment operator -template class VContainer, typename Alloc> -base_undirected_adjacency_list& -base_undirected_adjacency_list::operator=( +base_undirected_adjacency_list& +base_undirected_adjacency_list::operator=( const base_undirected_adjacency_list& other) { if (this != &other) { // Use copy-and-swap idiom @@ -1276,13 +1276,13 @@ base_undirected_adjacency_list::operator=( } // Helper method for validation -template class VContainer, typename Alloc> -void base_undirected_adjacency_list::throw_unordered_edges() const { +void base_undirected_adjacency_list::throw_unordered_edges() const { assert(false); // container must be sorted by edge_id.first throw std::invalid_argument("edges not ordered"); } @@ -1291,127 +1291,127 @@ void base_undirected_adjacency_list::throw_u // Accessor methods //------------------------------------------------------------------------------------- -template class VContainer, typename Alloc> -constexpr typename base_undirected_adjacency_list::edge_allocator_type -base_undirected_adjacency_list::edge_allocator() const noexcept { +constexpr typename base_undirected_adjacency_list::edge_allocator_type +base_undirected_adjacency_list::edge_allocator() const noexcept { return this->edge_alloc_; } -template class VContainer, typename Alloc> -constexpr typename base_undirected_adjacency_list::vertex_set& -base_undirected_adjacency_list::vertices() { +constexpr typename base_undirected_adjacency_list::vertex_set& +base_undirected_adjacency_list::vertices() { return this->vertices_; } -template class VContainer, typename Alloc> -constexpr const typename base_undirected_adjacency_list::vertex_set& -base_undirected_adjacency_list::vertices() const { +constexpr const typename base_undirected_adjacency_list::vertex_set& +base_undirected_adjacency_list::vertices() const { return this->vertices_; } -template class VContainer, typename Alloc> -constexpr typename base_undirected_adjacency_list::vertex_iterator -base_undirected_adjacency_list::begin() { +constexpr typename base_undirected_adjacency_list::vertex_iterator +base_undirected_adjacency_list::begin() { return this->vertices_.begin(); } -template class VContainer, typename Alloc> -constexpr typename base_undirected_adjacency_list::const_vertex_iterator -base_undirected_adjacency_list::begin() const { +constexpr typename base_undirected_adjacency_list::const_vertex_iterator +base_undirected_adjacency_list::begin() const { return this->vertices_.begin(); } -template class VContainer, typename Alloc> -constexpr typename base_undirected_adjacency_list::const_vertex_iterator -base_undirected_adjacency_list::cbegin() const { +constexpr typename base_undirected_adjacency_list::const_vertex_iterator +base_undirected_adjacency_list::cbegin() const { return this->vertices_.cbegin(); } -template class VContainer, typename Alloc> -constexpr typename base_undirected_adjacency_list::vertex_iterator -base_undirected_adjacency_list::end() { +constexpr typename base_undirected_adjacency_list::vertex_iterator +base_undirected_adjacency_list::end() { return this->vertices_.end(); } -template class VContainer, typename Alloc> -constexpr typename base_undirected_adjacency_list::const_vertex_iterator -base_undirected_adjacency_list::end() const { +constexpr typename base_undirected_adjacency_list::const_vertex_iterator +base_undirected_adjacency_list::end() const { return this->vertices_.end(); } -template class VContainer, typename Alloc> -constexpr typename base_undirected_adjacency_list::const_vertex_iterator -base_undirected_adjacency_list::cend() const { +constexpr typename base_undirected_adjacency_list::const_vertex_iterator +base_undirected_adjacency_list::cend() const { return this->vertices_.cend(); } -template class VContainer, typename Alloc> -typename base_undirected_adjacency_list::vertex_iterator -base_undirected_adjacency_list::try_find_vertex(vertex_id_type id) { +typename base_undirected_adjacency_list::vertex_iterator +base_undirected_adjacency_list::try_find_vertex(vertex_id_type id) { if (id < this->vertices_.size()) return this->vertices_.begin() + id; else return this->vertices_.end(); } -template class VContainer, typename Alloc> -typename base_undirected_adjacency_list::const_vertex_iterator -base_undirected_adjacency_list::try_find_vertex(vertex_id_type id) const { +typename base_undirected_adjacency_list::const_vertex_iterator +base_undirected_adjacency_list::try_find_vertex(vertex_id_type id) const { if (id < this->vertices_.size()) return this->vertices_.begin() + id; else @@ -1427,14 +1427,14 @@ base_undirected_adjacency_list::try_find_ver // Edge removal methods //------------------------------------------------------------------------------------- -template class VContainer, typename Alloc> -typename base_undirected_adjacency_list::edge_iterator -base_undirected_adjacency_list::erase_edge(edge_iterator pos) { +typename base_undirected_adjacency_list::edge_iterator +base_undirected_adjacency_list::erase_edge(edge_iterator pos) { edge_type* uv = &*pos; ++pos; uv->~edge_type(); // unlinks from vertices @@ -1447,13 +1447,13 @@ base_undirected_adjacency_list::erase_edge(e // Graph modification methods //------------------------------------------------------------------------------------- -template class VContainer, typename Alloc> -void base_undirected_adjacency_list::clear() { +void base_undirected_adjacency_list::clear() { // make sure edges are deallocated from this->edge_alloc_ // Use downcast to call derived class clear_edges method auto& derived = static_cast(*this); @@ -1462,13 +1462,13 @@ void base_undirected_adjacency_list::clear() this->vertices_.clear(); // now we can clear the vertices } -template class VContainer, typename Alloc> -void base_undirected_adjacency_list::swap(base_undirected_adjacency_list& other) { +void base_undirected_adjacency_list::swap(base_undirected_adjacency_list& other) { using std::swap; this->vertices_.swap(other.vertices_); swap(this->edges_size_, other.edges_size_); @@ -1481,33 +1481,33 @@ void base_undirected_adjacency_list::swap(ba // Utility methods //------------------------------------------------------------------------------------- -template class VContainer, typename Alloc> -void base_undirected_adjacency_list::reserve_vertices(vertex_size_type n) { +void base_undirected_adjacency_list::reserve_vertices(vertex_size_type n) { this->vertices_.reserve(n); } -template class VContainer, typename Alloc> -void base_undirected_adjacency_list::resize_vertices(vertex_size_type n) { +void base_undirected_adjacency_list::resize_vertices(vertex_size_type n) { this->vertices_.resize(n); } -template class VContainer, typename Alloc> -void base_undirected_adjacency_list::resize_vertices(vertex_size_type n, +void base_undirected_adjacency_list::resize_vertices(vertex_size_type n, const vertex_value_type& val) { this->vertices_.resize(n, val); } @@ -1517,47 +1517,47 @@ void base_undirected_adjacency_list::resize_ /// undirected_adjacency_list /// -template class VContainer, typename Alloc> -undirected_adjacency_list::undirected_adjacency_list(const allocator_type& alloc) +undirected_adjacency_list::undirected_adjacency_list(const allocator_type& alloc) : base_type(alloc) {} -template class VContainer, typename Alloc> -undirected_adjacency_list::undirected_adjacency_list(const graph_value_type& val, +undirected_adjacency_list::undirected_adjacency_list(const graph_value_type& val, const allocator_type& alloc) : base_type(alloc), graph_value_(val) {} -template class VContainer, typename Alloc> -undirected_adjacency_list::undirected_adjacency_list(graph_value_type&& val, +undirected_adjacency_list::undirected_adjacency_list(graph_value_type&& val, const allocator_type& alloc) : base_type(alloc), graph_value_(std::forward(val)) {} // clang-format off -template class VContainer, typename Alloc> +template class VContainer, typename Alloc> template requires ranges::forward_range && ranges::input_range && std::regular_invocable> && std::regular_invocable> && (!std::is_void_v) -undirected_adjacency_list::undirected_adjacency_list(const ERng& erng, +undirected_adjacency_list::undirected_adjacency_list(const ERng& erng, const VRng& vrng, const EProj& eproj, const VProj& vproj, @@ -1569,12 +1569,12 @@ undirected_adjacency_list::undirected_adjace {} // clang-format off -template class VContainer, typename Alloc> +template class VContainer, typename Alloc> template requires ranges::forward_range && std::regular_invocable> && (!std::is_void_v) -undirected_adjacency_list::undirected_adjacency_list(const ERng& erng, +undirected_adjacency_list::undirected_adjacency_list(const ERng& erng, const EProj& eproj, const GV_& gv, const Alloc& alloc) @@ -1585,14 +1585,14 @@ undirected_adjacency_list::undirected_adjace // Overload for GV=void: edge+vertex range constructor without graph value // clang-format off -template class VContainer, typename Alloc> +template class VContainer, typename Alloc> template requires ranges::forward_range && ranges::input_range && std::regular_invocable> && std::regular_invocable> && std::is_void_v -undirected_adjacency_list::undirected_adjacency_list(const ERng& erng, +undirected_adjacency_list::undirected_adjacency_list(const ERng& erng, const VRng& vrng, const EProj& eproj, const VProj& vproj, @@ -1644,12 +1644,12 @@ undirected_adjacency_list::undirected_adjace // Overload for GV=void: edge-only range constructor without graph value // clang-format off -template class VContainer, typename Alloc> +template class VContainer, typename Alloc> template requires ranges::forward_range && std::regular_invocable> && std::is_void_v -undirected_adjacency_list::undirected_adjacency_list(const ERng& erng, +undirected_adjacency_list::undirected_adjacency_list(const ERng& erng, const EProj& eproj, const Alloc& alloc) : undirected_adjacency_list(erng, vector(), eproj, [](auto) @@ -1657,46 +1657,46 @@ undirected_adjacency_list::undirected_adjace // clang-format on {} -template class VContainer, typename Alloc> -undirected_adjacency_list::undirected_adjacency_list( +undirected_adjacency_list::undirected_adjacency_list( const initializer_list>& ilist, const Alloc& alloc) : base_type(ilist, alloc) {} -template class VContainer, typename Alloc> -undirected_adjacency_list::undirected_adjacency_list( +undirected_adjacency_list::undirected_adjacency_list( const initializer_list>& ilist, const Alloc& alloc) : base_type(ilist, alloc) {} // Copy constructor - deep copies all vertices and edges -template class VContainer, typename Alloc> -undirected_adjacency_list::undirected_adjacency_list( +undirected_adjacency_list::undirected_adjacency_list( const undirected_adjacency_list& other) : base_type(other), graph_value_(other.graph_value_) {} // Copy assignment operator - uses copy-and-swap idiom -template class VContainer, typename Alloc> -undirected_adjacency_list& -undirected_adjacency_list::operator=(const undirected_adjacency_list& other) { +undirected_adjacency_list& +undirected_adjacency_list::operator=(const undirected_adjacency_list& other) { if (this != &other) { undirected_adjacency_list tmp(other); swap(tmp); @@ -1704,25 +1704,25 @@ undirected_adjacency_list::operator=(const u return *this; } -template class VContainer, typename Alloc> -undirected_adjacency_list::~undirected_adjacency_list() = default; +undirected_adjacency_list::~undirected_adjacency_list() = default; //------------------------------------------------------------------------------------- // Vertex/edge modification methods (non-accessor) //------------------------------------------------------------------------------------- -template class VContainer, typename Alloc> -void undirected_adjacency_list::swap(undirected_adjacency_list& rhs) { +void undirected_adjacency_list::swap(undirected_adjacency_list& rhs) { using std::swap; swap(graph_value_, rhs.graph_value_); base_type::swap(rhs); // Call base class swap for vertices, edges_size, allocators @@ -1731,145 +1731,145 @@ void undirected_adjacency_list::swap(undirec ///------------------------------------------------------------------------------------- /// ual_const_neighbor_iterator /// -template class VContainer, typename Alloc> -constexpr ual_const_neighbor_iterator::ual_const_neighbor_iterator( +constexpr ual_const_neighbor_iterator::ual_const_neighbor_iterator( vertex_edge_iterator const& uv) : uv_(uv) {} -template class VContainer, typename Alloc> -constexpr typename ual_const_neighbor_iterator::graph_type& -ual_const_neighbor_iterator::graph() noexcept { +constexpr typename ual_const_neighbor_iterator::graph_type& +ual_const_neighbor_iterator::graph() noexcept { return uv_.graph(); } -template class VContainer, typename Alloc> -constexpr const typename ual_const_neighbor_iterator::graph_type& -ual_const_neighbor_iterator::graph() const noexcept { +constexpr const typename ual_const_neighbor_iterator::graph_type& +ual_const_neighbor_iterator::graph() const noexcept { return uv_.graph(); } -template class VContainer, typename Alloc> -constexpr typename ual_const_neighbor_iterator::const_vertex_iterator -ual_const_neighbor_iterator::other_vertex() const { +constexpr typename ual_const_neighbor_iterator::const_vertex_iterator +ual_const_neighbor_iterator::other_vertex() const { return uv_->other_vertex(uv_.graph(), uv_.source_id()); } -template class VContainer, typename Alloc> -constexpr typename ual_const_neighbor_iterator::vertex_id_type -ual_const_neighbor_iterator::other_vertex_id() const { +constexpr typename ual_const_neighbor_iterator::vertex_id_type +ual_const_neighbor_iterator::other_vertex_id() const { return uv_->other_vertex_id(uv_.graph(), uv_.source_id()); } -template class VContainer, typename Alloc> -constexpr typename ual_const_neighbor_iterator::reference -ual_const_neighbor_iterator::operator*() const noexcept { +constexpr typename ual_const_neighbor_iterator::reference +ual_const_neighbor_iterator::operator*() const noexcept { return *uv_->other_vertex(uv_.graph(), uv_.source_id()); } -template class VContainer, typename Alloc> -constexpr typename ual_const_neighbor_iterator::pointer -ual_const_neighbor_iterator::operator->() const noexcept { +constexpr typename ual_const_neighbor_iterator::pointer +ual_const_neighbor_iterator::operator->() const noexcept { return &**this; } -template class VContainer, typename Alloc> -constexpr ual_const_neighbor_iterator& -ual_const_neighbor_iterator::operator++() noexcept { +constexpr ual_const_neighbor_iterator& +ual_const_neighbor_iterator::operator++() noexcept { ++uv_; return *this; } -template class VContainer, typename Alloc> -constexpr ual_const_neighbor_iterator -ual_const_neighbor_iterator::operator++(int) noexcept { +constexpr ual_const_neighbor_iterator +ual_const_neighbor_iterator::operator++(int) noexcept { ual_const_neighbor_iterator tmp(*this); ++*this; return tmp; } -template class VContainer, typename Alloc> -constexpr ual_const_neighbor_iterator& -ual_const_neighbor_iterator::operator--() noexcept { +constexpr ual_const_neighbor_iterator& +ual_const_neighbor_iterator::operator--() noexcept { --uv_; return *this; } -template class VContainer, typename Alloc> -constexpr ual_const_neighbor_iterator -ual_const_neighbor_iterator::operator--(int) noexcept { +constexpr ual_const_neighbor_iterator +ual_const_neighbor_iterator::operator--(int) noexcept { ual_const_neighbor_iterator tmp(*this); --*this; return tmp; } -template class VContainer, typename Alloc> -constexpr bool ual_const_neighbor_iterator::operator==( +constexpr bool ual_const_neighbor_iterator::operator==( const ual_const_neighbor_iterator& rhs) const noexcept { return uv_ == rhs.uv_; } -template class VContainer, typename Alloc> -constexpr bool ual_const_neighbor_iterator::operator!=( +constexpr bool ual_const_neighbor_iterator::operator!=( const ual_const_neighbor_iterator& rhs) const noexcept { return !operator==(rhs); } @@ -1878,160 +1878,160 @@ constexpr bool ual_const_neighbor_iterator:: ///------------------------------------------------------------------------------------- /// ual_neighbor_iterator /// -template class VContainer, typename Alloc> -constexpr ual_neighbor_iterator::ual_neighbor_iterator( +constexpr ual_neighbor_iterator::ual_neighbor_iterator( vertex_edge_iterator const& uv) : base_t(uv) {} -template class VContainer, typename Alloc> -constexpr typename ual_neighbor_iterator::vertex_iterator -ual_neighbor_iterator::other_vertex() { +constexpr typename ual_neighbor_iterator::vertex_iterator +ual_neighbor_iterator::other_vertex() { return uv_->other_vertex(uv_.graph(), uv_.source_id()); } -template class VContainer, typename Alloc> -constexpr typename ual_neighbor_iterator::const_reference -ual_neighbor_iterator::operator*() const { +constexpr typename ual_neighbor_iterator::const_reference +ual_neighbor_iterator::operator*() const { return *uv_->other_vertex(uv_.graph(), uv_.source_id()); } -template class VContainer, typename Alloc> -constexpr typename ual_neighbor_iterator::const_pointer -ual_neighbor_iterator::operator->() const { +constexpr typename ual_neighbor_iterator::const_pointer +ual_neighbor_iterator::operator->() const { return &**this; } -template class VContainer, typename Alloc> -constexpr ual_neighbor_iterator& -ual_neighbor_iterator::operator++() { +constexpr ual_neighbor_iterator& +ual_neighbor_iterator::operator++() { ++uv_; return *this; } -template class VContainer, typename Alloc> -constexpr ual_neighbor_iterator -ual_neighbor_iterator::operator++(int) { +constexpr ual_neighbor_iterator +ual_neighbor_iterator::operator++(int) { ual_neighbor_iterator tmp(*this); ++*this; return tmp; } -template class VContainer, typename Alloc> -constexpr ual_neighbor_iterator& -ual_neighbor_iterator::operator--() noexcept { +constexpr ual_neighbor_iterator& +ual_neighbor_iterator::operator--() noexcept { --uv_; return *this; } -template class VContainer, typename Alloc> -constexpr ual_neighbor_iterator -ual_neighbor_iterator::operator--(int) noexcept { +constexpr ual_neighbor_iterator +ual_neighbor_iterator::operator--(int) noexcept { ual_neighbor_iterator tmp(*this); --*this; return tmp; } -template class VContainer, typename Alloc> constexpr bool -ual_neighbor_iterator::operator==(const ual_neighbor_iterator& rhs) const noexcept { +ual_neighbor_iterator::operator==(const ual_neighbor_iterator& rhs) const noexcept { return base_t::operator==(rhs); } -template class VContainer, typename Alloc> constexpr bool -ual_neighbor_iterator::operator!=(const ual_neighbor_iterator& rhs) const noexcept { +ual_neighbor_iterator::operator!=(const ual_neighbor_iterator& rhs) const noexcept { return base_t::operator!=(rhs); } ///------------------------------------------------------------------------------------- -/// Implementations for undirected_adjacency_list specialization +/// Implementations for undirected_adjacency_list specialization /// (when GV=void, no graph_value_ member) /// // Default allocator constructor -template class VContainer, typename Alloc> -undirected_adjacency_list::undirected_adjacency_list(const allocator_type& alloc) +template class VContainer, typename Alloc> +undirected_adjacency_list::undirected_adjacency_list(const allocator_type& alloc) : base_type(alloc) {} // Copy constructor -template class VContainer, typename Alloc> -undirected_adjacency_list::undirected_adjacency_list( +template class VContainer, typename Alloc> +undirected_adjacency_list::undirected_adjacency_list( const undirected_adjacency_list& other) : base_type(other) {} // Initializer list constructors -template class VContainer, typename Alloc> -undirected_adjacency_list::undirected_adjacency_list( +template class VContainer, typename Alloc> +undirected_adjacency_list::undirected_adjacency_list( const initializer_list>& ilist, const Alloc& alloc) : base_type(ilist, alloc) {} -template class VContainer, typename Alloc> -undirected_adjacency_list::undirected_adjacency_list( +template class VContainer, typename Alloc> +undirected_adjacency_list::undirected_adjacency_list( const initializer_list>& ilist, const Alloc& alloc) : base_type(ilist, alloc) {} // Destructor -template class VContainer, typename Alloc> -undirected_adjacency_list::~undirected_adjacency_list() = default; +template class VContainer, typename Alloc> +undirected_adjacency_list::~undirected_adjacency_list() = default; //------------------------------------------------------------------------------------- // Vertex/edge modification methods (non-accessor) - GV=void specialization //------------------------------------------------------------------------------------- -template class VContainer, typename Alloc> -void undirected_adjacency_list::swap(undirected_adjacency_list& rhs) { +template class VContainer, typename Alloc> +void undirected_adjacency_list::swap(undirected_adjacency_list& rhs) { base_type::swap(rhs); // Call base class swap (no graph_value_ to swap) } -template class VContainer, typename Alloc> -undirected_adjacency_list& -undirected_adjacency_list::operator=(const undirected_adjacency_list& other) { +template class VContainer, typename Alloc> +undirected_adjacency_list& +undirected_adjacency_list::operator=(const undirected_adjacency_list& other) { if (this != &other) { undirected_adjacency_list tmp(other); swap(tmp); diff --git a/include/graph/container/undirected_adjacency_list.hpp b/include/graph/container/undirected_adjacency_list.hpp index 646a06c..2c64533 100644 --- a/include/graph/container/undirected_adjacency_list.hpp +++ b/include/graph/container/undirected_adjacency_list.hpp @@ -120,7 +120,7 @@ namespace ranges = std::ranges; /// EXAMPLE USAGE: /// -------------- /// @code -/// using Graph = undirected_adjacency_list; // vertex_value=string, edge_value=int +/// using Graph = undirected_adjacency_list; // edge_value=int, vertex_value=string /// /// Graph g; /// auto u = g.create_vertex("Alice"); @@ -133,56 +133,56 @@ namespace ranges = std::ranges; /// } /// @endcode /// -/// @tparam VV Vertex Value type (default: void for no value) /// @tparam EV Edge Value type (default: void for no value) +/// @tparam VV Vertex Value type (default: void for no value) /// @tparam GV Graph Value type (default: void for no value) /// @tparam VId Vertex id/index type (default: uint32_t) /// @tparam VContainer Vertex storage container template (default: std::vector) /// @tparam Alloc Allocator type (default: std::allocator) ///------------------------------------------------------------------------------------- -template class VContainer = vector, typename Alloc = allocator> class undirected_adjacency_list; -template class VContainer, typename Alloc> class ual_vertex; -template class VContainer, typename Alloc> class base_undirected_adjacency_list; -template class VContainer, typename Alloc> class ual_edge; -template class VContainer, typename Alloc> class ual_const_neighbor_iterator; -template class VContainer, @@ -196,16 +196,16 @@ class ual_neighbor_iterator; struct inward_list; struct outward_list; -template class VContainer, typename Alloc> class ual_vertex_edge_list; -template class VContainer, @@ -236,9 +236,9 @@ template class ual_edge_value { public: - using edge_type = ual_edge; + using edge_type = ual_edge; using value_type = EV; - using graph_type = undirected_adjacency_list; + using graph_type = undirected_adjacency_list; public: constexpr ual_edge_value(const value_type& val) : value_(val) {} @@ -280,24 +280,24 @@ class ual_edge_value {}; /// This class stores the user-defined vertex value. A specialization for VV=void provides /// zero memory overhead when no vertex value is needed. /// -/// @tparam VV Vertex value type /// @tparam EV Edge value type +/// @tparam VV Vertex value type /// @tparam GV Graph value type /// @tparam VId Vertex ID type /// @tparam VContainer Container template for vertices /// @tparam Alloc Allocator type /// -template class VContainer, typename Alloc> class ual_vertex_value { public: - using vertex_type = ual_vertex; + using vertex_type = ual_vertex; using value_type = VV; - using graph_type = undirected_adjacency_list; + using graph_type = undirected_adjacency_list; public: constexpr ual_vertex_value(const value_type& val) : value_(val) {} @@ -339,21 +339,21 @@ class ual_vertex_value { /// @brief Specialization for VV=void that provides zero memory overhead. /// template class VContainer, typename Alloc> -class ual_vertex_value {}; +class ual_vertex_value {}; ///------------------------------------------------------------------------------------- /// ual_vertex_edge_list /// -/// @tparam VV Vertex Value type. default = void. /// @tparam EV Edge Value type. default = void. +/// @tparam VV Vertex Value type. default = void. /// @tparam GV Graph Value type. default = void. /// @tparam IntexT The type used for vertex & edge index into the internal vectors. /// @tparam A Allocator. default = std::allocator /// @tparam ListT inward_list|outward_list. Which edge list this is for. /// -template class VContainer, @@ -363,9 +363,9 @@ class ual_vertex_edge_list { class iterator; class const_iterator; - using graph_type = undirected_adjacency_list; + using graph_type = undirected_adjacency_list; - using vertex_type = ual_vertex; + using vertex_type = ual_vertex; using vertex_allocator_type = typename allocator_traits::template rebind_alloc; using vertex_set = VContainer; using vertex_iterator = typename vertex_set::iterator; @@ -374,12 +374,12 @@ class ual_vertex_edge_list { using vertex_index = VId; using edge_value_type = EV; - using edge_type = ual_edge; + using edge_type = ual_edge; - using edge_list_type = ual_vertex_edge_list; - using vertex_edge_list_inward_link_type = ual_vertex_edge_list_link; + using edge_list_type = ual_vertex_edge_list; + using vertex_edge_list_inward_link_type = ual_vertex_edge_list_link; using vertex_edge_list_outward_link_type = - ual_vertex_edge_list_link; + ual_vertex_edge_list_link; using edge_allocator_type = typename allocator_traits::template rebind_alloc; using value_type = edge_type; @@ -518,13 +518,13 @@ class ual_vertex_edge_list { public: template - void link_front(edge_type& uv, ual_vertex_edge_list_link& uv_link); + void link_front(edge_type& uv, ual_vertex_edge_list_link& uv_link); template - void link_back(edge_type& uv, ual_vertex_edge_list_link& uv_link); + void link_back(edge_type& uv, ual_vertex_edge_list_link& uv_link); template - void unlink(edge_type& uv, ual_vertex_edge_list_link& uv_link); + void unlink(edge_type& uv, ual_vertex_edge_list_link& uv_link); iterator begin(graph_type& g, vertex_id_type uid) noexcept; const_iterator begin(const graph_type& g, vertex_id_type uid) const noexcept; @@ -546,15 +546,15 @@ class ual_vertex_edge_list { ///------------------------------------------------------------------------------------- /// ual_vertex_edge_list_link /// -/// @tparam VV Vertex Value type. default = void. /// @tparam EV Edge Value type. default = void. +/// @tparam VV Vertex Value type. default = void. /// @tparam GV Graph Value type. default = void. /// @tparam IntexT The type used for vertex & edge index into the internal vectors. /// @tparam A Allocator. default = std::allocator /// @tparam ListT inward_list|outward_list. Which edge list this is for. /// -template class VContainer, @@ -562,9 +562,9 @@ template class ual_vertex_edge_list_link { public: - using graph_type = undirected_adjacency_list; + using graph_type = undirected_adjacency_list; - using vertex_type = ual_vertex; + using vertex_type = ual_vertex; using vertex_allocator_type = typename allocator_traits::template rebind_alloc; using vertex_set = VContainer; using vertex_iterator = typename vertex_set::iterator; @@ -572,10 +572,10 @@ class ual_vertex_edge_list_link { using vertex_id_type = VId; using vertex_index = VId; - using edge_type = ual_edge; + using edge_type = ual_edge; - using edge_list_type = ual_vertex_edge_list; - using edge_list_link_type = ual_vertex_edge_list_link; + using edge_list_type = ual_vertex_edge_list; + using edge_list_link_type = ual_vertex_edge_list_link; public: ual_vertex_edge_list_link(vertex_id_type uid) noexcept : vertex_id_(uid) {} @@ -610,28 +610,28 @@ class ual_vertex_edge_list_link { ///------------------------------------------------------------------------------------- /// ual_edge /// -/// @tparam VV Vertex Value type. default = void. /// @tparam EV Edge Value type. default = void. +/// @tparam VV Vertex Value type. default = void. /// @tparam GV Graph Value type. default = void. /// @tparam IntexT The type used for vertex & edge index into the internal vectors. /// @tparam A Allocator. default = std::allocator /// -template class VContainer, typename Alloc> class ual_edge : public ual_edge_value - , public ual_vertex_edge_list_link - , public ual_vertex_edge_list_link { + , public ual_vertex_edge_list_link + , public ual_vertex_edge_list_link { public: - using graph_type = undirected_adjacency_list; + using graph_type = undirected_adjacency_list; using graph_value_type = GV; using base_value_type = ual_edge_value; - using vertex_type = ual_vertex; + using vertex_type = ual_vertex; using vertex_allocator_type = typename allocator_traits::template rebind_alloc; using vertex_set = VContainer; using vertex_iterator = typename vertex_set::iterator; @@ -642,16 +642,16 @@ class ual_edge using edge_id_type = pair; using edge_value_type = EV; - using edge_type = ual_edge; + using edge_type = ual_edge; using edge_allocator_type = typename allocator_traits::template rebind_alloc; using edge_index = VId; using edge_size_type = size_t; using edge_difference_type = ptrdiff_t; - using edge_list_type = ual_vertex_edge_list; - using vertex_edge_list_inward_link_type = ual_vertex_edge_list_link; + using edge_list_type = ual_vertex_edge_list; + using vertex_edge_list_inward_link_type = ual_vertex_edge_list_link; using vertex_edge_list_outward_link_type = - ual_vertex_edge_list_link; + ual_vertex_edge_list_link; protected: // noexcept is only defined for move ctor & assignment b/c the user-defined value type could @@ -724,7 +724,7 @@ class ual_edge edge_id_type edge_id(const graph_type& g) const noexcept; friend graph_type; // the graph is the one to create & destroy edges because it owns the allocator - friend base_undirected_adjacency_list; // base class also creates edges + friend base_undirected_adjacency_list; // base class also creates edges friend vertex_type; // vertex can also destroy its own edges friend edge_list_type; // for delete, when clearing the list }; @@ -749,26 +749,26 @@ class ual_edge /// - graph_value() accessors (general template only) /// - graph_value() CPO friend function (general template only) /// -/// @tparam VV Vertex Value type /// @tparam EV Edge Value type +/// @tparam VV Vertex Value type /// @tparam GV Graph Value type (may be void) /// @tparam VId Vertex id/index type /// @tparam VContainer Vertex storage container template /// @tparam Alloc Allocator type ///------------------------------------------------------------------------------------- -template class VContainer, typename Alloc> class base_undirected_adjacency_list { public: // Type Aliases - using graph_type = undirected_adjacency_list; + using graph_type = undirected_adjacency_list; using graph_value_type = GV; using allocator_type = Alloc; - using vertex_type = ual_vertex; + using vertex_type = ual_vertex; using vertex_value_type = VV; using vertex_id_type = VId; using vertex_index_type = VId; @@ -782,7 +782,7 @@ class base_undirected_adjacency_list { using vertex_range = vertex_set&; using const_vertex_range = const vertex_set&; - using edge_type = ual_edge; + using edge_type = ual_edge; using edge_value_type = EV; using edge_allocator_type = typename allocator_traits::template rebind_alloc; using edge_id_type = pair; // @@ -812,7 +812,7 @@ class base_undirected_adjacency_list { public: using iterator_category = forward_iterator_tag; using iterator_concept = std::forward_iterator_tag; - using value_type = ual_edge; + using value_type = ual_edge; using size_type = size_t; using difference_type = ptrdiff_t; using pointer = value_type const*; @@ -1198,14 +1198,14 @@ class base_undirected_adjacency_list { /// find_vertex(g, id) - returns view iterator yielding vertex_descriptor /// REQUIRED: Provides bounds checking - returns end() if id >= size() /// The default CPO implementation lacks this bounds checking. -template class VContainer, typename Alloc> -constexpr auto find_vertex(undirected_adjacency_list& g, VId id) noexcept { - using graph_type = undirected_adjacency_list; +constexpr auto find_vertex(undirected_adjacency_list& g, VId id) noexcept { + using graph_type = undirected_adjacency_list; using vertex_set = typename graph_type::vertex_set; using container_iter = typename vertex_set::iterator; using view_type = vertex_descriptor_view; @@ -1218,14 +1218,14 @@ constexpr auto find_vertex(undirected_adjacency_list(id)}; } -template class VContainer, typename Alloc> -constexpr auto find_vertex(const undirected_adjacency_list& g, VId id) noexcept { - using graph_type = undirected_adjacency_list; +constexpr auto find_vertex(const undirected_adjacency_list& g, VId id) noexcept { + using graph_type = undirected_adjacency_list; using vertex_set = typename graph_type::vertex_set; using container_iter = typename vertex_set::const_iterator; using view_type = vertex_descriptor_view; @@ -1242,15 +1242,15 @@ constexpr auto find_vertex(const undirected_adjacency_list class VContainer, typename Alloc, typename E> requires edge_descriptor_type -constexpr VId target_id(const undirected_adjacency_list& g, const E& e) noexcept { +constexpr VId target_id(const undirected_adjacency_list& g, const E& e) noexcept { return e.value()->other_vertex_id(g, static_cast(e.source_id())); } @@ -1261,15 +1261,15 @@ constexpr VId target_id(const undirected_adjacency_list class VContainer, typename Alloc, typename E> requires edge_descriptor_type -constexpr VId source_id([[maybe_unused]] const undirected_adjacency_list& g, +constexpr VId source_id([[maybe_unused]] const undirected_adjacency_list& g, const E& e) noexcept { return static_cast(e.source_id()); } @@ -1277,8 +1277,8 @@ constexpr VId source_id([[maybe_unused]] const undirected_adjacency_list class VContainer, @@ -1286,12 +1286,12 @@ template requires edge_descriptor_type && (!std::is_void_v) constexpr decltype(auto) - edge_value(undirected_adjacency_list&, const E& e) noexcept { + edge_value(undirected_adjacency_list&, const E& e) noexcept { return e.value()->value(); } -template class VContainer, @@ -1299,32 +1299,32 @@ template requires edge_descriptor_type && (!std::is_void_v) constexpr decltype(auto) - edge_value(const undirected_adjacency_list&, const E& e) noexcept { + edge_value(const undirected_adjacency_list&, const E& e) noexcept { return e.value()->value(); } ///------------------------------------------------------------------------------------- /// ual_vertex /// -/// @tparam VV Vertex Value type. default = void. /// @tparam EV Edge Value type. default = void. +/// @tparam VV Vertex Value type. default = void. /// @tparam GV Graph Value type. default = void. /// @tparam IntexT The type used for vertex & edge index into the internal vectors. /// @tparam A Allocator. default = std::allocator /// -template class VContainer, typename Alloc> -class ual_vertex : public ual_vertex_value { +class ual_vertex : public ual_vertex_value { public: - using graph_type = undirected_adjacency_list; + using graph_type = undirected_adjacency_list; using graph_value_type = GV; - using base_value_type = ual_vertex_value; + using base_value_type = ual_vertex_value; - using vertex_type = ual_vertex; + using vertex_type = ual_vertex; using vertex_allocator_type = typename allocator_traits::template rebind_alloc; using vertex_set = VContainer; using vertex_iterator = typename vertex_set::iterator; @@ -1334,23 +1334,23 @@ class ual_vertex : public ual_vertex_value { using vertex_value_type = VV; using edge_value_type = EV; - using edge_type = ual_edge; + using edge_type = ual_edge; using edge_allocator_type = typename allocator_traits::template rebind_alloc; using edge_size_type = typename edge_type::edge_size_type; using edge_difference_type = typename edge_type::edge_difference_type; - using vertex_edge_list_type = ual_vertex_edge_list; - using vertex_edge_list_inward_link_type = ual_vertex_edge_list_link; + using vertex_edge_list_type = ual_vertex_edge_list; + using vertex_edge_list_inward_link_type = ual_vertex_edge_list_link; using vertex_edge_list_outward_link_type = - ual_vertex_edge_list_link; + ual_vertex_edge_list_link; using vertex_edge_size_type = typename vertex_edge_list_type::size_type; using vertex_edge_iterator = typename vertex_edge_list_type::iterator; using const_vertex_edge_iterator = typename vertex_edge_list_type::const_iterator; using vertex_edge_range = typename vertex_edge_list_type::edge_range; using const_vertex_edge_range = typename vertex_edge_list_type::const_edge_range; - using neighbor_iterator = ual_neighbor_iterator; - using const_neighbor_iterator = ual_const_neighbor_iterator; + using neighbor_iterator = ual_neighbor_iterator; + using const_neighbor_iterator = ual_const_neighbor_iterator; using neighbor_range = ranges::subrange; using const_neighbor_range = ranges::subrange; @@ -1415,19 +1415,19 @@ class ual_vertex : public ual_vertex_value { friend vertex_edge_list_outward_link_type; }; -template class VContainer, typename Alloc> class ual_const_neighbor_iterator { public: - using this_t = ual_const_neighbor_iterator; + using this_t = ual_const_neighbor_iterator; - using graph_type = undirected_adjacency_list; + using graph_type = undirected_adjacency_list; - using vertex_type = ual_vertex; + using vertex_type = ual_vertex; using vertex_value_type = VV; using vertex_id_type = VId; using vertex_allocator_type = typename allocator_traits::template rebind_alloc; @@ -1436,12 +1436,12 @@ class ual_const_neighbor_iterator { using vertex_iterator = typename vertex_set::iterator; using const_vertex_iterator = typename vertex_set::const_iterator; - using edge_list_type = ual_vertex_edge_list; + using edge_list_type = ual_vertex_edge_list; using vertex_edge_size_type = typename edge_list_type::size_type; using vertex_edge_iterator = typename edge_list_type::iterator; using const_vertex_edge_iterator = typename edge_list_type::const_iterator; - using edge_type = ual_edge; + using edge_type = ual_edge; using edge_value_type = typename edge_type::edge_value_type; using edge_id_type = typename edge_type::edge_id_type; // @@ -1491,16 +1491,16 @@ class ual_const_neighbor_iterator { vertex_edge_iterator uv_; }; -template class VContainer, typename Alloc> -class ual_neighbor_iterator : public ual_const_neighbor_iterator { +class ual_neighbor_iterator : public ual_const_neighbor_iterator { public: - using this_t = ual_neighbor_iterator; - using base_t = ual_const_neighbor_iterator; + using this_t = ual_neighbor_iterator; + using base_t = ual_const_neighbor_iterator; using graph_type = typename base_t::graph_type; using vertex_type = typename base_t::vertex_type; @@ -1583,37 +1583,37 @@ class ual_neighbor_iterator : public ual_const_neighbor_iterator Random-access container type used to store vertices (V) with allocator (A). /// @tparam Alloc Allocator. default = std::allocator /// // clang-format off -template class VContainer, typename Alloc> class undirected_adjacency_list - : public base_undirected_adjacency_list + : public base_undirected_adjacency_list // clang-format on { public: - using base_type = base_undirected_adjacency_list; + using base_type = base_undirected_adjacency_list; // Note: Access base class members using this-> or base_type:: // (using declarations don't work in constructor initializer lists) - using graph_type = undirected_adjacency_list; + using graph_type = undirected_adjacency_list; using graph_value_type = GV; using allocator_type = Alloc; - using vertex_type = ual_vertex; + using vertex_type = ual_vertex; using vertex_value_type = VV; - using vertex_value_wrapper_type = ual_vertex_value; + using vertex_value_wrapper_type = ual_vertex_value; using vertex_id_type = VId; using vertex_index_type = VId; using vertex_allocator_type = typename allocator_traits::template rebind_alloc; @@ -1626,7 +1626,7 @@ class undirected_adjacency_list using vertex_range = vertex_set&; using const_vertex_range = const vertex_set&; - using edge_type = ual_edge; + using edge_type = ual_edge; using edge_value_type = EV; using edge_allocator_type = typename allocator_traits::template rebind_alloc; using edge_id_type = pair; // @@ -1884,7 +1884,7 @@ class undirected_adjacency_list }; ///------------------------------------------------------------------------------------- -/// undirected_adjacency_list - Specialization for GV=void +/// undirected_adjacency_list - Specialization for GV=void /// /// @brief Specialization of undirected_adjacency_list when no graph value is needed. /// @@ -1893,20 +1893,20 @@ class undirected_adjacency_list /// /// All other functionality remains identical to the primary template. ///------------------------------------------------------------------------------------- -template class VContainer, typename Alloc> -class undirected_adjacency_list - : public base_undirected_adjacency_list { +template class VContainer, typename Alloc> +class undirected_adjacency_list + : public base_undirected_adjacency_list { public: - using base_type = base_undirected_adjacency_list; + using base_type = base_undirected_adjacency_list; // Note: Access base class members using this-> or base_type:: // (using declarations don't work in constructor initializer lists) - using graph_type = undirected_adjacency_list; + using graph_type = undirected_adjacency_list; using graph_value_type = void; using allocator_type = Alloc; - using vertex_type = ual_vertex; + using vertex_type = ual_vertex; using vertex_value_type = VV; using vertex_id_type = VId; using vertex_index_type = VId; @@ -1920,7 +1920,7 @@ class undirected_adjacency_list using vertex_range = vertex_set&; using const_vertex_range = const vertex_set&; - using edge_type = ual_edge; + using edge_type = ual_edge; using edge_value_type = EV; using edge_allocator_type = typename allocator_traits::template rebind_alloc; using edge_id_type = pair; diff --git a/tests/container/undirected_adjacency_list/test_undirected_adjacency_list.cpp b/tests/container/undirected_adjacency_list/test_undirected_adjacency_list.cpp index 7291655..41f1a2b 100644 --- a/tests/container/undirected_adjacency_list/test_undirected_adjacency_list.cpp +++ b/tests/container/undirected_adjacency_list/test_undirected_adjacency_list.cpp @@ -1562,7 +1562,7 @@ TEST_CASE("erase_edge with iterator range", "[undirected_adjacency_list][edge][e /* TEST_CASE("graph with void vertex value", "[undirected_adjacency_list][void_types]") { - undirected_adjacency_list g; + undirected_adjacency_list g; g.create_vertex(); g.create_vertex(); g.create_edge(0, 1, 100); @@ -1577,7 +1577,7 @@ TEST_CASE("graph with void vertex value", "[undirected_adjacency_list][void_type } TEST_CASE("graph with void edge value", "[undirected_adjacency_list][void_types]") { - undirected_adjacency_list g; + undirected_adjacency_list g; g.create_vertex(10); g.create_vertex(20); g.create_edge(0, 1); From a477d6753a729c7f491630d3f27db0612babc8aa Mon Sep 17 00:00:00 2001 From: Phil Ratzloff Date: Sat, 21 Feb 2026 20:04:35 -0500 Subject: [PATCH 5/8] Add voum_graph_traits (vector+unordered_map) and rename voem/moem to vom/mom - Add voum_graph_traits: vector vertices + unordered_map edges (O(1) avg lookup) - Rename voem_graph_traits to vom_graph_traits (drop positional 'e') - Rename moem_graph_traits to mom_graph_traits (same convention) - Add voum tests and CPO edge map test coverage - Update all docs and counts from 26 to 27 trait combinations --- README.md | 6 +- agents/archive/dynamic_graph_todo.md | 46 +- .../test_reorganization_execution_plan.md | 16 +- agents/archive/view_plan.md | 2 +- agents/archive/view_strategy.md | 4 +- agents/bgl2_comparison_result.md | 4 +- agents/doc_refactor_plan.md | 10 +- agents/doc_refactor_strategy.md | 4 +- docs/archive/edge_map_analysis.md | 26 +- docs/contributing/architecture.md | 4 +- docs/getting-started.md | 2 +- docs/index.md | 2 +- docs/migration-from-v2.md | 2 +- docs/status/implementation_matrix.md | 46 +- docs/user-guide/containers.md | 14 +- ..._graph_traits.hpp => mom_graph_traits.hpp} | 12 +- ..._graph_traits.hpp => vom_graph_traits.hpp} | 10 +- .../container/traits/voum_graph_traits.hpp | 56 ++ tests/common/graph_test_types.hpp | 29 +- tests/container/CMakeLists.txt | 7 +- .../test_dynamic_graph_cpo_edge_map.cpp | 110 +-- ...ph_moem.cpp => test_dynamic_graph_mom.cpp} | 96 +-- ...ph_voem.cpp => test_dynamic_graph_vom.cpp} | 70 +- .../dynamic_graph/test_dynamic_graph_voum.cpp | 738 ++++++++++++++++++ 24 files changed, 1075 insertions(+), 241 deletions(-) rename include/graph/container/traits/{moem_graph_traits.hpp => mom_graph_traits.hpp} (90%) rename include/graph/container/traits/{voem_graph_traits.hpp => vom_graph_traits.hpp} (92%) create mode 100644 include/graph/container/traits/voum_graph_traits.hpp rename tests/container/dynamic_graph/{test_dynamic_graph_moem.cpp => test_dynamic_graph_mom.cpp} (88%) rename tests/container/dynamic_graph/{test_dynamic_graph_voem.cpp => test_dynamic_graph_vom.cpp} (88%) create mode 100644 tests/container/dynamic_graph/test_dynamic_graph_voum.cpp diff --git a/README.md b/README.md index c5ac828..361cdb0 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ - **13 algorithms** — Dijkstra, Bellman-Ford, BFS, DFS, topological sort, connected components, articulation points, biconnected components, MST, triangle counting, MIS, label propagation, Jaccard coefficient - **7 lazy views** — vertexlist, edgelist, incidence, neighbors, BFS, DFS, topological sort — all composable with range adaptors - **Customization Point Objects (CPOs)** — adapt existing data structures without modifying them -- **3 containers, 26 trait combinations** — `dynamic_graph`, `compressed_graph`, `undirected_adjacency_list` with mix-and-match vertex/edge storage +- **3 containers, 27 trait combinations** — `dynamic_graph`, `compressed_graph`, `undirected_adjacency_list` with mix-and-match vertex/edge storage - **4261 tests passing** — comprehensive Catch2 test suite --- @@ -78,7 +78,7 @@ Both share a common descriptor system and customization-point interface. |----------|-----------------|---------| | **Algorithms** | Dijkstra, Bellman-Ford, BFS, DFS, topological sort, connected components, articulation points, biconnected components, MST, triangle counting, MIS, label propagation, Jaccard | [Algorithm reference](docs/status/implementation_matrix.md#algorithms) | | **Views** | vertexlist, edgelist, incidence, neighbors, BFS, DFS, topological sort | [View reference](docs/status/implementation_matrix.md#views) | -| **Containers** | `dynamic_graph` (26 trait combos), `compressed_graph` (CSR), `undirected_adjacency_list` | [Container reference](docs/status/implementation_matrix.md#containers) | +| **Containers** | `dynamic_graph` (27 trait combos), `compressed_graph` (CSR), `undirected_adjacency_list` | [Container reference](docs/status/implementation_matrix.md#containers) | | **CPOs** | 19 customization point objects (vertices, edges, target_id, vertex_value, edge_value, …) | [CPO reference](docs/reference/cpo-reference.md) | | **Concepts** | 9 graph concepts (edge, vertex, adjacency_list, …) | [Concepts reference](docs/reference/concepts.md) | @@ -174,4 +174,4 @@ Distributed under the [Boost Software License 1.0](LICENSE). --- -**Status:** 4261 / 4261 tests passing · 13 algorithms · 7 views · 3 containers · 26 trait combinations · C++20 · BSL-1.0 +**Status:** 4261 / 4261 tests passing · 13 algorithms · 7 views · 3 containers · 27 trait combinations · C++20 · BSL-1.0 diff --git a/agents/archive/dynamic_graph_todo.md b/agents/archive/dynamic_graph_todo.md index af5b227..d7cb4a1 100644 --- a/agents/archive/dynamic_graph_todo.md +++ b/agents/archive/dynamic_graph_todo.md @@ -20,8 +20,8 @@ - ✅ uous (unordered_map + unordered_set): Basic + CPO tests COMPLETE (53 test cases, 563 assertions) **Phase 4.3: Map-Based Edge Containers - COMPLETE ✅** -- ✅ voem (vector + map edges): Basic + CPO tests COMPLETE (46 test cases, 292 assertions) -- ✅ moem (map + map edges): Basic + CPO tests COMPLETE (53 test cases, 578 assertions) +- ✅ vom (vector + map edges): Basic + CPO tests COMPLETE (46 test cases, 292 assertions) +- ✅ mom (map + map edges): Basic + CPO tests COMPLETE (53 test cases, 578 assertions) **Phase 4 Overall: 100% COMPLETE ✅ (10/10 traits implemented)** @@ -102,8 +102,8 @@ - test_dynamic_graph_uous.cpp + test_dynamic_graph_cpo_uous.cpp ✅ *Map Edge Containers (4 files):* -- test_dynamic_graph_voem.cpp + test_dynamic_graph_cpo_voem.cpp ✅ -- test_dynamic_graph_moem.cpp + test_dynamic_graph_cpo_moem.cpp ✅ +- test_dynamic_graph_vom.cpp + test_dynamic_graph_cpo_vom.cpp ✅ +- test_dynamic_graph_mom.cpp + test_dynamic_graph_cpo_mom.cpp ✅ *Additional Test Files:* - test_dynamic_graph_common.cpp ✅ @@ -965,42 +965,42 @@ All prerequisites for both std::set (Phase 4.1) and std::unordered_set (Phase 4. | 4.3.1b | Identify changes needed for map-based edge access (key vs iterator) | ✅ DONE | | 4.3.1c | Design edge_descriptor changes for map-based edges (if any) | ✅ DONE | -**Step 4.3.2: Create voem_graph_traits (vector + edge map)** ✅ **COMPLETE** (2024-12-28) +**Step 4.3.2: Create vom_graph_traits (vector + edge map)** ✅ **COMPLETE** (2024-12-28) **Implementation Summary:** - Added is_map_based_edge_container concept to container_utility.hpp - Added emplace_edge helper for pair-wrapped edge insertion - Updated edge_descriptor::target_id() to unwrap map pairs - Updated edge_value CPO to extract values from map pairs -- Created voem_graph_traits.hpp with std::map edges -- Created test_dynamic_graph_voem.cpp (~741 lines, 25 test cases) -- Created test_dynamic_graph_cpo_voem.cpp (~1273 lines, 21 test cases) +- Created vom_graph_traits.hpp with std::map edges +- Created test_dynamic_graph_vom.cpp (~741 lines, 25 test cases) +- Created test_dynamic_graph_cpo_vom.cpp (~1273 lines, 21 test cases) - All 46 test cases passing | Step | Task | Status | |------|------|--------| -| 4.3.2a | Create voem_graph_traits.hpp | ✅ DONE | +| 4.3.2a | Create vom_graph_traits.hpp | ✅ DONE | | 4.3.2b | Update load_edges or edge insertion for map semantics | ✅ DONE | -| 4.3.2c | Create test_dynamic_graph_voem.cpp basic tests (~800 lines) | ✅ DONE | -| 4.3.2d | Create test_dynamic_graph_cpo_voem.cpp CPO tests (~1200 lines) | ✅ DONE | +| 4.3.2c | Create test_dynamic_graph_vom.cpp basic tests (~800 lines) | ✅ DONE | +| 4.3.2d | Create test_dynamic_graph_cpo_vom.cpp CPO tests (~1200 lines) | ✅ DONE | | 4.3.2e | Update CMakeLists.txt and verify tests pass | ✅ DONE | -**Step 4.3.3: Create moem_graph_traits (map vertices + edge map)** ✅ **COMPLETE** (2024-12-28) +**Step 4.3.3: Create mom_graph_traits (map vertices + edge map)** ✅ **COMPLETE** (2024-12-28) **Implementation Summary:** -- Created moem_graph_traits.hpp with std::map vertices and std::map edges +- Created mom_graph_traits.hpp with std::map vertices and std::map edges - Simplified operator[] using at() for both container types (throws if not found) - Added is_map_based_vertex_container concept - Tests derived from mos (which also has map vertices) -- Created test_dynamic_graph_moem.cpp (~1123 lines, 27 test cases) -- Created test_dynamic_graph_cpo_moem.cpp (~1274 lines, 26 test cases) +- Created test_dynamic_graph_mom.cpp (~1123 lines, 27 test cases) +- Created test_dynamic_graph_cpo_mom.cpp (~1274 lines, 26 test cases) - All 53 test cases passing | Step | Task | Status | |------|------|--------| -| 4.3.3a | Create moem_graph_traits.hpp | ✅ DONE | -| 4.3.3b | Create test_dynamic_graph_moem.cpp (~800 lines) | ✅ DONE | -| 4.3.3c | Create test_dynamic_graph_cpo_moem.cpp (~1200 lines) | ✅ DONE | +| 4.3.3a | Create mom_graph_traits.hpp | ✅ DONE | +| 4.3.3b | Create test_dynamic_graph_mom.cpp (~800 lines) | ✅ DONE | +| 4.3.3c | Create test_dynamic_graph_cpo_mom.cpp (~1200 lines) | ✅ DONE | | 4.3.3d | Update CMakeLists.txt and verify tests pass | ✅ DONE | --- @@ -1010,7 +1010,7 @@ All prerequisites for both std::set (Phase 4.1) and std::unordered_set (Phase 4. **Total New Traits:** - Set edges: vos, dos, mos, uos (4 traits) - Unordered set edges: vous, dous, mous, uous (4 traits) -- Map edges: voem, moem (2 traits) +- Map edges: vom, mom (2 traits) **Implementation Changes:** - operator<=> for dynamic_edge (generates <, >, <=, >=) @@ -1038,8 +1038,8 @@ that map/unordered_map-based vertex containers work correctly with various ID ty the standard integral types. **Current State Analysis:** -- Map-based containers (mos, moem, mol, etc.) already support `std::string` vertex IDs -- Tests exist with `std::string` IDs in: mos, mous, moem, mol, mov, mod, mofl tests +- Map-based containers (mos, mom, mol, etc.) already support `std::string` vertex IDs +- Tests exist with `std::string` IDs in: mos, mous, mom, mol, mov, mod, mofl tests - Basic string ID functionality is validated but not comprehensively tested - No tests exist for: double IDs, compound types, custom types with complex comparison @@ -1088,7 +1088,7 @@ the standard integral types. |-------|-------------|-----------|-----------------| | mos | 3 test cases | 22 sections | ✅ Comprehensive | | mous | 3 test cases | 22 sections | ✅ Comprehensive | -| moem | 3 test cases | 22 sections | ✅ Comprehensive | +| mom | 3 test cases | 22 sections | ✅ Comprehensive | | mol | 3 test cases | ~20 sections | ✅ Comprehensive | | mov | 3 test cases | ~20 sections | ✅ Comprehensive | | mod | 3 test cases | ~20 sections | ✅ Comprehensive | @@ -2080,7 +2080,7 @@ For Phase 5 (non-integral): 1. **Finish Phase 4.1.4** - Complete mos CPO tests (1-2 days) 2. **Phase 4.1.5** - uos (unordered_map + set) basic + CPO tests (2-3 days) 3. **Phase 4.2** - Unordered set edge containers (vous, dous, mous, uous) (4-5 days) -4. **Phase 4.3** - Map-based edge containers (voem, moem) (3-4 days) +4. **Phase 4.3** - Map-based edge containers (vom, mom) (3-4 days) 5. **Phase 7.3** - Edge cases (ongoing, 2-3 days) 6. **Phase 6** - Integration tests (2-3 days) 7. **Phase 7.1** - Mutation operations (2-3 days) diff --git a/agents/archive/test_reorganization_execution_plan.md b/agents/archive/test_reorganization_execution_plan.md index c567000..9569e0d 100644 --- a/agents/archive/test_reorganization_execution_plan.md +++ b/agents/archive/test_reorganization_execution_plan.md @@ -149,8 +149,8 @@ git mv tests/test_dynamic_graph_uol.cpp tests/container/dynamic_graph/ git mv tests/test_dynamic_graph_uov.cpp tests/container/dynamic_graph/ git mv tests/test_dynamic_graph_uod.cpp tests/container/dynamic_graph/ git mv tests/test_dynamic_graph_vos.cpp tests/container/dynamic_graph/ -git mv tests/test_dynamic_graph_voem.cpp tests/container/dynamic_graph/ -git mv tests/test_dynamic_graph_moem.cpp tests/container/dynamic_graph/ +git mv tests/test_dynamic_graph_vom.cpp tests/container/dynamic_graph/ +git mv tests/test_dynamic_graph_mom.cpp tests/container/dynamic_graph/ git mv tests/test_dynamic_graph_dos.cpp tests/container/dynamic_graph/ git mv tests/test_dynamic_graph_mos.cpp tests/container/dynamic_graph/ git mv tests/test_dynamic_graph_uos.cpp tests/container/dynamic_graph/ @@ -190,8 +190,8 @@ git mv tests/test_dynamic_graph_cpo_uol.cpp tests/container/dynamic_graph/ git mv tests/test_dynamic_graph_cpo_uov.cpp tests/container/dynamic_graph/ git mv tests/test_dynamic_graph_cpo_uod.cpp tests/container/dynamic_graph/ git mv tests/test_dynamic_graph_cpo_vos.cpp tests/container/dynamic_graph/ -git mv tests/test_dynamic_graph_cpo_voem.cpp tests/container/dynamic_graph/ -git mv tests/test_dynamic_graph_cpo_moem.cpp tests/container/dynamic_graph/ +git mv tests/test_dynamic_graph_cpo_vom.cpp tests/container/dynamic_graph/ +git mv tests/test_dynamic_graph_cpo_mom.cpp tests/container/dynamic_graph/ git mv tests/test_dynamic_graph_cpo_dos.cpp tests/container/dynamic_graph/ git mv tests/test_dynamic_graph_cpo_mos.cpp tests/container/dynamic_graph/ git mv tests/test_dynamic_graph_cpo_uos.cpp tests/container/dynamic_graph/ @@ -309,8 +309,8 @@ add_executable(graph3_container_tests dynamic_graph/test_dynamic_graph_uov.cpp dynamic_graph/test_dynamic_graph_uod.cpp dynamic_graph/test_dynamic_graph_vos.cpp - dynamic_graph/test_dynamic_graph_voem.cpp - dynamic_graph/test_dynamic_graph_moem.cpp + dynamic_graph/test_dynamic_graph_vom.cpp + dynamic_graph/test_dynamic_graph_mom.cpp dynamic_graph/test_dynamic_graph_dos.cpp dynamic_graph/test_dynamic_graph_mos.cpp dynamic_graph/test_dynamic_graph_uos.cpp @@ -350,8 +350,8 @@ add_executable(graph3_container_tests dynamic_graph/test_dynamic_graph_cpo_uov.cpp dynamic_graph/test_dynamic_graph_cpo_uod.cpp dynamic_graph/test_dynamic_graph_cpo_vos.cpp - dynamic_graph/test_dynamic_graph_cpo_voem.cpp - dynamic_graph/test_dynamic_graph_cpo_moem.cpp + dynamic_graph/test_dynamic_graph_cpo_vom.cpp + dynamic_graph/test_dynamic_graph_cpo_mom.cpp dynamic_graph/test_dynamic_graph_cpo_dos.cpp dynamic_graph/test_dynamic_graph_cpo_mos.cpp dynamic_graph/test_dynamic_graph_cpo_uos.cpp diff --git a/agents/archive/view_plan.md b/agents/archive/view_plan.md index ed155ba..727bbad 100644 --- a/agents/archive/view_plan.md +++ b/agents/archive/view_plan.md @@ -739,7 +739,7 @@ Created `tests/views/test_edgelist.cpp`: - Value function receives descriptor - Structured bindings work: `for (auto [e] : edgelist(g))` and `for (auto [e, val] : edgelist(g, evf))` - Tests pass with sanitizers -- Map-based containers supported (vov, voem, mov, moem) +- Map-based containers supported (vov, vom, mov, mom) **Commit Message**: ``` diff --git a/agents/archive/view_strategy.md b/agents/archive/view_strategy.md index 216389c..7c80212 100644 --- a/agents/archive/view_strategy.md +++ b/agents/archive/view_strategy.md @@ -666,11 +666,11 @@ with both index-based and iterator-based vertex/edge storage: **Edge Container Types**: - `vector` - Random-access edges -- `map` (voem, moem, etc.) - Sorted edges by target, deduplicated +- `map` (vom, mom, etc.) - Sorted edges by target, deduplicated - `list` - Forward-only edges **Test Matrix** (minimum coverage per view): -| View | vov | mov | voem | moem | +| View | vov | mov | vom | mom | |------|-----|-----|------|------| | vertexlist | ✓ | ✓ | ✓ | ✓ | | incidence | ✓ | ✓ | ✓ | ✓ | diff --git a/agents/bgl2_comparison_result.md b/agents/bgl2_comparison_result.md index 416e1ec..9caed7a 100644 --- a/agents/bgl2_comparison_result.md +++ b/agents/bgl2_comparison_result.md @@ -435,9 +435,9 @@ using G = dynamic_graph) { ### 4.1 Minimal Viable Changes -**Required for Phase 4.3.2 (voem_graph_traits):** +**Required for Phase 4.3.2 (vom_graph_traits):** 1. **Add `is_map_based_edge_container` concept** (container_utility.hpp) ```cpp @@ -331,7 +331,7 @@ These can be added later in Phase 4.3: ### 4.3 Testing Strategy -For `voem_graph_traits` (vector vertices + map edges): +For `vom_graph_traits` (vector vertices + map edges): 1. **Basic functionality tests:** - Edge insertion (automatic target_id keying) @@ -346,7 +346,7 @@ For `voem_graph_traits` (vector vertices + map edges): - `contains_edge(g, uid, vid)` works correctly 3. **Comparison with existing traits:** - - voem should behave like vov except: + - vom should behave like vov except: - No parallel edges (map uniqueness) - Edges sorted by target_id - O(log n) lookup instead of O(n) @@ -405,7 +405,7 @@ For `voem_graph_traits` (vector vertices + map edges): **Problem:** Map uniqueness means only one edge per target, even if multiple edges should exist. **Mitigation:** -- Document this limitation clearly in voem/moem trait headers +- Document this limitation clearly in vom/mom trait headers - For multigraphs, users should use vector/list/set edge containers - This is a feature, not a bug: map-based edges are for graphs with unique edges @@ -444,21 +444,21 @@ For typical edge (1-2 VId members): ~8-16 bytes overhead per edge - [x] Design edge_descriptor changes for map-based edges - [x] Document findings in edge_map_analysis.md -### Phase 4.3.2 (voem_graph_traits) - ⏳ PENDING +### Phase 4.3.2 (vom_graph_traits) - ⏳ PENDING - [ ] Add `is_map_based_edge_container` concept to container_utility.hpp - [ ] Add `emplace_edge` helper function to container_utility.hpp - [ ] Update `edge_descriptor::target_id()` to handle map pair unwrapping - [ ] Update `load_edges` functions to use map-aware edge construction -- [ ] Create voem_graph_traits.hpp -- [ ] Create test_dynamic_graph_voem.cpp with basic tests -- [ ] Create test_dynamic_graph_cpo_voem.cpp with CPO tests +- [ ] Create vom_graph_traits.hpp +- [ ] Create test_dynamic_graph_vom.cpp with basic tests +- [ ] Create test_dynamic_graph_cpo_vom.cpp with CPO tests - [ ] Verify all tests pass -### Phase 4.3.3 (moem_graph_traits) - ⏳ PENDING +### Phase 4.3.3 (mom_graph_traits) - ⏳ PENDING -- [ ] Create moem_graph_traits.hpp (reuse voem infrastructure) -- [ ] Create test files for moem +- [ ] Create mom_graph_traits.hpp (reuse vom infrastructure) +- [ ] Create test files for mom - [ ] Verify all tests pass ## 9. Conclusion @@ -468,13 +468,13 @@ For typical edge (1-2 VId members): ~8-16 bytes overhead per edge 1. **Container Utility:** Add concept detection and helper for map pair construction 2. **Edge Descriptor:** Update target_id extraction to unwrap map pairs 3. **Load Functions:** Add conditional logic for map-based edge insertion -4. **Trait Headers:** Define new voem/moem traits with `std::map` +4. **Trait Headers:** Define new vom/mom traits with `std::map` **Estimated Effort:** - Container utility changes: ~50 lines - Edge descriptor updates: ~20 lines - Load function updates: ~30 lines per load variant -- Trait headers: ~40 lines each (voem, moem) +- Trait headers: ~40 lines each (vom, mom) - Test files: ~2000 lines per trait (existing template) **Total:** ~200 lines of core changes + ~4000 lines of tests for two new traits. diff --git a/docs/contributing/architecture.md b/docs/contributing/architecture.md index 704fe67..0ac4f4f 100644 --- a/docs/contributing/architecture.md +++ b/docs/contributing/architecture.md @@ -41,7 +41,7 @@ graph-v3/ │ │ ├── jaccard.hpp │ │ └── traversal_common.hpp │ ├── container/ # Library-provided containers -│ │ ├── dynamic_graph.hpp # 26 trait combinations +│ │ ├── dynamic_graph.hpp # 27 trait combinations │ │ ├── compressed_graph.hpp # CSR format │ │ ├── undirected_adjacency_list.hpp │ │ ├── container_utility.hpp @@ -206,7 +206,7 @@ Tests mirror the source structure: |----------------|---------------| | `tests/adj_list/` | Descriptor construction, CPO dispatch, type aliases | | `tests/algorithms/` | One test file per algorithm (Dijkstra, BFS, DFS, …) | -| `tests/container/` | Container conformance — all 26 trait combinations | +| `tests/container/` | Container conformance — all 27 trait combinations | | `tests/edge_list/` | Edge list model tests | | `tests/views/` | View iteration and composition | | `tests/common/` | Shared test utilities and helpers | diff --git a/docs/getting-started.md b/docs/getting-started.md index 54d470f..306170a 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -112,7 +112,7 @@ Vertices: 4 ### Using `dynamic_graph` For richer features — edge values, flexible container selection, partitioning — -use `dynamic_graph` with one of the 26 trait combinations. +use `dynamic_graph` with one of the 27 trait combinations. ```cpp #include diff --git a/docs/index.md b/docs/index.md index be433d5..530318c 100644 --- a/docs/index.md +++ b/docs/index.md @@ -9,7 +9,7 @@ - [Getting Started](getting-started.md) — installation, first graph, first algorithm - [Adjacency Lists](user-guide/adjacency-lists.md) — range-of-ranges model, concepts, CPOs - [Edge Lists](user-guide/edge-lists.md) — flat sourced-edge model, concepts, patterns -- [Containers](user-guide/containers.md) — `dynamic_graph`, `compressed_graph`, `undirected_adjacency_list`, 26 trait combinations +- [Containers](user-guide/containers.md) — `dynamic_graph`, `compressed_graph`, `undirected_adjacency_list`, 27 trait combinations - [Views](user-guide/views.md) — lazy traversal views (BFS, DFS, topological sort, etc.) - [Algorithms](user-guide/algorithms.md) — Dijkstra, Bellman-Ford, MST, connected components, and more diff --git a/docs/migration-from-v2.md b/docs/migration-from-v2.md index 20bd9fe..141c1e6 100644 --- a/docs/migration-from-v2.md +++ b/docs/migration-from-v2.md @@ -50,7 +50,7 @@ edges without holding references to the underlying container. - Vertex storage in `map` and `unordered_map` (for sparse vertex IDs). - Edge storage in `map`, `set`, `unordered_set` (for sorted or deduplicated edges). - Non-integral vertex IDs. - - 26 vertex×edge container combinations via traits (see + - 27 vertex×edge container combinations via traits (see [Containers](user-guide/containers.md)). ### Graph Container Interface diff --git a/docs/status/implementation_matrix.md b/docs/status/implementation_matrix.md index eb9d8ec..6e6d985 100644 --- a/docs/status/implementation_matrix.md +++ b/docs/status/implementation_matrix.md @@ -82,9 +82,10 @@ Utility header: `container_utility.hpp`. | `std::list` | Bidirectional | O(1) insertion/removal anywhere | `l` | | `std::set` | Bidirectional | Sorted, deduplicated | `s` | | `std::unordered_set` | Forward | Hash-based, O(1) avg lookup | `us` | -| `std::map` | Bidirectional | Sorted by target_id key | `em` | +| `std::map` | Bidirectional | Sorted by target_id key | `m` | +| `std::unordered_map` | Forward | Hash-based, O(1) avg lookup by target_id | `um` | -### All 26 trait files +### All 27 trait files Naming convention: `{vertex}o{edge}_graph_traits.hpp` @@ -96,26 +97,27 @@ Naming convention: `{vertex}o{edge}_graph_traits.hpp` | 4 | `vol_graph_traits.hpp` | vector | list | | 5 | `vos_graph_traits.hpp` | vector | set | | 6 | `vous_graph_traits.hpp` | vector | unordered_set | -| 7 | `voem_graph_traits.hpp` | vector | map | -| 8 | `dov_graph_traits.hpp` | deque | vector | -| 9 | `dod_graph_traits.hpp` | deque | deque | -| 10 | `dofl_graph_traits.hpp` | deque | forward_list | -| 11 | `dol_graph_traits.hpp` | deque | list | -| 12 | `dos_graph_traits.hpp` | deque | set | -| 13 | `dous_graph_traits.hpp` | deque | unordered_set | -| 14 | `mov_graph_traits.hpp` | map | vector | -| 15 | `mod_graph_traits.hpp` | map | deque | -| 16 | `mofl_graph_traits.hpp` | map | forward_list | -| 17 | `mol_graph_traits.hpp` | map | list | -| 18 | `mos_graph_traits.hpp` | map | set | -| 19 | `mous_graph_traits.hpp` | map | unordered_set | -| 20 | `moem_graph_traits.hpp` | map | map | -| 21 | `uov_graph_traits.hpp` | unordered_map | vector | -| 22 | `uod_graph_traits.hpp` | unordered_map | deque | -| 23 | `uofl_graph_traits.hpp` | unordered_map | forward_list | -| 24 | `uol_graph_traits.hpp` | unordered_map | list | -| 25 | `uos_graph_traits.hpp` | unordered_map | set | -| 26 | `uous_graph_traits.hpp` | unordered_map | unordered_set | +| 7 | `vom_graph_traits.hpp` | vector | map | +| 8 | `voum_graph_traits.hpp` | vector | unordered_map | +| 9 | `dov_graph_traits.hpp` | deque | vector | +| 10 | `dod_graph_traits.hpp` | deque | deque | +| 11 | `dofl_graph_traits.hpp` | deque | forward_list | +| 12 | `dol_graph_traits.hpp` | deque | list | +| 13 | `dos_graph_traits.hpp` | deque | set | +| 14 | `dous_graph_traits.hpp` | deque | unordered_set | +| 15 | `mov_graph_traits.hpp` | map | vector | +| 16 | `mod_graph_traits.hpp` | map | deque | +| 17 | `mofl_graph_traits.hpp` | map | forward_list | +| 18 | `mol_graph_traits.hpp` | map | list | +| 19 | `mos_graph_traits.hpp` | map | set | +| 20 | `mous_graph_traits.hpp` | map | unordered_set | +| 21 | `mom_graph_traits.hpp` | map | map | +| 22 | `uov_graph_traits.hpp` | unordered_map | vector | +| 23 | `uod_graph_traits.hpp` | unordered_map | deque | +| 24 | `uofl_graph_traits.hpp` | unordered_map | forward_list | +| 25 | `uol_graph_traits.hpp` | unordered_map | list | +| 26 | `uos_graph_traits.hpp` | unordered_map | set | +| 27 | `uous_graph_traits.hpp` | unordered_map | unordered_set | --- diff --git a/docs/user-guide/containers.md b/docs/user-guide/containers.md index c532df3..9d086e2 100644 --- a/docs/user-guide/containers.md +++ b/docs/user-guide/containers.md @@ -137,9 +137,10 @@ container abbreviation, the letter `o`, edge container abbreviation. | `std::list` | Bidirectional | O(1) insertion/removal anywhere. Edges added to the back. | `l` | | `std::set` | Bidirectional | Sorted, deduplicated target id | `s` | | `std::unordered_set` | Forward | Hash-based, O(1) avg lookup, deduplicated target id| `us` | -| `std::map` | Bidirectional | Sorted by target_id key, deduplicated target id | `em` | +| `std::map` | Bidirectional | Sorted by target_id key, deduplicated target id | `m` | +| `std::unordered_map` | Forward | Hash-based, O(1) avg lookup, deduplicated target id | `um` | -#### Full 26-combination matrix +#### Full 27-combination matrix Each trait struct is in `graph::container` and has its own header in `include/graph/container/traits/`. @@ -152,7 +153,8 @@ Each trait struct is in `graph::container` and has its own header in | `vol_graph_traits` | `vector` | `list` | `traits/vol_graph_traits.hpp` | | `vos_graph_traits` | `vector` | `set` | `traits/vos_graph_traits.hpp` | | `vous_graph_traits` | `vector` | `unordered_set` | `traits/vous_graph_traits.hpp` | -| `voem_graph_traits` | `vector` | `map` | `traits/voem_graph_traits.hpp` | +| `vom_graph_traits` | `vector` | `map` | `traits/vom_graph_traits.hpp` | +| `voum_graph_traits` | `vector` | `unordered_map` | `traits/voum_graph_traits.hpp` | | `dov_graph_traits` | `deque` | `vector` | `traits/dov_graph_traits.hpp` | | `dod_graph_traits` | `deque` | `deque` | `traits/dod_graph_traits.hpp` | | `dofl_graph_traits` | `deque` | `forward_list` | `traits/dofl_graph_traits.hpp` | @@ -165,7 +167,7 @@ Each trait struct is in `graph::container` and has its own header in | `mol_graph_traits` | `map` | `list` | `traits/mol_graph_traits.hpp` | | `mos_graph_traits` | `map` | `set` | `traits/mos_graph_traits.hpp` | | `mous_graph_traits` | `map` | `unordered_set` | `traits/mous_graph_traits.hpp` | -| `moem_graph_traits` | `map` | `map` | `traits/moem_graph_traits.hpp` | +| `mom_graph_traits` | `map` | `map` | `traits/mom_graph_traits.hpp` | | `uov_graph_traits` | `unordered_map` | `vector` | `traits/uov_graph_traits.hpp` | | `uod_graph_traits` | `unordered_map` | `deque` | `traits/uod_graph_traits.hpp` | | `uofl_graph_traits` | `unordered_map` | `forward_list` | `traits/uofl_graph_traits.hpp` | @@ -268,7 +270,7 @@ G g; // All CPOs, views, and algorithms work as normal ``` -> **Tip:** Model your traits struct on one of the 26 built-in traits headers in +> **Tip:** Model your traits struct on one of the 27 built-in traits headers in > `include/graph/container/traits/`. The simplest starting point is > `vov_graph_traits.hpp`. @@ -696,7 +698,7 @@ struct), if they are mutable. | Memory efficiency | Medium | Best (CSR) | Highest overhead | Zero overhead (existing data) | | Cache locality | Depends on trait | Excellent | Poor (linked-list) | Depends on containers used | | Multi-partite | No | Yes | No | No | -| Container flexibility | 26 trait combos | Fixed (CSR) | Configurable random access vertex container | Any forward_range of forward_ranges | +| Container flexibility | 27 trait combos | Fixed (CSR) | Configurable random access vertex container | Any forward_range of forward_ranges | **Custom graphs.** See [Section 5 (Custom Graphs)](#5-custom-graphs) for how to use your own graph data structure with all library views and algorithms by overriding graph CPOs. diff --git a/include/graph/container/traits/moem_graph_traits.hpp b/include/graph/container/traits/mom_graph_traits.hpp similarity index 90% rename from include/graph/container/traits/moem_graph_traits.hpp rename to include/graph/container/traits/mom_graph_traits.hpp index f6aa338..26b24ca 100644 --- a/include/graph/container/traits/moem_graph_traits.hpp +++ b/include/graph/container/traits/mom_graph_traits.hpp @@ -14,7 +14,7 @@ class dynamic_vertex; template class dynamic_graph; -// moem_graph_traits +// mom_graph_traits // Vertices: std::map (ordered map keyed by vertex ID) // Edges: std::map (ordered map keyed by target vertex ID) // @@ -34,18 +34,18 @@ class dynamic_graph; // - Efficient vertex insertion/deletion without affecting other IDs // - Memory overhead: ~24-32 bytes per vertex + ~8-16 bytes per edge // -// Parameter semantics mirror voem_graph_traits. +// Parameter semantics mirror vom_graph_traits. template -struct moem_graph_traits { +struct mom_graph_traits { using edge_value_type = EV; using vertex_value_type = VV; using graph_value_type = GV; using vertex_id_type = VId; static constexpr bool sourced = Sourced; - using edge_type = dynamic_edge; - using vertex_type = dynamic_vertex; - using graph_type = dynamic_graph; + using edge_type = dynamic_edge; + using vertex_type = dynamic_vertex; + using graph_type = dynamic_graph; using vertices_type = std::map; // Map keyed by vertex ID using edges_type = std::map; // Map keyed by target vertex ID diff --git a/include/graph/container/traits/voem_graph_traits.hpp b/include/graph/container/traits/vom_graph_traits.hpp similarity index 92% rename from include/graph/container/traits/voem_graph_traits.hpp rename to include/graph/container/traits/vom_graph_traits.hpp index 28e3fc3..a362be4 100644 --- a/include/graph/container/traits/voem_graph_traits.hpp +++ b/include/graph/container/traits/vom_graph_traits.hpp @@ -15,7 +15,7 @@ class dynamic_vertex; template class dynamic_graph; -// voem_graph_traits +// vom_graph_traits // Vertices: std::vector (contiguous; random access by vertex ID) // Edges: std::map (ordered map keyed by target vertex ID) // @@ -34,16 +34,16 @@ class dynamic_graph; // // Parameter semantics mirror vofl_graph_traits. template -struct voem_graph_traits { +struct vom_graph_traits { using edge_value_type = EV; using vertex_value_type = VV; using graph_value_type = GV; using vertex_id_type = VId; static constexpr bool sourced = Sourced; - using edge_type = dynamic_edge; - using vertex_type = dynamic_vertex; - using graph_type = dynamic_graph; + using edge_type = dynamic_edge; + using vertex_type = dynamic_vertex; + using graph_type = dynamic_graph; using vertices_type = std::vector; using edges_type = std::map; // Map keyed by target vertex ID diff --git a/include/graph/container/traits/voum_graph_traits.hpp b/include/graph/container/traits/voum_graph_traits.hpp new file mode 100644 index 0000000..7c24f3e --- /dev/null +++ b/include/graph/container/traits/voum_graph_traits.hpp @@ -0,0 +1,56 @@ +#pragma once + +#include +#include + +namespace graph::container { + +// Forward declarations +template +class dynamic_edge; + +template +class dynamic_vertex; + +template +class dynamic_graph; + +// voum_graph_traits +// Vertices: std::vector (contiguous; random access by vertex ID) +// Edges: std::unordered_map (hash map keyed by target vertex ID) +// +// Key characteristics: +// - O(1) average edge lookup by target vertex ID using unordered_map::find(vid) +// - Edges automatically deduplicated (only one edge per target vertex) +// - Edges stored in unordered fashion (hash-bucket order, not sorted) +// - Forward iterators only (no bidirectional or random access) +// - Edge insertion/deletion O(1) average, O(n) worst case +// - Requires std::hash and operator== on VId (for hash map) +// +// Compared to vom_graph_traits (std::map edges): +// - vom: O(log n) operations, sorted order, bidirectional iterators +// - voum: O(1) average operations, unordered, forward iterators only +// +// Use cases: +// - Graphs with unique edges (no parallel edges to same target) +// - Fast edge lookup by target: find_vertex_edge(g, u, v) in O(1) average +// - Memory overhead: ~8-16 bytes per edge (pair) +// +// Parameter semantics mirror vom_graph_traits. +template +struct voum_graph_traits { + using edge_value_type = EV; + using vertex_value_type = VV; + using graph_value_type = GV; + using vertex_id_type = VId; + static constexpr bool sourced = Sourced; + + using edge_type = dynamic_edge; + using vertex_type = dynamic_vertex; + using graph_type = dynamic_graph; + + using vertices_type = std::vector; + using edges_type = std::unordered_map; // Hash map keyed by target vertex ID +}; + +} // namespace graph::container diff --git a/tests/common/graph_test_types.hpp b/tests/common/graph_test_types.hpp index 3ab77e6..c26665c 100644 --- a/tests/common/graph_test_types.hpp +++ b/tests/common/graph_test_types.hpp @@ -50,8 +50,10 @@ #include #include // Edge multiset containers (sorted edges with duplicates allowed) -#include -#include +#include +#include +// Edge unordered_map containers (hash-based, deduplicated by target_id) +#include #include namespace graph::test { @@ -349,22 +351,33 @@ struct uofl_tag { * @brief Tag for vector + map container type * @note Edges are sorted by target_id (map key), deduplicated (only one edge per target) */ -struct voem_tag { - static constexpr const char* name = "voem"; +struct vom_tag { + static constexpr const char* name = "vom"; template - using traits = graph::container::voem_graph_traits; + using traits = graph::container::vom_graph_traits; }; /** * @brief Tag for map + map container type * @note Vertices are sparse, edges are sorted by target_id (map key), deduplicated */ -struct moem_tag { - static constexpr const char* name = "moem"; +struct mom_tag { + static constexpr const char* name = "mom"; template - using traits = graph::container::moem_graph_traits; + using traits = graph::container::mom_graph_traits; +}; + +/** + * @brief Tag for vector + unordered_map container type + * @note Edges are hash-based, deduplicated (only one edge per target), unordered + */ +struct voum_tag { + static constexpr const char* name = "voum"; + + template + using traits = graph::container::voum_graph_traits; }; // ============================================================================= diff --git a/tests/container/CMakeLists.txt b/tests/container/CMakeLists.txt index d59f006..ab3993d 100644 --- a/tests/container/CMakeLists.txt +++ b/tests/container/CMakeLists.txt @@ -22,8 +22,9 @@ add_executable(graph3_container_tests dynamic_graph/test_dynamic_graph_uov.cpp dynamic_graph/test_dynamic_graph_uod.cpp dynamic_graph/test_dynamic_graph_vos.cpp - dynamic_graph/test_dynamic_graph_voem.cpp - dynamic_graph/test_dynamic_graph_moem.cpp + dynamic_graph/test_dynamic_graph_vom.cpp + dynamic_graph/test_dynamic_graph_voum.cpp + dynamic_graph/test_dynamic_graph_mom.cpp dynamic_graph/test_dynamic_graph_dos.cpp dynamic_graph/test_dynamic_graph_mos.cpp dynamic_graph/test_dynamic_graph_uos.cpp @@ -53,7 +54,7 @@ add_executable(graph3_container_tests dynamic_graph/test_dynamic_graph_cpo_map_vertices.cpp dynamic_graph/test_dynamic_graph_cpo_unordered_map_vertices.cpp - # dynamic_graph - CPO tests (edge map containers - voem, moem) + # dynamic_graph - CPO tests (edge map containers - vom, mom) dynamic_graph/test_dynamic_graph_cpo_edge_map.cpp # undirected_adjacency_list diff --git a/tests/container/dynamic_graph/test_dynamic_graph_cpo_edge_map.cpp b/tests/container/dynamic_graph/test_dynamic_graph_cpo_edge_map.cpp index aeba686..7265a48 100644 --- a/tests/container/dynamic_graph/test_dynamic_graph_cpo_edge_map.cpp +++ b/tests/container/dynamic_graph/test_dynamic_graph_cpo_edge_map.cpp @@ -1,12 +1,12 @@ /** * @file test_dynamic_graph_cpo_edge_map.cpp - * @brief Consolidated CPO tests for edge map containers (voem, moem) + * @brief Consolidated CPO tests for edge map containers (vom, mom, voum) * - * Edge map containers use std::map for edge storage (keyed by target_id): - * - Edges are sorted by target_id + * Edge map containers use std::map or std::unordered_map for edge storage (keyed by target_id): * - Edges are DEDUPLICATED (only one edge per target vertex - no parallel edges) - * - voem: vector vertices (resize_vertices), map edges - * - moem: map vertices (sparse, on-demand), map edges + * - vom: vector vertices (resize_vertices), map edges (sorted by target_id) + * - mom: map vertices (sparse, on-demand), map edges (sorted by target_id) + * - voum: vector vertices (resize_vertices), unordered_map edges (hash-based, unordered) * * Tests are adapted to handle both vertex container semantics. */ @@ -26,7 +26,11 @@ using namespace graph::test; // Helper to check if a tag uses map-based vertices (sparse) template -constexpr bool is_map_based_v = std::is_same_v; +constexpr bool is_map_based_v = std::is_same_v; + +// Helper to check if a tag uses unordered edge containers (no sorted order guarantee) +template +constexpr bool is_unordered_edges_v = std::is_same_v; // Helper type for edges using edge_void = copyable_edge_t; @@ -36,7 +40,7 @@ using edge_int = copyable_edge_t; // 1. vertices(g) CPO Tests //================================================================================================== -TEMPLATE_TEST_CASE("edge_map CPO vertices(g)", "[dynamic_graph][cpo][vertices][edge_map]", voem_tag, moem_tag) { +TEMPLATE_TEST_CASE("edge_map CPO vertices(g)", "[dynamic_graph][cpo][vertices][edge_map]", vom_tag, mom_tag, voum_tag) { using Types = graph_test_types; using Graph_void = typename Types::void_type; using Graph_int_ev = typename Types::int_ev; @@ -94,7 +98,7 @@ TEMPLATE_TEST_CASE("edge_map CPO vertices(g)", "[dynamic_graph][cpo][vertices][e // 2. num_vertices(g) CPO Tests //================================================================================================== -TEMPLATE_TEST_CASE("edge_map CPO num_vertices(g)", "[dynamic_graph][cpo][num_vertices][edge_map]", voem_tag, moem_tag) { +TEMPLATE_TEST_CASE("edge_map CPO num_vertices(g)", "[dynamic_graph][cpo][num_vertices][edge_map]", vom_tag, mom_tag, voum_tag) { using Types = graph_test_types; using Graph_void = typename Types::void_type; using Graph_int_ev = typename Types::int_ev; @@ -164,8 +168,9 @@ TEMPLATE_TEST_CASE("edge_map CPO num_vertices(g)", "[dynamic_graph][cpo][num_ver TEMPLATE_TEST_CASE("edge_map CPO find_vertex(g, uid)", "[dynamic_graph][cpo][find_vertex][edge_map]", - voem_tag, - moem_tag) { + vom_tag, + mom_tag, + voum_tag) { using Types = graph_test_types; using Graph_void = typename Types::void_type; using Graph_int_ev = typename Types::int_ev; @@ -243,7 +248,7 @@ TEMPLATE_TEST_CASE("edge_map CPO find_vertex(g, uid)", // 4. vertex_id(g, u) CPO Tests //================================================================================================== -TEMPLATE_TEST_CASE("edge_map CPO vertex_id(g, u)", "[dynamic_graph][cpo][vertex_id][edge_map]", voem_tag, moem_tag) { +TEMPLATE_TEST_CASE("edge_map CPO vertex_id(g, u)", "[dynamic_graph][cpo][vertex_id][edge_map]", vom_tag, mom_tag, voum_tag) { using Types = graph_test_types; using Graph_void = typename Types::void_type; using Graph_int_ev = typename Types::int_ev; @@ -321,7 +326,7 @@ TEMPLATE_TEST_CASE("edge_map CPO vertex_id(g, u)", "[dynamic_graph][cpo][vertex_ // 5. num_edges(g) CPO Tests //================================================================================================== -TEMPLATE_TEST_CASE("edge_map CPO num_edges(g)", "[dynamic_graph][cpo][num_edges][edge_map]", voem_tag, moem_tag) { +TEMPLATE_TEST_CASE("edge_map CPO num_edges(g)", "[dynamic_graph][cpo][num_edges][edge_map]", vom_tag, mom_tag, voum_tag) { using Types = graph_test_types; using Graph_void = typename Types::void_type; using Graph_int_ev = typename Types::int_ev; @@ -395,7 +400,7 @@ TEMPLATE_TEST_CASE("edge_map CPO num_edges(g)", "[dynamic_graph][cpo][num_edges] // 6. has_edge(g) CPO Tests //================================================================================================== -TEMPLATE_TEST_CASE("edge_map CPO has_edge(g)", "[dynamic_graph][cpo][has_edge][edge_map]", voem_tag, moem_tag) { +TEMPLATE_TEST_CASE("edge_map CPO has_edge(g)", "[dynamic_graph][cpo][has_edge][edge_map]", vom_tag, mom_tag, voum_tag) { using Types = graph_test_types; using Graph_void = typename Types::void_type; using Graph_int_ev = typename Types::int_ev; @@ -450,7 +455,7 @@ TEMPLATE_TEST_CASE("edge_map CPO has_edge(g)", "[dynamic_graph][cpo][has_edge][e // 7. edges(g, u) CPO Tests //================================================================================================== -TEMPLATE_TEST_CASE("edge_map CPO edges(g, u)", "[dynamic_graph][cpo][edges][edge_map]", voem_tag, moem_tag) { +TEMPLATE_TEST_CASE("edge_map CPO edges(g, u)", "[dynamic_graph][cpo][edges][edge_map]", vom_tag, mom_tag, voum_tag) { using Types = graph_test_types; using Graph_void = typename Types::void_type; using Graph_int_ev = typename Types::int_ev; @@ -473,8 +478,10 @@ TEMPLATE_TEST_CASE("edge_map CPO edges(g, u)", "[dynamic_graph][cpo][edges][edge targets.push_back(target_id(g, uv)); } - // Edges are sorted by target_id in multiset REQUIRE(targets.size() == 2); + if constexpr (is_unordered_edges_v) { + std::ranges::sort(targets); + } REQUIRE(targets[0] == 1); REQUIRE(targets[1] == 2); } @@ -565,7 +572,7 @@ TEMPLATE_TEST_CASE("edge_map CPO edges(g, u)", "[dynamic_graph][cpo][edges][edge // 8. degree(g, u) CPO Tests //================================================================================================== -TEMPLATE_TEST_CASE("edge_map CPO degree(g, u)", "[dynamic_graph][cpo][degree][edge_map]", voem_tag, moem_tag) { +TEMPLATE_TEST_CASE("edge_map CPO degree(g, u)", "[dynamic_graph][cpo][degree][edge_map]", vom_tag, mom_tag, voum_tag) { using Types = graph_test_types; using Graph_void = typename Types::void_type; using Graph_int_ev = typename Types::int_ev; @@ -647,7 +654,7 @@ TEMPLATE_TEST_CASE("edge_map CPO degree(g, u)", "[dynamic_graph][cpo][degree][ed // 9. target_id(g, uv) CPO Tests //================================================================================================== -TEMPLATE_TEST_CASE("edge_map CPO target_id(g, uv)", "[dynamic_graph][cpo][target_id][edge_map]", voem_tag, moem_tag) { +TEMPLATE_TEST_CASE("edge_map CPO target_id(g, uv)", "[dynamic_graph][cpo][target_id][edge_map]", vom_tag, mom_tag, voum_tag) { using Types = graph_test_types; using Graph_void = typename Types::void_type; using Graph_int_ev = typename Types::int_ev; @@ -669,8 +676,10 @@ TEMPLATE_TEST_CASE("edge_map CPO target_id(g, uv)", "[dynamic_graph][cpo][target targets.push_back(target_id(g, uv)); } - // Map keeps edges sorted by target REQUIRE(targets.size() == 2); + if constexpr (is_unordered_edges_v) { + std::ranges::sort(targets); + } REQUIRE(targets[0] == 1); REQUIRE(targets[1] == 2); } @@ -734,7 +743,7 @@ TEMPLATE_TEST_CASE("edge_map CPO target_id(g, uv)", "[dynamic_graph][cpo][target // 10. target(g, uv) CPO Tests //================================================================================================== -TEMPLATE_TEST_CASE("edge_map CPO target(g, uv)", "[dynamic_graph][cpo][target][edge_map]", voem_tag, moem_tag) { +TEMPLATE_TEST_CASE("edge_map CPO target(g, uv)", "[dynamic_graph][cpo][target][edge_map]", vom_tag, mom_tag, voum_tag) { using Types = graph_test_types; using Graph_void = typename Types::void_type; using Graph_int_ev = typename Types::int_ev; @@ -816,8 +825,9 @@ TEMPLATE_TEST_CASE("edge_map CPO target(g, uv)", "[dynamic_graph][cpo][target][e TEMPLATE_TEST_CASE("edge_map CPO find_vertex_edge(g, uid, vid)", "[dynamic_graph][cpo][find_vertex_edge][edge_map]", - voem_tag, - moem_tag) { + vom_tag, + mom_tag, + voum_tag) { using Types = graph_test_types; using Graph_void = typename Types::void_type; using Graph_int_ev = typename Types::int_ev; @@ -886,8 +896,9 @@ TEMPLATE_TEST_CASE("edge_map CPO find_vertex_edge(g, uid, vid)", TEMPLATE_TEST_CASE("edge_map CPO contains_edge(g, uid, vid)", "[dynamic_graph][cpo][contains_edge][edge_map]", - voem_tag, - moem_tag) { + vom_tag, + mom_tag, + voum_tag) { using Types = graph_test_types; using Graph_void = typename Types::void_type; using Graph_int_ev = typename Types::int_ev; @@ -972,8 +983,9 @@ TEMPLATE_TEST_CASE("edge_map CPO contains_edge(g, uid, vid)", TEMPLATE_TEST_CASE("edge_map CPO vertex_value(g, u)", "[dynamic_graph][cpo][vertex_value][edge_map]", - voem_tag, - moem_tag) { + vom_tag, + mom_tag, + voum_tag) { using Types = graph_test_types; using Graph_int_vv = typename Types::int_vv; @@ -1029,7 +1041,7 @@ TEMPLATE_TEST_CASE("edge_map CPO vertex_value(g, u)", // 14. edge_value(g, uv) CPO Tests //================================================================================================== -TEMPLATE_TEST_CASE("edge_map CPO edge_value(g, uv)", "[dynamic_graph][cpo][edge_value][edge_map]", voem_tag, moem_tag) { +TEMPLATE_TEST_CASE("edge_map CPO edge_value(g, uv)", "[dynamic_graph][cpo][edge_value][edge_map]", vom_tag, mom_tag, voum_tag) { using Types = graph_test_types; using Graph_int_ev = typename Types::int_ev; @@ -1099,7 +1111,7 @@ TEMPLATE_TEST_CASE("edge_map CPO edge_value(g, uv)", "[dynamic_graph][cpo][edge_ // 15. graph_value(g) CPO Tests //================================================================================================== -TEMPLATE_TEST_CASE("edge_map CPO graph_value(g)", "[dynamic_graph][cpo][graph_value][edge_map]", voem_tag, moem_tag) { +TEMPLATE_TEST_CASE("edge_map CPO graph_value(g)", "[dynamic_graph][cpo][graph_value][edge_map]", vom_tag, mom_tag, voum_tag) { using Types = graph_test_types; using Graph_all_int = typename Types::all_int; @@ -1151,7 +1163,7 @@ TEMPLATE_TEST_CASE("edge_map CPO graph_value(g)", "[dynamic_graph][cpo][graph_va // 16. source_id(g, uv) CPO Tests (Sourced=true) //================================================================================================== -TEMPLATE_TEST_CASE("edge_map CPO source_id(g, uv)", "[dynamic_graph][cpo][source_id][edge_map]", voem_tag, moem_tag) { +TEMPLATE_TEST_CASE("edge_map CPO source_id(g, uv)", "[dynamic_graph][cpo][source_id][edge_map]", vom_tag, mom_tag, voum_tag) { using Types = graph_test_types; using Graph_sourced = typename Types::sourced_void; using Graph_sourced_int = typename Types::sourced_int; @@ -1228,7 +1240,7 @@ TEMPLATE_TEST_CASE("edge_map CPO source_id(g, uv)", "[dynamic_graph][cpo][source // 17. source(g, uv) CPO Tests (Sourced=true) //================================================================================================== -TEMPLATE_TEST_CASE("edge_map CPO source(g, uv)", "[dynamic_graph][cpo][source][edge_map]", voem_tag, moem_tag) { +TEMPLATE_TEST_CASE("edge_map CPO source(g, uv)", "[dynamic_graph][cpo][source][edge_map]", vom_tag, mom_tag, voum_tag) { using Types = graph_test_types; using Graph_sourced = typename Types::sourced_void; using Graph_sourced_int = typename Types::sourced_int; @@ -1310,8 +1322,9 @@ TEMPLATE_TEST_CASE("edge_map CPO source(g, uv)", "[dynamic_graph][cpo][source][e TEMPLATE_TEST_CASE("edge_map CPO partition_id(g, u)", "[dynamic_graph][cpo][partition_id][edge_map]", - voem_tag, - moem_tag) { + vom_tag, + mom_tag, + voum_tag) { using Types = graph_test_types; using Graph_void = typename Types::void_type; @@ -1353,8 +1366,9 @@ TEMPLATE_TEST_CASE("edge_map CPO partition_id(g, u)", TEMPLATE_TEST_CASE("edge_map CPO num_partitions(g)", "[dynamic_graph][cpo][num_partitions][edge_map]", - voem_tag, - moem_tag) { + vom_tag, + mom_tag, + voum_tag) { using Types = graph_test_types; using Graph_void = typename Types::void_type; @@ -1397,8 +1411,9 @@ TEMPLATE_TEST_CASE("edge_map CPO num_partitions(g)", TEMPLATE_TEST_CASE("edge_map CPO vertices(g, pid)", "[dynamic_graph][cpo][vertices][partition][edge_map]", - voem_tag, - moem_tag) { + vom_tag, + mom_tag, + voum_tag) { using Types = graph_test_types; using Graph_void = typename Types::void_type; @@ -1454,8 +1469,9 @@ TEMPLATE_TEST_CASE("edge_map CPO vertices(g, pid)", TEMPLATE_TEST_CASE("edge_map CPO num_vertices(g, pid)", "[dynamic_graph][cpo][num_vertices][partition][edge_map]", - voem_tag, - moem_tag) { + vom_tag, + mom_tag, + voum_tag) { using Types = graph_test_types; using Graph_void = typename Types::void_type; @@ -1511,8 +1527,9 @@ TEMPLATE_TEST_CASE("edge_map CPO num_vertices(g, pid)", TEMPLATE_TEST_CASE("edge_map CPO integration: duplicate edges", "[dynamic_graph][cpo][integration][edge_map]", - voem_tag, - moem_tag) { + vom_tag, + mom_tag, + voum_tag) { using Types = graph_test_types; using Graph_int_ev = typename Types::int_ev; @@ -1569,8 +1586,9 @@ TEMPLATE_TEST_CASE("edge_map CPO integration: duplicate edges", TEMPLATE_TEST_CASE("edge_map CPO integration: values", "[dynamic_graph][cpo][integration][edge_map]", - voem_tag, - moem_tag) { + vom_tag, + mom_tag, + voum_tag) { using Types = graph_test_types; using Graph_all_int = typename Types::all_int; @@ -1612,8 +1630,9 @@ TEMPLATE_TEST_CASE("edge_map CPO integration: values", TEMPLATE_TEST_CASE("edge_map CPO integration: traversal", "[dynamic_graph][cpo][integration][edge_map]", - voem_tag, - moem_tag) { + vom_tag, + mom_tag, + voum_tag) { using Types = graph_test_types; using Graph_void = typename Types::void_type; @@ -1654,8 +1673,11 @@ TEMPLATE_TEST_CASE("edge_map CPO integration: traversal", targets.push_back(target_id(g, uv)); } - // Map sorts by target key + // Map sorts by target key; unordered_map does not, so sort for comparison REQUIRE(targets.size() == 3); + if constexpr (is_unordered_edges_v) { + std::ranges::sort(targets); + } REQUIRE(targets[0] == 1); REQUIRE(targets[1] == 2); REQUIRE(targets[2] == 3); diff --git a/tests/container/dynamic_graph/test_dynamic_graph_moem.cpp b/tests/container/dynamic_graph/test_dynamic_graph_mom.cpp similarity index 88% rename from tests/container/dynamic_graph/test_dynamic_graph_moem.cpp rename to tests/container/dynamic_graph/test_dynamic_graph_mom.cpp index 4c84a5d..13d2dc3 100644 --- a/tests/container/dynamic_graph/test_dynamic_graph_moem.cpp +++ b/tests/container/dynamic_graph/test_dynamic_graph_mom.cpp @@ -3,7 +3,7 @@ * @brief Tests for dynamic_graph with map vertices + set edges * * Phase 4.1.4: Map Vertex + Set Edge Containers - * Tests moem_graph_traits (map vertices + set edges) + * Tests mom_graph_traits (map vertices + set edges) * * Key characteristics: * - Vertices: std::map (associative; key-based lookup; bidirectional iteration) @@ -18,7 +18,7 @@ #include #include -#include +#include #include #include #include @@ -31,35 +31,35 @@ using namespace graph::container; // Type aliases for common test configurations with uint32_t vertex IDs using mos_void_void_void = - dynamic_graph>; + dynamic_graph>; using mos_int_void_void = - dynamic_graph>; + dynamic_graph>; using mos_void_int_void = - dynamic_graph>; + dynamic_graph>; using mos_int_int_void = - dynamic_graph>; + dynamic_graph>; using mos_void_void_int = - dynamic_graph>; + dynamic_graph>; using mos_int_int_int = - dynamic_graph>; + dynamic_graph>; // Type aliases with string vertex IDs (the primary use case for map containers) using mos_str_void_void_void = - dynamic_graph>; + dynamic_graph>; using mos_str_int_void_void = - dynamic_graph>; + dynamic_graph>; using mos_str_void_int_void = - dynamic_graph>; + dynamic_graph>; using mos_str_int_int_int = - dynamic_graph>; + dynamic_graph>; using mos_sourced = - dynamic_graph>; + dynamic_graph>; using mos_int_sourced = - dynamic_graph>; + dynamic_graph>; using mos_str_sourced = - dynamic_graph>; + dynamic_graph>; // Edge and vertex data types for loading using edge_void = copyable_edge_t; @@ -80,9 +80,9 @@ size_t count_all_edges(G& g) { // 1. Traits Verification Tests //================================================================================================== -TEST_CASE("moem traits verification", "[dynamic_graph][moem][traits]") { +TEST_CASE("mom traits verification", "[dynamic_graph][mom][traits]") { SECTION("vertices_type is std::map") { - using traits = moem_graph_traits; + using traits = mom_graph_traits; using vertices_t = typename traits::vertices_type; // Verify it's a map by checking for map-specific members static_assert(requires { typename vertices_t::key_type; }); @@ -91,7 +91,7 @@ TEST_CASE("moem traits verification", "[dynamic_graph][moem][traits]") { } SECTION("edges_type is std::set") { - using traits = moem_graph_traits; + using traits = mom_graph_traits; using edges_t = typename traits::edges_type; // set has key_type and doesn't have mapped_type (unlike map) static_assert(requires { typename edges_t::key_type; }); @@ -99,27 +99,27 @@ TEST_CASE("moem traits verification", "[dynamic_graph][moem][traits]") { } SECTION("vertex_id_type can be string") { - using traits = moem_graph_traits; + using traits = mom_graph_traits; static_assert(std::same_as); REQUIRE(true); } SECTION("sourced flag is preserved") { - using traits_unsourced = moem_graph_traits; - using traits_sourced = moem_graph_traits; + using traits_unsourced = mom_graph_traits; + using traits_sourced = mom_graph_traits; static_assert(traits_unsourced::sourced == false); static_assert(traits_sourced::sourced == true); REQUIRE(true); } SECTION("vertex_id_type for uint32_t") { - using traits = moem_graph_traits; + using traits = mom_graph_traits; static_assert(std::same_as); REQUIRE(true); } SECTION("custom vertex_id_type") { - using traits = moem_graph_traits; + using traits = mom_graph_traits; static_assert(std::same_as); REQUIRE(true); } @@ -129,7 +129,7 @@ TEST_CASE("moem traits verification", "[dynamic_graph][moem][traits]") { // 2. Iterator Category Tests //================================================================================================== -TEST_CASE("moem iterator categories", "[dynamic_graph][moem][iterators]") { +TEST_CASE("mom iterator categories", "[dynamic_graph][mom][iterators]") { SECTION("underlying map iterators are bidirectional") { using G = mos_void_void_void; using iter_t = typename G::vertices_type::iterator; @@ -139,7 +139,7 @@ TEST_CASE("moem iterator categories", "[dynamic_graph][moem][iterators]") { } SECTION("set edge iterators are bidirectional") { - using traits = moem_graph_traits; + using traits = mom_graph_traits; using edges_t = typename traits::edges_type; using edge_iter_t = typename edges_t::iterator; // set iterators are bidirectional @@ -161,7 +161,7 @@ TEST_CASE("moem iterator categories", "[dynamic_graph][moem][iterators]") { // 3. Construction Tests //================================================================================================== -TEST_CASE("moem construction", "[dynamic_graph][moem][construction]") { +TEST_CASE("mom construction", "[dynamic_graph][mom][construction]") { SECTION("default constructor creates empty graph") { mos_void_void_void g; REQUIRE(g.size() == 0); @@ -223,7 +223,7 @@ TEST_CASE("moem construction", "[dynamic_graph][moem][construction]") { } } -TEST_CASE("moem construction with string vertex IDs", "[dynamic_graph][moem][construction][string]") { +TEST_CASE("mom construction with string vertex IDs", "[dynamic_graph][mom][construction][string]") { SECTION("default constructor creates empty graph") { mos_str_void_void_void g; REQUIRE(g.size() == 0); @@ -245,7 +245,7 @@ TEST_CASE("moem construction with string vertex IDs", "[dynamic_graph][moem][con } } -TEST_CASE("moem construction sourced", "[dynamic_graph][moem][construction][sourced]") { +TEST_CASE("mom construction sourced", "[dynamic_graph][mom][construction][sourced]") { SECTION("sourced edge construction with uint32_t IDs") { mos_sourced g; REQUIRE(g.size() == 0); @@ -266,7 +266,7 @@ TEST_CASE("moem construction sourced", "[dynamic_graph][moem][construction][sour // 4. Basic Properties Tests //================================================================================================== -TEST_CASE("moem properties", "[dynamic_graph][moem][properties]") { +TEST_CASE("mom properties", "[dynamic_graph][mom][properties]") { SECTION("size() on empty graph") { mos_void_void_void g; REQUIRE(g.size() == 0); @@ -293,7 +293,7 @@ TEST_CASE("moem properties", "[dynamic_graph][moem][properties]") { } } -TEST_CASE("moem properties with string IDs", "[dynamic_graph][moem][properties][string]") { +TEST_CASE("mom properties with string IDs", "[dynamic_graph][mom][properties][string]") { SECTION("size() on empty graph") { mos_str_void_void_void g; REQUIRE(g.size() == 0); @@ -309,7 +309,7 @@ TEST_CASE("moem properties with string IDs", "[dynamic_graph][moem][properties][ // 5. Initializer List Construction Tests (uint32_t vertex IDs) //================================================================================================== -TEST_CASE("moem initializer_list construction", "[dynamic_graph][moem][initializer_list]") { +TEST_CASE("mom initializer_list construction", "[dynamic_graph][mom][initializer_list]") { SECTION("empty initializer list") { using G = mos_void_void_void; G g({}); @@ -371,7 +371,7 @@ TEST_CASE("moem initializer_list construction", "[dynamic_graph][moem][initializ // 6. Set-Specific Behavior: Deduplication Tests //================================================================================================== -TEST_CASE("moem edge deduplication", "[dynamic_graph][moem][set][deduplication]") { +TEST_CASE("mom edge deduplication", "[dynamic_graph][mom][set][deduplication]") { SECTION("duplicate edges are ignored - unsourced") { mos_void_void_void g; // Load edges with duplicates @@ -429,7 +429,7 @@ TEST_CASE("moem edge deduplication", "[dynamic_graph][moem][set][deduplication]" // 7. Set-Specific Behavior: Sorted Order Tests //================================================================================================== -TEST_CASE("moem edges are sorted by target_id", "[dynamic_graph][moem][set][sorted]") { +TEST_CASE("mom edges are sorted by target_id", "[dynamic_graph][mom][set][sorted]") { SECTION("unsourced edges sorted by target_id") { // Insert edges in unsorted order mos_void_void_void g; @@ -469,7 +469,7 @@ TEST_CASE("moem edges are sorted by target_id", "[dynamic_graph][moem][set][sort // 8. Initializer List Construction Tests (string vertex IDs) //================================================================================================== -TEST_CASE("moem initializer_list construction string IDs", "[dynamic_graph][moem][initializer_list][string]") { +TEST_CASE("mom initializer_list construction string IDs", "[dynamic_graph][mom][initializer_list][string]") { SECTION("single edge with string IDs") { using G = mos_str_void_void_void; G g({{"alice", "bob"}}); @@ -510,7 +510,7 @@ TEST_CASE("moem initializer_list construction string IDs", "[dynamic_graph][moem // 9. Graph Value Tests //================================================================================================== -TEST_CASE("moem graph value access", "[dynamic_graph][moem][graph_value]") { +TEST_CASE("mom graph value access", "[dynamic_graph][mom][graph_value]") { SECTION("graph_value() returns reference") { mos_void_void_int g(100); REQUIRE(g.graph_value() == 100); @@ -542,7 +542,7 @@ TEST_CASE("moem graph value access", "[dynamic_graph][moem][graph_value]") { // 10. Graph Iteration Tests //================================================================================================== -TEST_CASE("moem graph iteration", "[dynamic_graph][moem][iteration]") { +TEST_CASE("mom graph iteration", "[dynamic_graph][mom][iteration]") { SECTION("iterate over empty graph") { mos_void_void_void g; size_t count = 0; @@ -604,7 +604,7 @@ TEST_CASE("moem graph iteration", "[dynamic_graph][moem][iteration]") { // 11. Vertex Accessor Methods Tests //================================================================================================== -TEST_CASE("moem contains_vertex", "[dynamic_graph][moem][accessor][contains_vertex]") { +TEST_CASE("mom contains_vertex", "[dynamic_graph][mom][accessor][contains_vertex]") { SECTION("uint32_t vertex IDs") { using G = mos_void_void_void; G g({{0, 1}, {1, 2}, {5, 10}}); @@ -654,7 +654,7 @@ TEST_CASE("moem contains_vertex", "[dynamic_graph][moem][accessor][contains_vert } } -TEST_CASE("moem try_find_vertex", "[dynamic_graph][moem][accessor][try_find_vertex]") { +TEST_CASE("mom try_find_vertex", "[dynamic_graph][mom][accessor][try_find_vertex]") { SECTION("uint32_t vertex IDs - found") { using G = mos_void_void_void; G g({{0, 1}, {1, 2}, {5, 10}}); @@ -718,7 +718,7 @@ TEST_CASE("moem try_find_vertex", "[dynamic_graph][moem][accessor][try_find_vert } } -TEST_CASE("moem vertex_at", "[dynamic_graph][moem][accessor][vertex_at]") { +TEST_CASE("mom vertex_at", "[dynamic_graph][mom][accessor][vertex_at]") { SECTION("uint32_t vertex IDs - found") { using G = mos_void_void_void; G g({{0, 1}, {1, 2}}); @@ -778,7 +778,7 @@ TEST_CASE("moem vertex_at", "[dynamic_graph][moem][accessor][vertex_at]") { // 12. load_vertices Tests //================================================================================================== -TEST_CASE("moem load_vertices", "[dynamic_graph][moem][load_vertices]") { +TEST_CASE("mom load_vertices", "[dynamic_graph][mom][load_vertices]") { SECTION("uint32_t IDs - basic load") { using G = mos_void_int_void; using vertex_data = copyable_vertex_t; @@ -829,7 +829,7 @@ TEST_CASE("moem load_vertices", "[dynamic_graph][moem][load_vertices]") { // 13. load_edges Tests //================================================================================================== -TEST_CASE("moem load_edges explicit", "[dynamic_graph][moem][load_edges]") { +TEST_CASE("mom load_edges explicit", "[dynamic_graph][mom][load_edges]") { SECTION("uint32_t IDs - basic load") { using G = mos_int_void_void; using edge_data = copyable_edge_t; @@ -883,7 +883,7 @@ TEST_CASE("moem load_edges explicit", "[dynamic_graph][moem][load_edges]") { // 14. Edge Cases and Error Handling //================================================================================================== -TEST_CASE("moem edge cases", "[dynamic_graph][moem][edge_cases]") { +TEST_CASE("mom edge cases", "[dynamic_graph][mom][edge_cases]") { SECTION("graph with single vertex (self-loop)") { using G = mos_void_void_void; G g({{0, 0}}); @@ -944,7 +944,7 @@ TEST_CASE("moem edge cases", "[dynamic_graph][moem][edge_cases]") { // 15. Const Correctness Tests //================================================================================================== -TEST_CASE("moem const correctness", "[dynamic_graph][moem][const]") { +TEST_CASE("mom const correctness", "[dynamic_graph][mom][const]") { SECTION("const graph properties") { using G = mos_int_void_void; const G g({{0, 1, 10}, {1, 2, 20}}); @@ -969,7 +969,7 @@ TEST_CASE("moem const correctness", "[dynamic_graph][moem][const]") { // 16. Memory and Resource Management Tests //================================================================================================== -TEST_CASE("moem memory management", "[dynamic_graph][moem][memory]") { +TEST_CASE("mom memory management", "[dynamic_graph][mom][memory]") { SECTION("multiple independent graphs") { using G = mos_void_void_int; G g1(100, {{0, 1}}); @@ -1005,7 +1005,7 @@ TEST_CASE("moem memory management", "[dynamic_graph][moem][memory]") { // 17. Template Instantiation Tests //================================================================================================== -TEST_CASE("moem template instantiation", "[dynamic_graph][moem][compilation]") { +TEST_CASE("mom template instantiation", "[dynamic_graph][mom][compilation]") { [[maybe_unused]] mos_void_void_void g1; [[maybe_unused]] mos_int_void_void g2; [[maybe_unused]] mos_void_int_void g3; @@ -1026,7 +1026,7 @@ TEST_CASE("moem template instantiation", "[dynamic_graph][moem][compilation]") { // 18. Sparse Vertex Behavior Tests //================================================================================================== -TEST_CASE("moem sparse vertex behavior", "[dynamic_graph][moem][sparse]") { +TEST_CASE("mom sparse vertex behavior", "[dynamic_graph][mom][sparse]") { SECTION("only referenced vertices are created") { using G = mos_void_void_void; @@ -1053,7 +1053,7 @@ TEST_CASE("moem sparse vertex behavior", "[dynamic_graph][moem][sparse]") { // 19. Edge Bidirectional Iteration Tests //================================================================================================== -TEST_CASE("moem edge bidirectional iteration", "[dynamic_graph][moem][set][iteration]") { +TEST_CASE("mom edge bidirectional iteration", "[dynamic_graph][mom][set][iteration]") { SECTION("forward iteration") { mos_void_void_void g({{0, 1}, {0, 2}, {0, 3}}); @@ -1090,7 +1090,7 @@ TEST_CASE("moem edge bidirectional iteration", "[dynamic_graph][moem][set][itera // 20. Sourced Edge Tests //================================================================================================== -TEST_CASE("moem sourced edges", "[dynamic_graph][moem][sourced]") { +TEST_CASE("mom sourced edges", "[dynamic_graph][mom][sourced]") { SECTION("source_id access") { mos_sourced g({{0, 1}, {0, 2}, {1, 0}}); diff --git a/tests/container/dynamic_graph/test_dynamic_graph_voem.cpp b/tests/container/dynamic_graph/test_dynamic_graph_vom.cpp similarity index 88% rename from tests/container/dynamic_graph/test_dynamic_graph_voem.cpp rename to tests/container/dynamic_graph/test_dynamic_graph_vom.cpp index b96ee09..45c4371 100644 --- a/tests/container/dynamic_graph/test_dynamic_graph_voem.cpp +++ b/tests/container/dynamic_graph/test_dynamic_graph_vom.cpp @@ -3,7 +3,7 @@ * @brief Comprehensive tests for dynamic_graph with vector vertices + set edges * * Phase 4.1.2: Set Edge Container Support - * Tests voem_graph_traits (vector vertices + set edges) + * Tests vom_graph_traits (vector vertices + set edges) * * Key characteristics of std::set edges: * - Automatic deduplication (no parallel edges with same endpoints) @@ -15,7 +15,7 @@ #include #include -#include +#include #include #include #include @@ -28,17 +28,17 @@ using namespace graph::container; // Type aliases for common test configurations using vos_void_void_void = - dynamic_graph>; + dynamic_graph>; using vos_int_void_void = - dynamic_graph>; + dynamic_graph>; using vos_void_int_void = - dynamic_graph>; + dynamic_graph>; using vos_int_int_void = - dynamic_graph>; + dynamic_graph>; using vos_void_void_int = - dynamic_graph>; + dynamic_graph>; using vos_int_int_int = - dynamic_graph>; + dynamic_graph>; using vos_string_string_string = dynamic_graph>; + vom_graph_traits>; using vos_sourced = - dynamic_graph>; + dynamic_graph>; using vos_int_sourced = - dynamic_graph>; + dynamic_graph>; // Edge and vertex data types for loading using edge_void = copyable_edge_t; @@ -72,7 +72,7 @@ size_t count_all_edges(G& g) { // 1. Construction Tests //================================================================================================== -TEST_CASE("voem default construction", "[voem][construction]") { +TEST_CASE("vom default construction", "[vom][construction]") { SECTION("creates empty graph") { vos_void_void_void g; REQUIRE(g.size() == 0); @@ -109,7 +109,7 @@ TEST_CASE("voem default construction", "[voem][construction]") { } } -TEST_CASE("voem constructor with graph value", "[voem][construction]") { +TEST_CASE("vom constructor with graph value", "[vom][construction]") { SECTION("void GV - no graph value can be passed") { vos_void_void_void g; REQUIRE(g.size() == 0); @@ -126,7 +126,7 @@ TEST_CASE("voem constructor with graph value", "[voem][construction]") { // 2. Load Edges Tests //================================================================================================== -TEST_CASE("voem load_edges", "[voem][load]") { +TEST_CASE("vom load_edges", "[vom][load]") { SECTION("simple edges") { vos_void_void_void g; std::vector ee = {{0, 1}, {0, 2}, {1, 2}}; @@ -168,7 +168,7 @@ TEST_CASE("voem load_edges", "[voem][load]") { // 3. Initializer List Construction Tests //================================================================================================== -TEST_CASE("voem initializer list construction", "[voem][construction][initializer_list]") { +TEST_CASE("vom initializer list construction", "[vom][construction][initializer_list]") { SECTION("simple initializer list") { vos_void_void_void g({{0, 1}, {0, 2}, {1, 2}}); @@ -181,7 +181,7 @@ TEST_CASE("voem initializer list construction", "[voem][construction][initialize // 4. Set-Specific Behavior: Deduplication Tests //================================================================================================== -TEST_CASE("voem edge deduplication", "[voem][set][deduplication]") { +TEST_CASE("vom edge deduplication", "[vom][set][deduplication]") { SECTION("duplicate edges are ignored - unsourced") { vos_void_void_void g; // Load edges with duplicates @@ -238,7 +238,7 @@ TEST_CASE("voem edge deduplication", "[voem][set][deduplication]") { // 5. Set-Specific Behavior: Sorted Order Tests //================================================================================================== -TEST_CASE("voem edges are sorted by target_id", "[voem][set][sorted]") { +TEST_CASE("vom edges are sorted by target_id", "[vom][set][sorted]") { SECTION("unsourced edges sorted by target_id") { // Insert edges in unsorted order vos_void_void_void g; @@ -276,7 +276,7 @@ TEST_CASE("voem edges are sorted by target_id", "[voem][set][sorted]") { // 6. Vertex Access Tests //================================================================================================== -TEST_CASE("voem vertex access", "[voem][vertex][access]") { +TEST_CASE("vom vertex access", "[vom][vertex][access]") { SECTION("operator[] access") { vos_void_void_void g({{0, 1}, {1, 2}, {2, 3}}); @@ -305,7 +305,7 @@ TEST_CASE("voem vertex access", "[voem][vertex][access]") { } } -TEST_CASE("voem vertex iteration", "[voem][vertex][iteration]") { +TEST_CASE("vom vertex iteration", "[vom][vertex][iteration]") { SECTION("range-based for") { vos_void_void_void g({{0, 1}, {1, 2}, {2, 0}}); @@ -335,7 +335,7 @@ TEST_CASE("voem vertex iteration", "[voem][vertex][iteration]") { // 7. Edge Access Tests //================================================================================================== -TEST_CASE("voem edge access", "[voem][edge][access]") { +TEST_CASE("vom edge access", "[vom][edge][access]") { SECTION("edges() returns set") { vos_void_void_void g({{0, 1}, {0, 2}, {0, 3}}); @@ -364,7 +364,7 @@ TEST_CASE("voem edge access", "[voem][edge][access]") { } } -TEST_CASE("voem edge bidirectional iteration", "[voem][edge][iteration]") { +TEST_CASE("vom edge bidirectional iteration", "[vom][edge][iteration]") { SECTION("forward iteration") { vos_void_void_void g({{0, 1}, {0, 2}, {0, 3}}); @@ -397,7 +397,7 @@ TEST_CASE("voem edge bidirectional iteration", "[voem][edge][iteration]") { // 8. Vertex and Edge Value Tests //================================================================================================== -TEST_CASE("voem vertex values", "[voem][vertex][value]") { +TEST_CASE("vom vertex values", "[vom][vertex][value]") { SECTION("vertex value access") { vos_void_int_void g; std::vector vv = {{0, 100}, {1, 200}}; @@ -411,7 +411,7 @@ TEST_CASE("voem vertex values", "[voem][vertex][value]") { } } -TEST_CASE("voem edge values", "[voem][edge][value]") { +TEST_CASE("vom edge values", "[vom][edge][value]") { SECTION("edge values preserved after deduplication") { // First edge with value 100 should be kept vos_int_void_void g; @@ -430,7 +430,7 @@ TEST_CASE("voem edge values", "[voem][edge][value]") { // 9. Sourced Edge Tests //================================================================================================== -TEST_CASE("voem sourced edges", "[voem][sourced]") { +TEST_CASE("vom sourced edges", "[vom][sourced]") { SECTION("source_id access") { vos_sourced g({{0, 1}, {0, 2}, {1, 0}}); @@ -468,7 +468,7 @@ TEST_CASE("voem sourced edges", "[voem][sourced]") { // 10. Self-Loop Tests //================================================================================================== -TEST_CASE("voem self-loops", "[voem][self-loop]") { +TEST_CASE("vom self-loops", "[vom][self-loop]") { SECTION("single self-loop") { vos_void_void_void g({{0, 0}}); @@ -507,7 +507,7 @@ TEST_CASE("voem self-loops", "[voem][self-loop]") { // 11. Large Graph Tests //================================================================================================== -TEST_CASE("voem large graph", "[voem][performance]") { +TEST_CASE("vom large graph", "[vom][performance]") { SECTION("1000 vertices linear chain") { std::vector ee; for (uint32_t i = 0; i < 999; ++i) { @@ -543,7 +543,7 @@ TEST_CASE("voem large graph", "[voem][performance]") { // 12. Iterator Stability Tests (std::set guarantees) //================================================================================================== -TEST_CASE("voem set iterator stability", "[voem][set][iterator]") { +TEST_CASE("vom set iterator stability", "[vom][set][iterator]") { SECTION("edge iterators are bidirectional") { vos_void_void_void g({{0, 1}, {0, 2}, {0, 3}}); @@ -570,7 +570,7 @@ TEST_CASE("voem set iterator stability", "[voem][set][iterator]") { // 13. Algorithm Compatibility Tests //================================================================================================== -TEST_CASE("voem algorithm compatibility", "[voem][algorithm]") { +TEST_CASE("vom algorithm compatibility", "[vom][algorithm]") { SECTION("std::ranges::for_each on vertices") { vos_void_void_void g({{0, 1}, {1, 2}, {2, 0}}); @@ -613,7 +613,7 @@ TEST_CASE("voem algorithm compatibility", "[voem][algorithm]") { // 14. Edge Case Tests //================================================================================================== -TEST_CASE("voem edge cases", "[voem][edge-cases]") { +TEST_CASE("vom edge cases", "[vom][edge-cases]") { SECTION("empty graph operations") { vos_void_void_void g; @@ -657,9 +657,9 @@ TEST_CASE("voem edge cases", "[voem][edge-cases]") { // 15. Type Trait Tests //================================================================================================== -TEST_CASE("voem type traits", "[voem][traits]") { +TEST_CASE("vom type traits", "[vom][traits]") { SECTION("edge_type is correct") { - using traits = voem_graph_traits; + using traits = vom_graph_traits; using edge_t = traits::edge_type; static_assert(std::is_same_v); @@ -667,7 +667,7 @@ TEST_CASE("voem type traits", "[voem][traits]") { } SECTION("edges_type is std::set") { - using traits = voem_graph_traits; + using traits = vom_graph_traits; using edges_t = traits::edges_type; // Verify it's a set by checking it has set-specific types @@ -675,8 +675,8 @@ TEST_CASE("voem type traits", "[voem][traits]") { } SECTION("sourced trait") { - using traits_unsourced = voem_graph_traits; - using traits_sourced = voem_graph_traits; + using traits_unsourced = vom_graph_traits; + using traits_sourced = vom_graph_traits; static_assert(traits_unsourced::sourced == false); static_assert(traits_sourced::sourced == true); @@ -687,7 +687,7 @@ TEST_CASE("voem type traits", "[voem][traits]") { // 16. Complex Graph Structure Tests //================================================================================================== -TEST_CASE("voem complex structures", "[voem][complex]") { +TEST_CASE("vom complex structures", "[vom][complex]") { SECTION("complete graph K4") { std::vector ee; for (uint32_t i = 0; i < 4; ++i) { diff --git a/tests/container/dynamic_graph/test_dynamic_graph_voum.cpp b/tests/container/dynamic_graph/test_dynamic_graph_voum.cpp new file mode 100644 index 0000000..77488fe --- /dev/null +++ b/tests/container/dynamic_graph/test_dynamic_graph_voum.cpp @@ -0,0 +1,738 @@ +/** + * @file test_dynamic_graph_voum.cpp + * @brief Comprehensive tests for dynamic_graph with vector vertices + unordered_map edges + * + * Tests voum_graph_traits (vector vertices + unordered_map edges) + * + * Key characteristics of std::unordered_map edges: + * - Automatic deduplication (only one edge per target vertex) + * - Edges stored in unordered fashion (hash-bucket order, not sorted) + * - O(1) average edge insertion, lookup, and deletion + * - Forward iterators only (no bidirectional or random access) + * - Requires std::hash and operator== on VId + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace graph::container; + +// Type aliases for common test configurations +using voum_void_void_void = + dynamic_graph>; +using voum_int_void_void = + dynamic_graph>; +using voum_void_int_void = + dynamic_graph>; +using voum_int_int_void = + dynamic_graph>; +using voum_void_void_int = + dynamic_graph>; +using voum_int_int_int = + dynamic_graph>; + +using voum_string_string_string = + dynamic_graph>; + +using voum_sourced = + dynamic_graph>; +using voum_int_sourced = + dynamic_graph>; + +// Edge and vertex data types for loading +using edge_void = copyable_edge_t; +using edge_int = copyable_edge_t; +using vertex_int = copyable_vertex_t; + +// Helper function to count total edges in graph +template +size_t count_all_edges(G& g) { + size_t count = 0; + for (auto& v : g) { + count += static_cast(std::ranges::distance(v.edges())); + } + return count; +} + +//================================================================================================== +// 1. Construction Tests +//================================================================================================== + +TEST_CASE("voum default construction", "[voum][construction]") { + SECTION("creates empty graph") { + voum_void_void_void g; + REQUIRE(g.size() == 0); + } + + SECTION("with void types") { + voum_void_void_void g; + REQUIRE(g.size() == 0); + } + + SECTION("with int edge values") { + voum_int_void_void g; + REQUIRE(g.size() == 0); + } + + SECTION("with int vertex values") { + voum_void_int_void g; + REQUIRE(g.size() == 0); + } + + SECTION("with int graph value") { + voum_void_void_int g; + REQUIRE(g.size() == 0); + } + + SECTION("with all int values") { + voum_int_int_int g; + REQUIRE(g.size() == 0); + } + + SECTION("with string values") { + voum_string_string_string g; + REQUIRE(g.size() == 0); + } +} + +TEST_CASE("voum constructor with graph value", "[voum][construction]") { + SECTION("void GV - no graph value can be passed") { + voum_void_void_void g; + REQUIRE(g.size() == 0); + } + + SECTION("int GV") { + voum_void_void_int g(42); + REQUIRE(g.size() == 0); + REQUIRE(g.graph_value() == 42); + } +} + +//================================================================================================== +// 2. Load Edges Tests +//================================================================================================== + +TEST_CASE("voum load_edges", "[voum][load]") { + SECTION("simple edges") { + voum_void_void_void g; + std::vector ee = {{0, 1}, {0, 2}, {1, 2}}; + g.load_edges(ee, std::identity{}); + + REQUIRE(g.size() == 3); + REQUIRE(count_all_edges(g) == 3); + } + + SECTION("edges with vertex count") { + voum_void_void_void g; + std::vector ee = {{0, 1}, {1, 2}}; + g.load_edges(ee, std::identity{}, 6); // Request 6 vertices + + REQUIRE(g.size() == 6); // 0 through 5 + REQUIRE(count_all_edges(g) == 2); + } + + SECTION("edges with values") { + voum_int_void_void g; + std::vector ee = {{0, 1, 100}, {0, 2, 200}}; + g.load_edges(ee, std::identity{}); + + REQUIRE(g.size() == 3); + REQUIRE(count_all_edges(g) == 2); + + // Collect edge values (order is unspecified for unordered_map) + auto& v0 = g[0]; + std::unordered_map edge_vals; + for (const auto& edge : v0.edges()) { + edge_vals[edge.second.target_id()] = edge.second.value(); + } + REQUIRE(edge_vals[1] == 100); + REQUIRE(edge_vals[2] == 200); + } +} + +//================================================================================================== +// 3. Initializer List Construction Tests +//================================================================================================== + +TEST_CASE("voum initializer list construction", "[voum][construction][initializer_list]") { + SECTION("simple initializer list") { + voum_void_void_void g({{0, 1}, {0, 2}, {1, 2}}); + + REQUIRE(g.size() == 3); + REQUIRE(count_all_edges(g) == 3); + } +} + +//================================================================================================== +// 4. Deduplication Tests (unordered_map guarantees unique keys) +//================================================================================================== + +TEST_CASE("voum edge deduplication", "[voum][unordered_map][deduplication]") { + SECTION("duplicate edges are ignored - unsourced") { + voum_void_void_void g; + // Load edges with duplicates + std::vector ee = { + {0, 1}, {0, 1}, {0, 1}, // Three identical edges + {0, 2}, {0, 2}, // Two identical edges + {1, 2} // One unique edge + }; + g.load_edges(ee, std::identity{}); + + REQUIRE(g.size() == 3); + // Deduplication: only 3 unique edges should exist + REQUIRE(count_all_edges(g) == 3); + + // Verify each vertex has correct number of edges + auto& v0 = g[0]; + auto& v1 = g[1]; + REQUIRE(std::distance(v0.edges().begin(), v0.edges().end()) == 2); // 0->1, 0->2 + REQUIRE(std::distance(v1.edges().begin(), v1.edges().end()) == 1); // 1->2 + } + + SECTION("duplicate edges with different values - first value wins") { + voum_int_void_void g; + std::vector ee = { + {0, 1, 100}, {0, 1, 200}, {0, 1, 300} // Same edge, different values + }; + g.load_edges(ee, std::identity{}); + + REQUIRE(g.size() == 2); + REQUIRE(count_all_edges(g) == 1); // Only one edge stored + + // First inserted value should be kept + auto& v0 = g[0]; + REQUIRE(v0.edges().begin()->second.value() == 100); + } + + SECTION("sourced edges - deduplication by (source_id, target_id)") { + voum_sourced g; + std::vector ee = { + {0, 1}, + {0, 1}, // Duplicates + {1, 0}, + {1, 0} // Different direction, also duplicates + }; + g.load_edges(ee, std::identity{}); + + // Should have exactly 2 unique edges (0->1 and 1->0) + REQUIRE(count_all_edges(g) == 2); + } +} + +//================================================================================================== +// 5. Unordered Behavior Tests +//================================================================================================== + +TEST_CASE("voum edges are unordered", "[voum][unordered_map][unordered]") { + SECTION("all target_ids are present regardless of order") { + voum_void_void_void g; + std::vector ee = {{0, 5}, {0, 2}, {0, 8}, {0, 1}, {0, 3}}; + g.load_edges(ee, std::identity{}); + + // Collect all target_ids (order is unspecified) + auto& v0 = g[0]; + std::set target_ids; + for (const auto& edge : v0.edges()) { + target_ids.insert(edge.second.target_id()); + } + + REQUIRE(target_ids == std::set{1, 2, 3, 5, 8}); + } + + SECTION("sourced edges - all target_ids present") { + voum_sourced g; + std::vector ee = {{0, 7}, {0, 3}, {0, 9}, {0, 1}}; + g.load_edges(ee, std::identity{}); + + auto& v0 = g[0]; + std::set target_ids; + for (const auto& edge : v0.edges()) { + target_ids.insert(edge.second.target_id()); + } + + REQUIRE(target_ids == std::set{1, 3, 7, 9}); + } +} + +//================================================================================================== +// 6. Vertex Access Tests +//================================================================================================== + +TEST_CASE("voum vertex access", "[voum][vertex][access]") { + SECTION("operator[] access") { + voum_void_void_void g({{0, 1}, {1, 2}, {2, 3}}); + + REQUIRE(g.size() == 4); + // Access each vertex + auto& v0 = g[0]; + auto& v1 = g[1]; + auto& v2 = g[2]; + auto& v3 = g[3]; + + // Verify edge counts + REQUIRE(std::distance(v0.edges().begin(), v0.edges().end()) == 1); + REQUIRE(std::distance(v1.edges().begin(), v1.edges().end()) == 1); + REQUIRE(std::distance(v2.edges().begin(), v2.edges().end()) == 1); + REQUIRE(std::distance(v3.edges().begin(), v3.edges().end()) == 0); + } + + SECTION("const operator[] access") { + const voum_void_void_void g({{0, 1}, {1, 2}}); + + const auto& v0 = g[0]; + const auto& v1 = g[1]; + + REQUIRE(std::distance(v0.edges().begin(), v0.edges().end()) == 1); + REQUIRE(std::distance(v1.edges().begin(), v1.edges().end()) == 1); + } +} + +TEST_CASE("voum vertex iteration", "[voum][vertex][iteration]") { + SECTION("range-based for") { + voum_void_void_void g({{0, 1}, {1, 2}, {2, 0}}); + + size_t count = 0; + for (const auto& vertex : g) { + (void)vertex; + ++count; + } + REQUIRE(count == 3); + } + + SECTION("begin/end iteration") { + voum_void_void_void g({{0, 1}, {1, 2}}); + + auto it = g.begin(); + REQUIRE(it != g.end()); + ++it; + REQUIRE(it != g.end()); + ++it; + REQUIRE(it != g.end()); + ++it; + REQUIRE(it == g.end()); + } +} + +//================================================================================================== +// 7. Edge Access Tests +//================================================================================================== + +TEST_CASE("voum edge access", "[voum][edge][access]") { + SECTION("edges() returns unordered_map") { + voum_void_void_void g({{0, 1}, {0, 2}, {0, 3}}); + + auto& v0 = g[0]; + auto& edge_map = v0.edges(); + + REQUIRE(std::distance(edge_map.begin(), edge_map.end()) == 3); + } + + SECTION("edge target_id access") { + voum_void_void_void g({{0, 5}}); + + auto& v0 = g[0]; + auto it = v0.edges().begin(); + REQUIRE(it->second.target_id() == 5); + } + + SECTION("edge value access") { + voum_int_void_void g; + std::vector ee = {{0, 1, 42}}; + g.load_edges(ee, std::identity{}); + + auto& v0 = g[0]; + auto it = v0.edges().begin(); + REQUIRE(it->second.value() == 42); + } +} + +TEST_CASE("voum edge forward iteration", "[voum][edge][iteration]") { + SECTION("forward iteration covers all edges") { + voum_void_void_void g({{0, 1}, {0, 2}, {0, 3}}); + + auto& v0 = g[0]; + std::set targets; + for (const auto& edge : v0.edges()) { + targets.insert(edge.second.target_id()); + } + + REQUIRE(targets.size() == 3); + REQUIRE(targets == std::set{1, 2, 3}); + } + + // Note: No reverse iteration test — unordered_map only provides forward iterators +} + +//================================================================================================== +// 8. Vertex and Edge Value Tests +//================================================================================================== + +TEST_CASE("voum vertex values", "[voum][vertex][value]") { + SECTION("vertex value access") { + voum_void_int_void g; + std::vector vv = {{0, 100}, {1, 200}}; + g.load_vertices(vv, std::identity{}); + + std::vector ee = {{0, 1}}; + g.load_edges(ee, std::identity{}); + + REQUIRE(g[0].value() == 100); + REQUIRE(g[1].value() == 200); + } +} + +TEST_CASE("voum edge values", "[voum][edge][value]") { + SECTION("edge values preserved after deduplication") { + voum_int_void_void g; + std::vector ee = {{0, 1, 100}, {0, 2, 200}}; + g.load_edges(ee, std::identity{}); + + auto& v0 = g[0]; + std::unordered_map edge_vals; + for (const auto& edge : v0.edges()) { + edge_vals[edge.second.target_id()] = edge.second.value(); + } + REQUIRE(edge_vals[1] == 100); + REQUIRE(edge_vals[2] == 200); + } +} + +//================================================================================================== +// 9. Sourced Edge Tests +//================================================================================================== + +TEST_CASE("voum sourced edges", "[voum][sourced]") { + SECTION("source_id access") { + voum_sourced g({{0, 1}, {0, 2}, {1, 0}}); + + auto& v0 = g[0]; + for (const auto& edge : v0.edges()) { + REQUIRE(edge.second.source_id() == 0); + } + + auto& v1 = g[1]; + for (const auto& edge : v1.edges()) { + REQUIRE(edge.second.source_id() == 1); + } + } + + SECTION("sourced edge with values") { + voum_int_sourced g; + std::vector ee = {{0, 1, 100}, {1, 0, 200}}; + g.load_edges(ee, std::identity{}); + + // Verify edges from vertex 0 + auto& v0 = g[0]; + auto it0 = v0.edges().begin(); + REQUIRE(it0->second.source_id() == 0); + REQUIRE(it0->second.target_id() == 1); + REQUIRE(it0->second.value() == 100); + + // Verify edges from vertex 1 + auto& v1 = g[1]; + auto it1 = v1.edges().begin(); + REQUIRE(it1->second.source_id() == 1); + REQUIRE(it1->second.target_id() == 0); + REQUIRE(it1->second.value() == 200); + } +} + +//================================================================================================== +// 10. Self-Loop Tests +//================================================================================================== + +TEST_CASE("voum self-loops", "[voum][self-loop]") { + SECTION("single self-loop") { + voum_void_void_void g({{0, 0}}); + + REQUIRE(g.size() == 1); + REQUIRE(count_all_edges(g) == 1); + + auto& v0 = g[0]; + REQUIRE(std::distance(v0.edges().begin(), v0.edges().end()) == 1); + REQUIRE(v0.edges().begin()->second.target_id() == 0); + } + + SECTION("self-loop deduplication") { + voum_void_void_void g({{0, 0}, {0, 0}, {0, 0}}); + + // Only one self-loop should exist + REQUIRE(count_all_edges(g) == 1); + } + + SECTION("self-loop with outgoing edges") { + voum_void_void_void g({{0, 0}, {0, 1}, {0, 2}}); + + REQUIRE(count_all_edges(g) == 3); + + auto& v0 = g[0]; + std::set targets; + for (const auto& edge : v0.edges()) { + targets.insert(edge.second.target_id()); + } + + REQUIRE(targets == std::set{0, 1, 2}); + } +} + +//================================================================================================== +// 11. Large Graph Tests +//================================================================================================== + +TEST_CASE("voum large graph", "[voum][performance]") { + SECTION("1000 vertices linear chain") { + std::vector ee; + for (uint32_t i = 0; i < 999; ++i) { + ee.push_back({i, i + 1}); + } + + voum_void_void_void g; + g.load_edges(ee, std::identity{}); + + REQUIRE(g.size() == 1000); + REQUIRE(count_all_edges(g) == 999); + } + + SECTION("star graph with 100 spokes") { + std::vector ee; + for (uint32_t i = 1; i <= 100; ++i) { + ee.push_back({0, i}); + } + + voum_void_void_void g; + g.load_edges(ee, std::identity{}); + + REQUIRE(g.size() == 101); + REQUIRE(count_all_edges(g) == 100); + + // Vertex 0 should have all 100 edges + auto& v0 = g[0]; + REQUIRE(std::distance(v0.edges().begin(), v0.edges().end()) == 100); + } +} + +//================================================================================================== +// 12. Iterator Tests (forward only for unordered_map) +//================================================================================================== + +TEST_CASE("voum forward iterator behavior", "[voum][unordered_map][iterator]") { + SECTION("edge iterators are forward") { + voum_void_void_void g({{0, 1}, {0, 2}, {0, 3}}); + + auto& v0 = g[0]; + auto& edge_map = v0.edges(); + + // Forward iteration + std::set visited; + for (auto it = edge_map.begin(); it != edge_map.end(); ++it) { + visited.insert(it->second.target_id()); + } + REQUIRE(visited == std::set{1, 2, 3}); + } + + SECTION("find by key") { + voum_void_void_void g({{0, 1}, {0, 2}, {0, 3}}); + + auto& v0 = g[0]; + auto& edge_map = v0.edges(); + + // unordered_map supports O(1) average find + auto it = edge_map.find(2); + REQUIRE(it != edge_map.end()); + REQUIRE(it->second.target_id() == 2); + + auto it_miss = edge_map.find(99); + REQUIRE(it_miss == edge_map.end()); + } +} + +//================================================================================================== +// 13. Algorithm Compatibility Tests +//================================================================================================== + +TEST_CASE("voum algorithm compatibility", "[voum][algorithm]") { + SECTION("std::ranges::for_each on vertices") { + voum_void_void_void g({{0, 1}, {1, 2}, {2, 0}}); + + size_t count = 0; + std::ranges::for_each(g, [&count](const auto& v) { + (void)v; + ++count; + }); + + REQUIRE(count == 3); + } + + SECTION("std::ranges::for_each on edges") { + voum_void_void_void g({{0, 1}, {0, 2}, {0, 3}}); + + auto& v0 = g[0]; + size_t count = 0; + std::ranges::for_each(v0.edges(), [&count](const auto& e) { + (void)e; + ++count; + }); + + REQUIRE(count == 3); + } + + SECTION("std::find_if on edges") { + voum_int_void_void g; + std::vector ee = {{0, 1, 100}, {0, 2, 200}, {0, 3, 300}}; + g.load_edges(ee, std::identity{}); + + auto& v0 = g[0]; + auto it = std::ranges::find_if(v0.edges(), [](const auto& e) { return e.second.value() == 200; }); + + REQUIRE(it != v0.edges().end()); + REQUIRE(it->second.target_id() == 2); + } +} + +//================================================================================================== +// 14. Edge Case Tests +//================================================================================================== + +TEST_CASE("voum edge cases", "[voum][edge-cases]") { + SECTION("empty graph operations") { + voum_void_void_void g; + + REQUIRE(g.size() == 0); + REQUIRE(count_all_edges(g) == 0); + REQUIRE(g.begin() == g.end()); + } + + SECTION("single vertex no edges") { + voum_void_void_void g; + std::vector empty_edges; + g.load_edges(empty_edges, std::identity{}, 1); + + REQUIRE(g.size() == 1); + REQUIRE(count_all_edges(g) == 0); + + auto& v0 = g[0]; + REQUIRE(v0.edges().empty()); + } + + SECTION("vertices with no outgoing edges") { + voum_void_void_void g; + std::vector ee = {{0, 1}}; + g.load_edges(ee, std::identity{}, 6); + + REQUIRE(g.size() == 6); // 0 through 5 + + // Only vertex 0 has an edge + REQUIRE(std::distance(g[0].edges().begin(), g[0].edges().end()) == 1); + + // Vertices 2-5 have no outgoing edges + for (uint32_t i = 2; i <= 5; ++i) { + REQUIRE(g[i].edges().empty()); + } + } +} + +//================================================================================================== +// 15. Type Trait Tests +//================================================================================================== + +TEST_CASE("voum type traits", "[voum][traits]") { + SECTION("edge_type is correct") { + using traits = voum_graph_traits; + using edge_t = traits::edge_type; + + static_assert(std::is_same_v); + static_assert(std::is_same_v); + } + + SECTION("edges_type is std::unordered_map") { + using traits = voum_graph_traits; + using edges_t = traits::edges_type; + + // Verify it's an unordered_map by checking it has key_type and hasher + static_assert(requires { typename edges_t::key_type; }); + static_assert(requires { typename edges_t::hasher; }); + } + + SECTION("sourced trait") { + using traits_unsourced = voum_graph_traits; + using traits_sourced = voum_graph_traits; + + static_assert(traits_unsourced::sourced == false); + static_assert(traits_sourced::sourced == true); + } +} + +//================================================================================================== +// 16. Complex Graph Structure Tests +//================================================================================================== + +TEST_CASE("voum complex structures", "[voum][complex]") { + SECTION("complete graph K4") { + std::vector ee; + for (uint32_t i = 0; i < 4; ++i) { + for (uint32_t j = 0; j < 4; ++j) { + if (i != j) { + ee.push_back({i, j}); + } + } + } + + voum_void_void_void g; + g.load_edges(ee, std::identity{}); + + REQUIRE(g.size() == 4); + REQUIRE(count_all_edges(g) == 12); // 4 * 3 directed edges + + // Each vertex should have 3 outgoing edges + for (uint32_t i = 0; i < 4; ++i) { + REQUIRE(std::distance(g[i].edges().begin(), g[i].edges().end()) == 3); + } + } + + SECTION("cycle graph C5") { + voum_void_void_void g({{0, 1}, {1, 2}, {2, 3}, {3, 4}, {4, 0}}); + + REQUIRE(g.size() == 5); + REQUIRE(count_all_edges(g) == 5); + } + + SECTION("binary tree depth 3") { + voum_void_void_void g({ + {0, 1}, + {0, 2}, // Level 1 + {1, 3}, + {1, 4}, // Level 2 left + {2, 5}, + {2, 6} // Level 2 right + }); + + REQUIRE(g.size() == 7); + REQUIRE(count_all_edges(g) == 6); + + // Root has 2 children + REQUIRE(std::distance(g[0].edges().begin(), g[0].edges().end()) == 2); + + // Internal nodes have 2 children each + REQUIRE(std::distance(g[1].edges().begin(), g[1].edges().end()) == 2); + REQUIRE(std::distance(g[2].edges().begin(), g[2].edges().end()) == 2); + + // Leaves have no children + for (uint32_t i = 3; i <= 6; ++i) { + REQUIRE(g[i].edges().empty()); + } + } +} From 92627c01ad7c4e0380734ba9ae5a3ca93555c7fe Mon Sep 17 00:00:00 2001 From: Phil Ratzloff Date: Sat, 21 Feb 2026 20:26:34 -0500 Subject: [PATCH 6/8] doc: document decltype(auto) return-type preservation for value CPOs All three value CPOs (edge_value, vertex_value, graph_value) use decltype(auto), which preserves the exact return type of the resolved member/ADL/default function: by-value (T), by-reference (T&), by-const-reference (const T&), and by-rvalue-reference (T&&). Updated: - edge_cpo.hpp: edge_value doc comment and @return - graph_cpo.hpp: vertex_value and graph_value doc comments and @return - cpo-reference.md: Value CPOs section with return-type rows - cpo-implementation.md: guideline #11 and checklist item" --- docs/contributing/cpo-implementation.md | 2 ++ docs/reference/cpo-reference.md | 30 ++++++++++++++++----- include/graph/adj_list/detail/graph_cpo.hpp | 20 ++++++++++++-- include/graph/detail/edge_cpo.hpp | 10 ++++++- 4 files changed, 53 insertions(+), 9 deletions(-) diff --git a/docs/contributing/cpo-implementation.md b/docs/contributing/cpo-implementation.md index 5b4c00f..8c107fd 100644 --- a/docs/contributing/cpo-implementation.md +++ b/docs/contributing/cpo-implementation.md @@ -829,6 +829,7 @@ strategy: 8. **Mark `[[nodiscard]]`** on CPOs that return values. 9. **Mark `constexpr`** on all CPOs — enable compile-time evaluation where possible. 10. **Document compiler workarounds** if any are needed, with `#if defined(_MSC_VER)` guards. +11. **Use `decltype(auto)` for value CPOs** — the three value CPOs (`vertex_value`, `edge_value`, `graph_value`) must return `decltype(auto)` so that the exact return type of the resolved member/ADL/default function is preserved. Return-by-value (`T`), return-by-reference (`T&`), return-by-const-reference (`const T&`), and return-by-rvalue-reference (`T&&`) must all be forwarded faithfully without decay or copy. ### `_Fake_copy_init` Pattern (Advanced) @@ -938,6 +939,7 @@ For each new CPO: - [ ] `_fn::operator()` uses `if constexpr` chain (no separate overloads) - [ ] `noexcept` specification references `_Choice._No_throw` - [ ] `[[nodiscard]]` on operator() +- [ ] Value CPOs use `decltype(auto)` return type to preserve by-value/by-ref/by-const-ref/by-rvalue-ref semantics - [ ] `static_assert` with helpful message in the `_none` branch - [ ] `std::remove_cvref_t` used consistently for `_Choice` lookups - [ ] Type alias defined after the CPO (if applicable) diff --git a/docs/reference/cpo-reference.md b/docs/reference/cpo-reference.md index e06767e..2996cd6 100644 --- a/docs/reference/cpo-reference.md +++ b/docs/reference/cpo-reference.md @@ -35,9 +35,9 @@ All CPOs listed below are available in `namespace graph` after | `find_vertex_edge` | `(g, u, v)` / `(g, uid, vid)` | `vertex_edge_iterator_t` | O(deg) | Yes | | `contains_edge` | `(g, uid, vid)` | `bool` | O(deg) | Yes | | `has_edge` | `(g)` | `bool` | O(V) | Yes | -| `vertex_value` | `(g, u)` | deduced | O(1) | No | -| `edge_value` | `(g, uv)` | deduced | O(1) | Yes | -| `graph_value` | `(g)` | deduced | O(1) | No | +| `vertex_value` | `(g, u)` | `decltype(auto)` | O(1) | No | +| `edge_value` | `(g, uv)` | `decltype(auto)` | O(1) | Yes | +| `graph_value` | `(g)` | `decltype(auto)` | O(1) | No | | `partition_id` | `(g, u)` | `partition_id_t` | O(1) | No | | `num_partitions` | `(g)` | integral | O(1) | Yes (1) | @@ -253,19 +253,31 @@ Tests whether the graph has at least one edge. ## Value CPOs +All three value CPOs (`vertex_value`, `edge_value`, `graph_value`) return +`decltype(auto)`, which **preserves the exact return type** of the resolved +member, ADL, or default function. Return-by-value (`T`), +return-by-reference (`T&`), return-by-const-reference (`const T&`), and +return-by-rvalue-reference (`T&&`) are all faithfully forwarded without +decay or copy. + ### `vertex_value(g, u)` ```cpp -auto vertex_value(G& g, vertex_t& u) -> /* deduced */; +auto vertex_value(G& g, vertex_t& u) -> /* decltype(auto) */; ``` Returns the user-defined value associated with vertex `u`. **No default** — the graph must provide this via member or ADL. +| Property | Value | +|----------|-------| +| **Return type** | `decltype(auto)` — preserves by-value, by-ref, by-const-ref, and by-rvalue-ref | +| **Complexity** | O(1) | + ### `edge_value(g, uv)` ```cpp -auto edge_value(G& g, edge_t& uv) -> /* deduced */; +auto edge_value(G& g, edge_t& uv) -> /* decltype(auto) */; ``` Returns the user-defined value associated with edge `uv`. @@ -273,17 +285,23 @@ Returns the user-defined value associated with edge `uv`. | Property | Value | |----------|-------| | **Default** | Resolution chain: `uv.edge_value(g)` → ADL → descriptor → `uv.value` member → tuple `get<1>(uv)` (adj_list) / `get<2>(uv)` (edge list) | +| **Return type** | `decltype(auto)` — preserves by-value, by-ref, by-const-ref, and by-rvalue-ref | | **Complexity** | O(1) | ### `graph_value(g)` ```cpp -auto graph_value(G& g) -> /* deduced */; +auto graph_value(G& g) -> /* decltype(auto) */; ``` Returns the user-defined value associated with the graph itself. **No default** — the graph must provide this. +| Property | Value | +|----------|-------| +| **Return type** | `decltype(auto)` — preserves by-value, by-ref, by-const-ref, and by-rvalue-ref | +| **Complexity** | O(1) | + --- ## Partition CPOs diff --git a/include/graph/adj_list/detail/graph_cpo.hpp b/include/graph/adj_list/detail/graph_cpo.hpp index 96f601d..f0237a1 100644 --- a/include/graph/adj_list/detail/graph_cpo.hpp +++ b/include/graph/adj_list/detail/graph_cpo.hpp @@ -2198,11 +2198,19 @@ namespace _cpo_impls { * * This provides access to user-defined vertex properties/data stored in the graph. * + * **Return-type preservation guarantee:** The `operator()` returns `decltype(auto)`, + * which faithfully preserves the exact return type of the resolved function: + * return-by-value (`T`), return-by-reference (`T&`), return-by-const-reference + * (`const T&`), and return-by-rvalue-reference (`T&&`) are all forwarded without + * decay or copy. + * * @tparam G Graph type * @tparam U Vertex descriptor type (constrained to be a vertex_descriptor_type) * @param g Graph container * @param u Vertex descriptor - * @return Reference to the vertex value/data + * @return Exactly the type returned by the resolved dispatch path (`decltype(auto)`). + * By-value, by-reference, by-const-reference, and by-rvalue-reference + * are all preserved. */ template [[nodiscard]] constexpr decltype(auto) operator()(G&& g, const U& u) const @@ -2292,9 +2300,17 @@ namespace _cpo_impls { * This provides access to user-defined graph-level properties/metadata * stored in the graph container (e.g., name, creation date, statistics). * + * **Return-type preservation guarantee:** The `operator()` returns `decltype(auto)`, + * which faithfully preserves the exact return type of the resolved function: + * return-by-value (`T`), return-by-reference (`T&`), return-by-const-reference + * (`const T&`), and return-by-rvalue-reference (`T&&`) are all forwarded without + * decay or copy. + * * @tparam G Graph type * @param g Graph container - * @return Reference to the graph value/properties (or by-value if custom implementation returns by-value) + * @return Exactly the type returned by the resolved dispatch path (`decltype(auto)`). + * By-value, by-reference, by-const-reference, and by-rvalue-reference + * are all preserved. */ template [[nodiscard]] constexpr decltype(auto) operator()(G&& g) const noexcept(_Choice>._No_throw) diff --git a/include/graph/detail/edge_cpo.hpp b/include/graph/detail/edge_cpo.hpp index 2319693..3e28137 100644 --- a/include/graph/detail/edge_cpo.hpp +++ b/include/graph/detail/edge_cpo.hpp @@ -486,11 +486,19 @@ namespace _cpo_impls { * * This provides access to user-defined edge properties/weights stored in the graph. * + * **Return-type preservation guarantee:** The `operator()` returns `decltype(auto)`, + * which faithfully preserves the exact return type of the resolved function: + * return-by-value (`T`), return-by-reference (`T&`), return-by-const-reference + * (`const T&`), and return-by-rvalue-reference (`T&&`) are all forwarded without + * decay or copy. + * * @tparam G Graph type * @tparam E Edge descriptor or edge type * @param g Graph container * @param uv Edge descriptor or edge - * @return Reference to the edge value/properties (or by-value if custom implementation returns by-value) + * @return Exactly the type returned by the resolved dispatch path (`decltype(auto)`). + * By-value, by-reference, by-const-reference, and by-rvalue-reference + * are all preserved. */ template [[nodiscard]] constexpr decltype(auto) operator()(G&& g, E&& uv) const From 102e3915db4678eacbf571fca409cf78df9ec5eb Mon Sep 17 00:00:00 2001 From: Phil Ratzloff Date: Sat, 21 Feb 2026 20:38:01 -0500 Subject: [PATCH 7/8] docs: add project logo and integrate into README and docs index - Add docs/assets/logo.svg (K4 graph in C++ blue/cyan, "GRAPH v3" text) - README.md: logo inline with title using HTML table layout - docs/index.md: logo inline with title using HTML table layout" --- README.md | 7 ++++++ docs/assets/logo.svg | 52 ++++++++++++++++++++++++++++++++++++++++++++ docs/index.md | 7 ++++++ 3 files changed, 66 insertions(+) create mode 100644 docs/assets/logo.svg diff --git a/README.md b/README.md index 361cdb0..1763a67 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,14 @@ + + + +
graph-v3 logo + # graph-v3 **A modern C++20 graph library — header-only, descriptor-based, works with your containers.** +
+ [![C++ Standard](https://img.shields.io/badge/C%2B%2B-20-blue.svg)](https://en.cppreference.com/w/cpp/20) diff --git a/docs/assets/logo.svg b/docs/assets/logo.svg new file mode 100644 index 0000000..6eb18af --- /dev/null +++ b/docs/assets/logo.svg @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + GRAPH v3 + \ No newline at end of file diff --git a/docs/index.md b/docs/index.md index 530318c..64b926a 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,7 +1,14 @@ + + + +
graph-v3 logo + # graph-v3 Documentation > A modern C++20 graph library — 13 algorithms, 7 lazy views, 3 containers, 4261+ tests. +
+ --- ## For Users From def39decdce60ac9dc22a10276f29801f2894c28 Mon Sep 17 00:00:00 2001 From: Phil Ratzloff Date: Sat, 21 Feb 2026 23:02:34 -0500 Subject: [PATCH 8/8] Add phased implementation plan for incoming edges MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Create agents/incoming_edges_plan.md with 10 self-contained phases - Second review pass on design doc: remove has_in_edge (duplicates contains_in_edge), add missing find_in_edge/contains_in_edge overloads, add pipe-syntax adaptor section (§8.4), expand Appendix A CPO table, fix trait wording, add topological_sort to §9 with precise call counts --- agents/incoming_edges_design.md | 201 +++++- agents/incoming_edges_plan.md | 1094 +++++++++++++++++++++++++++++++ 2 files changed, 1270 insertions(+), 25 deletions(-) create mode 100644 agents/incoming_edges_plan.md diff --git a/agents/incoming_edges_design.md b/agents/incoming_edges_design.md index 8c61a98..f32acf9 100644 --- a/agents/incoming_edges_design.md +++ b/agents/incoming_edges_design.md @@ -48,7 +48,7 @@ The current names remain as-is and **continue to mean "outgoing edges"**: | `num_edges(g)` | Yes | Total edge count (direction-agnostic) | | `find_vertex_edge(g, u, v)` | Yes | Find outgoing edge from `u` to `v` | | `contains_edge(g, u, v)` | Yes | Check outgoing edge from `u` to `v` | -| `has_edge(g, uid, vid)` | Yes | Check outgoing edge from `uid` to `vid` | +| `has_edge(g)` | Yes | Check whether graph has at least one edge | | `edge_value(g, uv)` | Yes | Edge property (direction-agnostic) | ### 3.2 New outgoing aliases (optional clarity) @@ -75,9 +75,23 @@ These are defined as inline constexpr CPO aliases or thin wrapper functions in |---|---| | `in_edges(g, u)` / `in_edges(g, uid)` | Incoming edges to vertex `u` | | `in_degree(g, u)` / `in_degree(g, uid)` | In-degree of `u` | -| `find_in_edge(g, u, v)` | Find incoming edge from `v` to `u` | -| `contains_in_edge(g, u, v)` | Check incoming edge from `v` to `u` | -| `has_in_edge(g, uid, vid)` | Check incoming edge from `vid` to `uid` | +| `find_in_edge(g, u, v)` | Find incoming edge from `v` to `u` (both descriptors) | +| `find_in_edge(g, u, vid)` | Find incoming edge from `vid` to `u` (descriptor + ID) | +| `find_in_edge(g, uid, vid)` | Find incoming edge from `vid` to `uid` (both IDs) | +| `contains_in_edge(g, u, v)` | Check incoming edge from `v` to `u` (both descriptors) | +| `contains_in_edge(g, uid, vid)` | Check incoming edge from `vid` to `uid` (both IDs) | + +Parameter convention for incoming-edge queries: + +- `u`/`uid` = **target** vertex (the vertex receiving incoming edges) +- `v`/`vid` = **source** vertex (the vertex the edge comes from) + +Example: + +```cpp +// true if edge 7 -> 3 exists +bool b = contains_in_edge(g, /*target=*/3, /*source=*/7); +``` --- @@ -93,7 +107,6 @@ These are defined as inline constexpr CPO aliases or thin wrapper functions in |---|---|---| | 1 | `_vertex_member` | `u.inner_value(g).in_edges()` | | 2 | `_adl` | ADL `in_edges(g, u)` | -| 3 | `_edge_value_pattern` | (not applicable — no implicit reverse structure) | For the `(g, uid)` overload: @@ -102,8 +115,9 @@ For the `(g, uid)` overload: | 1 | `_adl` | ADL `in_edges(g, uid)` | | 2 | `_default` | `in_edges(g, *find_vertex(g, uid))` | -**Return type:** `in_vertex_edge_range_t` — an `edge_descriptor_view` wrapping -the reverse edge container's iterators. +**Return type:** `in_vertex_edge_range_t` — i.e. the exact range type returned by +`in_edges(g, u)` after CPO resolution. This may be an `edge_descriptor_view`, but +custom member/ADL implementations may return a different valid range type. ### 4.2 `in_degree(g, u)` / `in_degree(g, uid)` @@ -127,12 +141,30 @@ inline constexpr auto& out_degree = degree; inline constexpr auto& find_out_edge = find_vertex_edge; ``` -### 4.4 `find_in_edge`, `contains_in_edge`, `has_in_edge` +### 4.4 `find_in_edge`, `contains_in_edge` -These mirror `find_vertex_edge` (`find_out_edge`) / `contains_edge` / `has_edge` but -operate on the reverse adjacency structure. Implementation follows the same +These mirror `find_vertex_edge` (`find_out_edge`) / `contains_edge` but operate +on the reverse adjacency structure. Implementation follows the same member → ADL → default cascade pattern. +`find_in_edge` has three overloads mirroring `find_vertex_edge`: +- `find_in_edge(g, u, v)` — both vertex descriptors +- `find_in_edge(g, u, vid)` — descriptor + vertex ID +- `find_in_edge(g, uid, vid)` — both vertex IDs + +`contains_in_edge` has two overloads mirroring `contains_edge`: +- `contains_in_edge(g, u, v)` — both vertex descriptors +- `contains_in_edge(g, uid, vid)` — both vertex IDs + +The default implementation of `find_in_edge` iterates `in_edges(g, u)` and +matches `source_id(g, ie)` against the target vertex (the in-neighbor). The default +`contains_in_edge` calls `find_in_edge` and checks the result. + +> **Note:** There is no `has_in_edge` counterpart. The existing `has_edge(g)` takes +> only the graph and means "graph has at least one edge" — a direction-agnostic +> query. A `has_in_edge(g)` variant would be redundant with `has_edge(g)`, and +> `has_in_edge(g, uid, vid)` would duplicate `contains_in_edge(g, uid, vid)`. + --- ## 5. New Type Aliases @@ -220,9 +252,9 @@ concept index_bidirectional_adjacency_list = |---|---| | `has_in_degree` | `in_degree(g, u)` and `in_degree(g, uid)` both return integral | | `has_in_degree_v` | `bool` shorthand | -| `has_find_in_edge` | All `find_in_edge` overloads return `in_edge_t` | +| `has_find_in_edge` | `find_in_edge` CPO resolves for graph type `G` | | `has_find_in_edge_v` | `bool` shorthand | -| `has_contains_in_edge` | `contains_in_edge(g, u, v)` returns bool | +| `has_contains_in_edge` | `contains_in_edge` CPO resolves for graph type `G` | | `has_contains_in_edge_v` | `bool` shorthand | --- @@ -288,13 +320,42 @@ namespace graph::views { } ``` +### 8.4 Pipe-syntax adaptors + +**File:** `include/graph/views/adaptors.hpp` (extend existing) + +The existing `adaptors.hpp` provides pipe-syntax closures for all outgoing views +(`g | incidence(uid)`, `g | neighbors(uid)`, etc.). Corresponding closures must +be added for the new incoming views: + +| Pipe expression | Expands to | +|---|---| +| `g \| in_incidence(uid)` | `in_incidence(g, uid)` | +| `g \| in_incidence(uid, evf)` | `in_incidence(g, uid, evf)` | +| `g \| basic_in_incidence(uid)` | `basic_in_incidence(g, uid)` | +| `g \| basic_in_incidence(uid, evf)` | `basic_in_incidence(g, uid, evf)` | +| `g \| in_neighbors(uid)` | `in_neighbors(g, uid)` | +| `g \| in_neighbors(uid, vvf)` | `in_neighbors(g, uid, vvf)` | +| `g \| basic_in_neighbors(uid)` | `basic_in_neighbors(g, uid)` | +| `g \| basic_in_neighbors(uid, vvf)` | `basic_in_neighbors(g, uid, vvf)` | + +Outgoing aliases for symmetry: + +| Pipe alias | Forwards to | +|---|---| +| `g \| out_incidence(uid)` | `g \| incidence(uid)` | +| `g \| out_neighbors(uid)` | `g \| neighbors(uid)` | +| `g \| basic_out_incidence(uid)` | `g \| basic_incidence(uid)` | +| `g \| basic_out_neighbors(uid)` | `g \| basic_neighbors(uid)` | + --- -## 9. BFS/DFS Parameterization +## 9. BFS/DFS/Topological-Sort Parameterization ### Problem -`bfs.hpp` and `dfs.hpp` hardcode `adj_list::edges(g, vertex)` in ~6 locations each. +`bfs.hpp`, `dfs.hpp`, and `topological_sort.hpp` hardcode `adj_list::edges(g, vertex)` +(5 locations in BFS, 5 in DFS, 7 in topological sort). To support reverse traversal (following incoming edges), they need parameterization. ### Solution @@ -336,7 +397,9 @@ vertices_bfs(g, seed, vvf, in_edges_accessor{}) // reverse BFS edges_bfs(g, seed, evf, in_edges_accessor{}) // reverse BFS ``` -Same pattern applies to DFS and topological sort views. +Same pattern applies to DFS views (`vertices_dfs_view`, `edges_dfs_view`) and +topological sort views (`vertices_topological_sort_view`, `edges_topological_sort_view`). +All three view families receive identical `EdgeAccessor` parameterization. --- @@ -413,7 +476,7 @@ at zero cost. ### 10.4 New trait type combinations for `dynamic_graph` -For each existing trait file (26 total in `include/graph/container/traits/`), a +For each existing trait file (27 total in `include/graph/container/traits/`), a corresponding `b` (bidirectional) variant may be added: | Existing | Bidirectional Variant | Description | @@ -425,6 +488,30 @@ corresponding `b` (bidirectional) variant may be added: Alternatively, a single set of traits with `Bidirectional` as a template parameter avoids the combinatorial explosion. +### 10.5 Mutator Invariants & Storage/Complexity Budget + +To keep the implementation predictable, reverse adjacency support must define and +enforce container invariants explicitly. + +**Mutator invariants (Bidirectional = true):** + +1. `create_edge(u, v)` inserts one forward entry in `u.out_edges_` and one reverse + entry in `v.in_edges_` representing the same logical edge. +2. `erase_edge(u, v)` removes both forward and reverse entries. +3. `erase_vertex(x)` removes all incident edges and corresponding mirrored entries + in opposite adjacency lists. +4. `clear_vertex(x)` removes both outgoing and incoming incidence for `x`. +5. `clear()` empties both forward and reverse structures. +6. Copy/move/swap preserve forward↔reverse consistency. + +**Complexity/storage targets:** + +- Outgoing-only mode (`Bidirectional = false`) remains unchanged. +- Bidirectional mode targets O(1) additional work per edge insertion/erasure in + adjacency-list containers (plus container-specific costs). +- Storage overhead target is roughly one additional reverse entry per edge + (`O(E)` extra), with optional reduced footprint via lightweight `in_edge_t`. + --- ## 11. Algorithm Updates @@ -460,7 +547,6 @@ using adj_list::out_degree; using adj_list::find_out_edge; using adj_list::find_in_edge; using adj_list::contains_in_edge; -using adj_list::has_in_edge; // New concepts using adj_list::in_vertex_edge_range; @@ -501,7 +587,7 @@ using adj_list::out_edge_t; | `docs/user-guide/algorithms.md` | Note which algorithms benefit from bidirectional access | | `docs/user-guide/containers.md` | Document bidirectional dynamic_graph and compressed_graph | | `docs/reference/concepts.md` | Add `bidirectional_adjacency_list`, `in_vertex_edge_range` | -| `docs/reference/cpo-reference.md` | Add `in_edges`, `in_degree`, `out_edges`, `out_degree`, `find_vertex_in_edge`, `contains_in_edge`, `has_in_edge` | +| `docs/reference/cpo-reference.md` | Add `in_edges`, `in_degree`, `out_edges`, `out_degree`, `find_in_edge`, `contains_in_edge` | | `docs/reference/type-aliases.md` | Add `in_vertex_edge_range_t`, `in_edge_t`, etc. | | `docs/reference/adjacency-list-interface.md` | Add incoming edge section | | `docs/contributing/cpo-implementation.md` | Add `in_edges` as an example of the CPO pattern | @@ -527,22 +613,26 @@ using adj_list::out_edge_t; 6. Add `has_in_degree` trait 7. Update `graph.hpp` re-exports 8. Add unit tests for CPO resolution (stub graph with `in_edges()` member) +9. Add compile-time concept tests for `bidirectional_adjacency_list` +10. Add mixed-type tests where `in_edge_t != edge_t` (source-only incoming edges) ### Phase 2: Views (~2 days) 1. Create `in_incidence.hpp` (copy+adapt `incidence.hpp`) 2. Create `in_neighbors.hpp` (copy+adapt `neighbors.hpp`) 3. Add `out_incidence` / `out_neighbors` aliases -4. Unit tests for all new views +4. Add pipe-syntax adaptors to `adaptors.hpp` (§8.4) +5. Unit tests for all new views (including pipe-syntax adaptor tests) ### Phase 3: Container Support (~3-4 days) 1. Add `Bidirectional` parameter to `dynamic_graph_base` 2. Update `create_edge` / `erase_edge` / `clear` to maintain reverse lists -3. Add ADL `in_edges()` friend to bidirectional dynamic_graph -4. Add CSC support to `compressed_graph` -5. Add `in_edges()` to `undirected_adjacency_list` (returns same as `edges()`) -6. Unit tests for all three containers +3. Update `erase_vertex` / `clear_vertex` / copy-move-swap paths to preserve reverse invariants +4. Add ADL `in_edges()` friend to bidirectional dynamic_graph +5. Add CSC support to `compressed_graph` +6. Add `in_edges()` to `undirected_adjacency_list` (returns same as `edges()`) +7. Unit tests for all three containers (including mutator invariant checks) ### Phase 4: BFS/DFS Parameterization (~1-2 days) @@ -563,17 +653,65 @@ using adj_list::out_edge_t; 2. Create `bidirectional-access.md` tutorial 3. Update CHANGELOG.md +### Recommended execution order (risk-first) + +To reduce integration risk and keep PRs reviewable, execute phases with strict +merge gates and defer high-churn container internals until API contracts are +proven by tests. + +1. **Phase 1 (CPOs/concepts/traits) first, in one or two PRs max.** +2. **Phase 2 (views) second**, consuming only the new public CPOs. +3. **Phase 4 (BFS/DFS parameterization) third**, before container internals, + to validate the accessor approach on existing graphs. +4. **Phase 3 (container support) fourth**, split by container: + `undirected_adjacency_list` → `dynamic_graph` → `compressed_graph`. +5. **Phase 5 (algorithm updates) fifth**, after reverse traversal behavior is + stable and benchmarked. +6. **Phase 6 (docs/changelog) last**, plus updates in each phase PR where needed. + +### Merge gates (go/no-go criteria) + +Each phase must satisfy all gates before proceeding: + +- `linux-gcc-debug` full test suite passes. +- New compile-time concept tests pass (`requires`/`static_assert` coverage). +- No regressions in existing outgoing-edge APIs (`edges`, `degree`, `find_vertex_edge`). +- New incoming APIs are documented in reference docs for that phase. + +Additional phase-specific gates: + +- **After Phase 1:** + - Mixed-type incoming-edge test (`in_edge_t != edge_t`) passes. + - `in_edges`/`in_degree` ADL and member dispatch paths are covered. +- **After Phase 4:** + - Reverse BFS/DFS outputs validated against expected traversal sets. + - Existing BFS/DFS/topological-sort signatures remain source-compatible. +- **After Phase 3:** + - Mutator invariant tests pass (`create_edge`, `erase_edge`, `erase_vertex`, + `clear_vertex`, `clear`, copy/move/swap consistency). + +### Fallback and scope control + +- If CSC integration in `compressed_graph` slips, ship `dynamic_graph` + + `undirected_adjacency_list` incoming support first and keep CSC behind a + follow-up milestone. +- Keep `transpose_view` optional for initial release; prioritize core CPOs, + views, and reverse traversal correctness. +- If API pressure grows, keep only the decided aliases (`out_*`) and avoid + introducing additional directional synonyms. + --- ## 15. Summary of All Changes | Category | New | Modified | Deleted | |---|---|---|---| -| **CPOs** | `in_edges`, `in_degree`, `out_edges` (alias), `out_degree` (alias), `find_out_edge` (alias), `find_in_edge`, `contains_in_edge`, `has_in_edge` | — | — | +| **CPOs** | `in_edges`, `in_degree`, `out_edges` (alias), `out_degree` (alias), `find_out_edge` (alias), `find_in_edge`, `contains_in_edge` | — | — | | **Concepts** | `in_vertex_edge_range`, `bidirectional_adjacency_list`, `index_bidirectional_adjacency_list` | — | — | | **Traits** | `has_in_degree`, `has_in_degree_v`, `has_find_in_edge`, `has_contains_in_edge` | — | — | | **Type Aliases** | `in_vertex_edge_range_t`, `in_vertex_edge_iterator_t`, `in_edge_t`, `out_vertex_edge_range_t`, `out_vertex_edge_iterator_t`, `out_edge_t` | — | — | -| **Views** | `in_incidence.hpp`, `in_neighbors.hpp`, `out_edges_accessor`, `in_edges_accessor` | `bfs.hpp`, `dfs.hpp`, `topological_sort.hpp` (add `EdgeAccessor` param) | — | +| **Views** | `in_incidence.hpp`, `in_neighbors.hpp` | `bfs.hpp`, `dfs.hpp`, `topological_sort.hpp` (add `EdgeAccessor` param), `adaptors.hpp` (add pipe closures) | — | +| **Traversal policies** | `out_edges_accessor`, `in_edges_accessor` | — | — | | **Containers** | — | `dynamic_graph.hpp` (add `Bidirectional`), `compressed_graph.hpp` (add CSC), `undirected_adjacency_list.hpp` (add `in_edges` friend) | — | | **Algorithms** | `transpose_view` | `connected_components.hpp` (Kosaraju optimization) | — | | **Headers** | — | `graph.hpp` (new includes & re-exports) | — | @@ -592,8 +730,14 @@ using adj_list::out_edge_t; | `in_edges(g, u)` | `u.inner_value(g).in_edges()` | ADL `in_edges(g, u)` | *(no default)* | | `degree(g, u)` | `g.degree(u)` | ADL `degree(g, u)` | `size(edges(g, u))` | | `in_degree(g, u)` | `g.in_degree(u)` | ADL `in_degree(g, u)` | `size(in_edges(g, u))` | +| `find_vertex_edge(g, u, v)` | `g.find_vertex_edge(u, v)` | ADL `find_vertex_edge(g, u, v)` | iterate `edges(g, u)`, match `target_id` | +| `find_in_edge(g, u, v)` | `g.find_in_edge(u, v)` | ADL `find_in_edge(g, u, v)` | iterate `in_edges(g, u)`, match `source_id` | +| `contains_edge(g, u, v)` | `g.contains_edge(u, v)` | ADL `contains_edge(g, u, v)` | iterate `edges(g, u)`, match `target_id` | +| `contains_in_edge(g, u, v)` | `g.contains_in_edge(u, v)` | ADL `contains_in_edge(g, u, v)` | iterate `in_edges(g, u)`, match `source_id` | +| `has_edge(g)` | `g.has_edge()` | ADL `has_edge(g)` | iterate vertices, find non-empty edges | | `out_edges(g, u)` | → `edges(g, u)` | | | | `out_degree(g, u)` | → `degree(g, u)` | | | +| `find_out_edge(g, u, v)` | → `find_vertex_edge(g, u, v)` | | | ## Appendix B: No-Rename Justification @@ -636,6 +780,13 @@ on incoming edges — not `edge_value()`. This means: `requires edge_value(g, in_edge_t{})` constraint. - The undirected case (`in_edge_t == edge_t`) is the zero-cost happy path. +Minimum incoming-edge contract for algorithms: + +- **Always required:** `in_edges(g, u)`, `source_id(g, ie)` +- **Conditionally required:** `edge_value(g, ie)` only for algorithms/views that + explicitly consume incoming edge properties +- **Not required:** `target_id(g, ie)` for incoming-only traversal algorithms + ## Appendix D: `out_` Alias Retention Decision **Date:** 2026-02-21 diff --git a/agents/incoming_edges_plan.md b/agents/incoming_edges_plan.md new file mode 100644 index 0000000..a3b4b1c --- /dev/null +++ b/agents/incoming_edges_plan.md @@ -0,0 +1,1094 @@ +# Incoming Edges — Phased Implementation Plan + +**Source:** `agents/incoming_edges_design.md` +**Branch:** `incoming` +**Build preset:** `linux-gcc-debug` (full test suite) + +--- + +## How to use this plan + +Each phase is a self-contained unit of work that can be completed by an agent in +one session. Phases are strictly ordered — each depends on the prior phase compiling +and passing all tests. Within each phase, steps are listed in execution order. + +**Conventions used throughout:** + +- "Mirror X" means copy the implementation pattern from X, replacing outgoing + names/semantics with incoming equivalents. +- "Full test suite passes" means `cmake --build build/linux-gcc-debug -j$(nproc)` + succeeds and `cd build/linux-gcc-debug && ctest --output-on-failure` reports + zero failures with no regressions. +- File paths are relative to the repository root. + +--- + +## Phase 1 — `in_edges` and `in_degree` CPOs + type aliases + +**Goal:** Add the two core incoming-edge CPOs, their public instances, the +outgoing aliases (`out_edges`, `out_degree`, `find_out_edge`), the six new type +aliases, and a CPO unit-test file proving all resolution tiers work. + +**Why first:** Every subsequent phase depends on `in_edges` and `in_edge_t` +existing. This phase touches exactly one production header and adds one test +file, so it is low-risk and easy to validate. + +### Files to modify + +| File | Action | +|---|---| +| `include/graph/adj_list/detail/graph_cpo.hpp` | Add CPOs, aliases, public instances | +| `tests/adj_list/CMakeLists.txt` | Register new test file | + +### Files to create + +| File | Content | +|---|---| +| `tests/adj_list/cpo/test_in_edges_cpo.cpp` | Unit tests | + +### Steps + +#### 1.1 Add `in_edges` CPO (graph_cpo.hpp) + +Insert a new section **after** the `edges` public instance and type aliases +(after line ~835, before the `_target` namespace). Follow the exact structural +pattern of `namespace _edges`: + +``` +namespace _cpo_impls { + namespace _in_edges { + // --- (g, u) overload --- + enum class _St_u { _none, _vertex_member, _adl }; + + template + concept _has_vertex_member_u = is_vertex_descriptor_v<...> && + requires(G& g, const U& u) { + { u.inner_value(g).in_edges() } -> std::ranges::forward_range; + }; + + template + concept _has_adl_u = requires(G& g, const U& u) { + { in_edges(g, u) } -> std::ranges::forward_range; + }; + + // NOTE: No _edge_value_pattern tier — in_edges has no implicit default. + // A graph MUST explicitly provide in_edges() via member or ADL. + + template + [[nodiscard]] consteval _Choice_t<_St_u> _Choose_u() noexcept { ... } + + // --- (g, uid) overload --- + enum class _St_uid { _none, _adl, _default }; + // _has_adl_uid, _has_default_uid (find_vertex + in_edges(g, u)) + template + [[nodiscard]] consteval _Choice_t<_St_uid> _Choose_uid() noexcept { ... } + + class _fn { + // operator()(G&& g, const U& u) — vertex descriptor + // operator()(G&& g, const VId& uid) — vertex ID + // Both use _wrap_if_needed() to ensure edge_descriptor_view return + }; + } +} +``` + +Key differences from `edges`: +- Only 2 tiers for `(g, u)`: `_vertex_member` and `_adl`. No `_edge_value_pattern`. +- The `(g, uid)` default delegates to `in_edges(g, *find_vertex(g, uid))`. +- The `_has_default_uid` concept checks `_has_adl_u` (not `_has_edge_value_pattern`). + +#### 1.2 Add `in_edges` public instance and type aliases + +Immediately after the `_in_edges` namespace closing brace: + +```cpp +inline namespace _cpo_instances { + inline constexpr _cpo_impls::_in_edges::_fn in_edges{}; +} + +template +using in_vertex_edge_range_t = decltype(in_edges(std::declval(), std::declval>())); + +template +using in_vertex_edge_iterator_t = std::ranges::iterator_t>; + +template +using in_edge_t = std::ranges::range_value_t>; +``` + +#### 1.3 Add `in_degree` CPO + +Insert after the `in_edges` section, mirroring `namespace _degree`: + +``` +namespace _cpo_impls { + namespace _in_degree { + // (g, u): _member (g.in_degree(u)), _adl, _default (size(in_edges(g,u))) + // (g, uid): _member, _adl, _default (*find_vertex then in_degree(g,u)) + class _fn { ... }; + } +} + +inline namespace _cpo_instances { + inline constexpr _cpo_impls::_in_degree::_fn in_degree{}; +} +``` + +Key difference from `degree`: the `_default` tier calls `in_edges(g, u)` instead +of `edges(g, u)`. + +#### 1.4 Add outgoing aliases + +After the `in_degree` public instance: + +```cpp +inline namespace _cpo_instances { + inline constexpr auto& out_edges = edges; + inline constexpr auto& out_degree = degree; + inline constexpr auto& find_out_edge = find_vertex_edge; +} +``` + +#### 1.5 Add outgoing type aliases + +```cpp +template +using out_vertex_edge_range_t = vertex_edge_range_t; + +template +using out_vertex_edge_iterator_t = vertex_edge_iterator_t; + +template +using out_edge_t = edge_t; +``` + +#### 1.6 Create test file `tests/adj_list/cpo/test_in_edges_cpo.cpp` + +Test the following scenarios (mirror `test_edges_cpo.cpp` structure): + +1. **Stub graph with `in_edges()` member** — a minimal struct whose vertex + `inner_value(g).in_edges()` returns a `vector`. Verify the CPO + dispatches to `_vertex_member` tier and the return is an + `edge_descriptor_view`. + +2. **Stub graph with ADL `in_edges(g, u)` friend** — verify `_adl` tier. + +3. **`(g, uid)` overload with default** — verify `_default` tier delegates + through `find_vertex` + `in_edges(g, u)`. + +4. **Type alias verification** — `in_vertex_edge_range_t`, + `in_vertex_edge_iterator_t`, `in_edge_t` all compile and produce + expected types. + +5. **Mixed-type test** — a stub graph where `edge_t` is + `pair` but `in_edge_t` is just `int`. Verify both + aliases are independently deduced. + +6. **`out_edges` / `out_degree` / `find_out_edge` aliases** — verify they + are the exact same object as `edges` / `degree` / `find_vertex_edge` + (use `static_assert(&out_edges == &edges)`). + +7. **`in_degree` CPO** — test member, ADL, and default (`size(in_edges)`) + resolution tiers. + +8. **Outgoing type aliases** — verify `out_edge_t` is `edge_t` etc. + +#### 1.7 Register test in CMakeLists + +Add to `tests/adj_list/CMakeLists.txt` under the CPOs section: + +```cmake + cpo/test_in_edges_cpo.cpp +``` + +#### 1.8 Build and run full test suite + +```bash +cmake --build build/linux-gcc-debug -j$(nproc) +cd build/linux-gcc-debug && ctest --output-on-failure +``` + +### Merge gate + +- [ ] Full test suite passes with zero regressions. +- [ ] All 8 test scenarios pass. +- [ ] `in_edge_t != edge_t` mixed-type test passes. +- [ ] `out_edges` alias identity check passes. + +--- + +## Phase 2 — `find_in_edge` and `contains_in_edge` CPOs + traits + +**Goal:** Add the remaining incoming-edge CPOs (`find_in_edge`, +`contains_in_edge`), the new traits (`has_in_degree`, `has_find_in_edge`, +`has_contains_in_edge`), and their unit tests. + +**Why second:** These CPOs depend on `in_edges` (Phase 1). They complete the +full incoming-edge CPO surface before concepts are defined. + +### Files to modify + +| File | Action | +|---|---| +| `include/graph/adj_list/detail/graph_cpo.hpp` | Add `find_in_edge`, `contains_in_edge` CPOs | +| `include/graph/adj_list/adjacency_list_traits.hpp` | Add 3 new traits | +| `tests/adj_list/CMakeLists.txt` | Register new test files | + +### Files to create + +| File | Content | +|---|---| +| `tests/adj_list/cpo/test_find_in_edge_cpo.cpp` | Unit tests for `find_in_edge` | +| `tests/adj_list/cpo/test_contains_in_edge_cpo.cpp` | Unit tests for `contains_in_edge` | +| `tests/adj_list/traits/test_incoming_edge_traits.cpp` | Trait tests | + +### Steps + +#### 2.1 Add `find_in_edge` CPO (graph_cpo.hpp) + +Mirror `namespace _find_vertex_edge` with three overload groups: + +1. `find_in_edge(g, u, v)` — both descriptors. Default iterates + `in_edges(g, u)` and matches `source_id(g, ie)` against `vertex_id(g, v)`. +2. `find_in_edge(g, u, vid)` — descriptor + ID. Default iterates + `in_edges(g, u)` and matches `source_id(g, ie)` against `vid`. +3. `find_in_edge(g, uid, vid)` — both IDs. Default delegates via + `*find_vertex(g, uid)`. + +Key difference from `find_vertex_edge`: iterates `in_edges` and compares +`source_id` instead of `target_id`. + +```cpp +inline namespace _cpo_instances { + inline constexpr _cpo_impls::_find_in_edge::_fn find_in_edge{}; +} +``` + +#### 2.2 Add `contains_in_edge` CPO (graph_cpo.hpp) + +Mirror `namespace _contains_edge` with two overload groups: + +1. `contains_in_edge(g, u, v)` — both descriptors. +2. `contains_in_edge(g, uid, vid)` — both IDs. + +Default implementations iterate `in_edges(g, u)` and match `source_id`. + +```cpp +inline namespace _cpo_instances { + inline constexpr _cpo_impls::_contains_in_edge::_fn contains_in_edge{}; +} +``` + +#### 2.3 Add traits (adjacency_list_traits.hpp) + +Follow the existing `detail::has_X_impl` → `has_X` → `has_X_v` pattern: + +```cpp +// --- has_in_degree --- +namespace detail { + template + concept has_in_degree_impl = requires(G& g, vertex_t u, vertex_id_t uid) { + { in_degree(g, u) } -> std::integral; + { in_degree(g, uid) } -> std::integral; + }; +} +template concept has_in_degree = detail::has_in_degree_impl; +template inline constexpr bool has_in_degree_v = has_in_degree; + +// --- has_find_in_edge --- +namespace detail { + template + concept has_find_in_edge_impl = requires(G& g, vertex_t u, vertex_t v, + vertex_id_t uid, vertex_id_t vid) { + find_in_edge(g, u, v); + find_in_edge(g, u, vid); + find_in_edge(g, uid, vid); + }; +} +template concept has_find_in_edge = detail::has_find_in_edge_impl; +template inline constexpr bool has_find_in_edge_v = has_find_in_edge; + +// --- has_contains_in_edge --- +namespace detail { + template + concept has_contains_in_edge_impl = requires(G& g, vertex_t u, vertex_t v, + vertex_id_t uid, vertex_id_t vid) { + { contains_in_edge(g, u, v) } -> std::convertible_to; + { contains_in_edge(g, uid, vid) } -> std::convertible_to; + }; +} +template concept has_contains_in_edge = detail::has_contains_in_edge_impl; +template inline constexpr bool has_contains_in_edge_v = has_contains_in_edge; +``` + +#### 2.4 Create test files + +**`test_find_in_edge_cpo.cpp`** — test all 3 overloads: +- Stub graph with ADL `in_edges()` friend returning known edges. +- Verify `find_in_edge(g, u, v)` finds correct incoming edge by `source_id`. +- Verify `find_in_edge(g, u, vid)` works. +- Verify `find_in_edge(g, uid, vid)` delegates through `find_vertex`. +- Verify not-found case. + +**`test_contains_in_edge_cpo.cpp`** — test both overloads: +- Verify `contains_in_edge(g, u, v)` returns true/false correctly. +- Verify `contains_in_edge(g, uid, vid)` returns true/false correctly. + +**`test_incoming_edge_traits.cpp`** — test all 3 traits: +- Stub graph that models `in_edges`/`in_degree` → `has_in_degree_v` is true. +- Plain `vector>` → `has_in_degree_v` is false. +- Stub graph with `in_edges` + `find_in_edge` → `has_find_in_edge_v` is true. +- Stub graph with `in_edges` + `contains_in_edge` → `has_contains_in_edge_v` is true. + +#### 2.5 Register tests in CMakeLists + +Add to `tests/adj_list/CMakeLists.txt`: + +```cmake + cpo/test_find_in_edge_cpo.cpp + cpo/test_contains_in_edge_cpo.cpp + traits/test_incoming_edge_traits.cpp +``` + +#### 2.6 Build and run full test suite + +### Merge gate + +- [ ] Full test suite passes. +- [ ] All `find_in_edge` overloads work (member, ADL, default). +- [ ] All `contains_in_edge` overloads work. +- [ ] Traits correctly detect presence/absence of incoming-edge CPOs. + +--- + +## Phase 3 — Concepts + namespace re-exports + +**Goal:** Define `in_vertex_edge_range`, `bidirectional_adjacency_list`, +`index_bidirectional_adjacency_list` concepts; update `graph.hpp` with all +re-exports for Phases 1-3. + +**Why third:** Concepts depend on the CPOs and type aliases from Phases 1-2. +Re-exports should be done once after the complete CPO surface exists. + +### Files to modify + +| File | Action | +|---|---| +| `include/graph/adj_list/adjacency_list_concepts.hpp` | Add 3 concepts | +| `include/graph/graph.hpp` | Add re-exports for all new CPOs, aliases, traits, concepts | +| `tests/adj_list/CMakeLists.txt` | Register new test file | + +### Files to create + +| File | Content | +|---|---| +| `tests/adj_list/concepts/test_bidirectional_concepts.cpp` | Concept tests | + +### Steps + +#### 3.1 Add concepts (adjacency_list_concepts.hpp) + +After the `index_adjacency_list` concept: + +```cpp +template +concept in_vertex_edge_range = std::ranges::forward_range; + +template +concept bidirectional_adjacency_list = + adjacency_list && + requires(G& g, vertex_t u, in_edge_t ie) { + { in_edges(g, u) } -> in_vertex_edge_range; + { source_id(g, ie) } -> std::convertible_to>; + }; + +template +concept index_bidirectional_adjacency_list = + bidirectional_adjacency_list && index_vertex_range; +``` + +#### 3.2 Update graph.hpp re-exports + +In the `namespace graph { ... }` using-declaration block, add: + +```cpp +// Incoming-edge CPOs +using adj_list::in_edges; +using adj_list::in_degree; +using adj_list::find_in_edge; +using adj_list::contains_in_edge; + +// Outgoing aliases +using adj_list::out_edges; +using adj_list::out_degree; +using adj_list::find_out_edge; + +// Incoming-edge type aliases +using adj_list::in_vertex_edge_range_t; +using adj_list::in_vertex_edge_iterator_t; +using adj_list::in_edge_t; + +// Outgoing type aliases +using adj_list::out_vertex_edge_range_t; +using adj_list::out_vertex_edge_iterator_t; +using adj_list::out_edge_t; + +// Incoming-edge concepts +using adj_list::in_vertex_edge_range; +using adj_list::bidirectional_adjacency_list; +using adj_list::index_bidirectional_adjacency_list; + +// Incoming-edge traits +using adj_list::has_in_degree; +using adj_list::has_in_degree_v; +using adj_list::has_find_in_edge; +using adj_list::has_find_in_edge_v; +using adj_list::has_contains_in_edge; +using adj_list::has_contains_in_edge_v; +``` + +#### 3.3 Create test file `tests/adj_list/concepts/test_bidirectional_concepts.cpp` + +1. **Stub bidirectional graph** — a struct with both `edges()` and `in_edges()` + ADL friends. `static_assert(bidirectional_adjacency_list)`. + +2. **Outgoing-only graph** — a `vector>`. + `static_assert(!bidirectional_adjacency_list)`. + +3. **Index variant** — stub graph with random-access vertices. + `static_assert(index_bidirectional_adjacency_list)`. + +4. **Mixed-type concept** — stub where `in_edge_t` is just `int` + (lightweight back-pointer) but `source_id(g, ie)` works. + `static_assert(bidirectional_adjacency_list)`. + +5. **Re-export test** — verify `graph::bidirectional_adjacency_list` is + accessible (compile-time only). + +#### 3.4 Register test and build + +### Merge gate + +- [ ] Full test suite passes. +- [ ] `bidirectional_adjacency_list` satisfied by stub bidirectional graph. +- [ ] `bidirectional_adjacency_list` not satisfied by `vector>`. +- [ ] Mixed-type (`in_edge_t != edge_t`) graph satisfies concept. +- [ ] All re-exports compile via `#include `. + +--- + +## Phase 4 — `undirected_adjacency_list` incoming-edge support + +**Goal:** Add `in_edges()` and `in_degree()` ADL friends to +`undirected_adjacency_list` that return the same ranges as `edges()` and +`degree()`, making undirected graphs model `bidirectional_adjacency_list` +at zero cost. + +**Why fourth:** This is the simplest container change (just forwarding to +existing functions) and provides a real container for integration testing. + +### Files to modify + +| File | Action | +|---|---| +| `include/graph/container/undirected_adjacency_list.hpp` | Add `in_edges`, `in_degree` friends | +| `tests/container/CMakeLists.txt` | Register new test file | + +### Files to create + +| File | Content | +|---|---| +| `tests/container/test_undirected_bidirectional.cpp` | Integration tests | + +### Steps + +#### 4.1 Add ADL friends (undirected_adjacency_list.hpp) + +In `base_undirected_adjacency_list`, immediately after the existing `edges()` +friend functions (around line 1030), add: + +```cpp + // in_edges(g, u) — for undirected graphs, same as edges(g, u) + template + requires adj_list::vertex_descriptor_type + friend constexpr auto in_edges(graph_type& g, const U& u) noexcept { + auto uid = static_cast(u.vertex_id()); + return g.vertices_[uid].edges(g, uid); + } + template + requires adj_list::vertex_descriptor_type + friend constexpr auto in_edges(const graph_type& g, const U& u) noexcept { + auto uid = static_cast(u.vertex_id()); + return g.vertices_[uid].edges(g, uid); + } +``` + +Also add `in_degree` friends if the class has a `degree()` member/friend. +Otherwise, the `in_degree` CPO's default tier (`size(in_edges(g, u))`) will +handle it automatically. + +#### 4.2 Create test file + +Test using actual `undirected_adjacency_list` instances (e.g., `vov_graph`): + +1. **`in_edges` returns same edges as `edges`** — for each vertex, verify + `in_edges(g, u)` and `edges(g, u)` yield the same edge set. + +2. **`in_degree` equals `degree`** — for each vertex. + +3. **Concept satisfaction** — `static_assert(bidirectional_adjacency_list)`. + +4. **`find_in_edge` works** — since `in_edges` returns the same range. + +5. **`contains_in_edge` works** — mirrors `contains_edge`. + +6. Run against multiple trait types (at least `vov` and `vol`). + +#### 4.3 Register test and build + +### Merge gate + +- [ ] Full test suite passes. +- [ ] `undirected_adjacency_list` models `bidirectional_adjacency_list`. +- [ ] `in_edges(g, u)` and `edges(g, u)` produce identical results. + +--- + +## Phase 5 — `in_incidence` and `in_neighbors` views + +**Goal:** Create the incoming-edge view files, their factory functions, +and tests. + +**Why fifth:** Views consume the CPOs from Phases 1-3 and can now also be +tested against the undirected container from Phase 4. + +### Files to modify + +| File | Action | +|---|---| +| `tests/views/CMakeLists.txt` | Register new test files | + +### Files to create + +| File | Content | +|---|---| +| `include/graph/views/in_incidence.hpp` | `in_incidence_view`, `basic_in_incidence_view`, factory functions | +| `include/graph/views/in_neighbors.hpp` | `in_neighbors_view`, `basic_in_neighbors_view`, factory functions | +| `tests/views/test_in_incidence.cpp` | View tests | +| `tests/views/test_in_neighbors.cpp` | View tests | + +### Steps + +#### 5.1 Create `include/graph/views/in_incidence.hpp` + +Copy `incidence.hpp` and make these changes: + +- Rename all classes: `incidence_view` → `in_incidence_view`, + `basic_incidence_view` → `basic_in_incidence_view`. +- Change edge iteration from `edges(g, u)` to `in_edges(g, u)`. +- Change neighbor ID extraction from `target_id(g, e)` to `source_id(g, e)`. +- Change `edge_t` references to `in_edge_t`. +- Change concept constraint from `adjacency_list` to `bidirectional_adjacency_list`. +- Update all doxygen comments. +- Update the `edge_info` yield: `{sid, uv}` / `{sid, uv, val}` / `{sid}` / `{sid, val}`. + +#### 5.2 Create `include/graph/views/in_neighbors.hpp` + +Copy `neighbors.hpp` and apply the same transformation: + +- Rename: `neighbors_view` → `in_neighbors_view`. +- Iterate `in_edges` instead of `edges`. +- Extract `source_id` instead of `target_id`. +- Retrieve source vertex via `source(g, e)` instead of `target(g, e)`. +- Constrain with `bidirectional_adjacency_list`. + +#### 5.3 Add outgoing view aliases + +At the bottom of `in_incidence.hpp`: + +```cpp +namespace graph::views { + inline constexpr auto& out_incidence = incidence; + inline constexpr auto& basic_out_incidence = basic_incidence; +} +``` + +At the bottom of `in_neighbors.hpp`: + +```cpp +namespace graph::views { + inline constexpr auto& out_neighbors = neighbors; + inline constexpr auto& basic_out_neighbors = basic_neighbors; +} +``` + +#### 5.4 Update `graph.hpp` + +Add includes: + +```cpp +#include +#include +``` + +#### 5.5 Create test files + +**`test_in_incidence.cpp`** — mirror `test_incidence.cpp`: +- Use undirected_adjacency_list (from Phase 4) as the bidirectional graph. +- Verify `in_incidence(g, u)` yields `{source_id, edge}` tuples. +- Verify `in_incidence(g, u, evf)` yields `{source_id, edge, value}`. +- Verify `basic_in_incidence(g, uid)` yields `{source_id}`. +- Verify factory function overloads (descriptor and ID). +- Verify `out_incidence` alias is the same as `incidence`. + +**`test_in_neighbors.cpp`** — mirror `test_neighbors.cpp`: +- Verify `in_neighbors(g, u)` yields `{source_id, vertex}` tuples. +- Verify `basic_in_neighbors(g, uid)` yields `{source_id}`. +- Verify `out_neighbors` alias. + +#### 5.6 Register tests and build + +### Merge gate + +- [ ] Full test suite passes. +- [ ] `in_incidence_view` iterates incoming edges, yields `source_id`. +- [ ] `in_neighbors_view` iterates source vertices. +- [ ] All factory function overloads work. +- [ ] Outgoing aliases are identity references. + +--- + +## Phase 6 — Pipe-syntax adaptors + +**Goal:** Add pipe-syntax closures for `in_incidence`, `in_neighbors`, +`basic_in_incidence`, `basic_in_neighbors`, and their `out_` aliases +to `adaptors.hpp`. + +**Why sixth:** Adaptors depend on the view headers from Phase 5. + +### Files to modify + +| File | Action | +|---|---| +| `include/graph/views/adaptors.hpp` | Add adaptor closures and factory fns | +| `tests/views/test_adaptors.cpp` | Add pipe-syntax tests for new views | + +### Steps + +#### 6.1 Add adaptor closures (adaptors.hpp) + +For each new view, follow the existing pattern: + +1. `in_incidence_adaptor_closure` — holds `uid` and optional `evf`. + `operator|(G&& g, closure)` calls `graph::views::in_incidence(g, uid)` or + `graph::views::in_incidence(g, uid, evf)`. + +2. `in_incidence_adaptor_fn` — has `operator()(uid)`, `operator()(uid, evf)`, + `operator()(g, uid)`, `operator()(g, uid, evf)`. + +3. Same for `basic_in_incidence`, `in_neighbors`, `basic_in_neighbors`. + +4. Outgoing aliases: + ```cpp + inline constexpr auto& out_incidence = incidence; + inline constexpr auto& basic_out_incidence = basic_incidence; + inline constexpr auto& out_neighbors = neighbors; + inline constexpr auto& basic_out_neighbors = basic_neighbors; + ``` + +Add to the adaptor namespace block at the bottom: + +```cpp +inline constexpr in_incidence_adaptor_fn in_incidence{}; +inline constexpr basic_in_incidence_adaptor_fn basic_in_incidence{}; +inline constexpr in_neighbors_adaptor_fn in_neighbors{}; +inline constexpr basic_in_neighbors_adaptor_fn basic_in_neighbors{}; +``` + +#### 6.2 Add tests to `test_adaptors.cpp` + +Append new test cases: + +```cpp +TEST_CASE("pipe: g | in_incidence(uid)", "[adaptors][in_incidence]") { ... } +TEST_CASE("pipe: g | in_incidence(uid, evf)", "[adaptors][in_incidence]") { ... } +TEST_CASE("pipe: g | basic_in_incidence(uid)", "[adaptors][basic_in_incidence]") { ... } +TEST_CASE("pipe: g | in_neighbors(uid)", "[adaptors][in_neighbors]") { ... } +TEST_CASE("pipe: g | basic_in_neighbors(uid)", "[adaptors][in_neighbors]") { ... } +TEST_CASE("pipe: out_ aliases forward", "[adaptors][aliases]") { ... } +``` + +#### 6.3 Build and run full test suite + +### Merge gate + +- [ ] Full test suite passes. +- [ ] `g | in_incidence(uid)` produces same results as `in_incidence(g, uid)`. +- [ ] All 8 pipe expressions from design doc §8.4 work. + +--- + +## Phase 7 — BFS/DFS/topological-sort `EdgeAccessor` parameterization + +**Goal:** Add an `EdgeAccessor` template parameter to all 6 traversal view +classes and their factory functions; define `out_edges_accessor` and +`in_edges_accessor` functors. + +**Why seventh:** This is a cross-cutting change to 3 existing view headers. +It must not break any existing call sites (the default accessor preserves +current behavior). Having the undirected bidirectional container (Phase 4) +enables testing reverse traversal on a real graph. + +### Files to modify + +| File | Action | +|---|---| +| `include/graph/views/bfs.hpp` | Add `EdgeAccessor` param to `vertices_bfs_view`, `edges_bfs_view`; replace 5 hardcoded `adj_list::edges()` calls | +| `include/graph/views/dfs.hpp` | Same for `vertices_dfs_view`, `edges_dfs_view`; replace 5 calls | +| `include/graph/views/topological_sort.hpp` | Same for `vertices_topological_sort_view`, `edges_topological_sort_view`; replace 7 calls | +| `tests/views/CMakeLists.txt` | Register new test file | + +### Files to create + +| File | Content | +|---|---| +| `include/graph/views/edge_accessor.hpp` | `out_edges_accessor`, `in_edges_accessor` definitions | +| `tests/views/test_reverse_traversal.cpp` | Reverse BFS/DFS tests | + +### Steps + +#### 7.1 Create `edge_accessor.hpp` + +```cpp +#pragma once +#include + +namespace graph::views { + +struct out_edges_accessor { + template + constexpr auto operator()(G& g, adj_list::vertex_t u) const { + return adj_list::edges(g, u); + } +}; + +struct in_edges_accessor { + template + constexpr auto operator()(G& g, adj_list::vertex_t u) const { + return adj_list::in_edges(g, u); + } +}; + +} // namespace graph::views +``` + +#### 7.2 Parameterize BFS views (bfs.hpp) + +For each of `vertices_bfs_view` and `edges_bfs_view`: + +1. Add `class EdgeAccessor = out_edges_accessor` as the last template parameter + (before `Alloc` if present, or after the value function type). + +2. Store `[[no_unique_address]] EdgeAccessor edge_accessor_;` member. + +3. Replace every `adj_list::edges(g, v)` or `adj_list::edges(*g_, v)` with + `edge_accessor_(g, v)` or `edge_accessor_(*g_, v)`. + +4. Add factory function overloads accepting `EdgeAccessor`: + ```cpp + vertices_bfs(g, seed, vvf, accessor) + edges_bfs(g, seed, evf, accessor) + ``` + +5. Existing signatures remain unchanged (default `EdgeAccessor`). + +#### 7.3 Parameterize DFS views (dfs.hpp) + +Same transformation: 5 `adj_list::edges()` calls → `edge_accessor_()`. + +#### 7.4 Parameterize topological sort views (topological_sort.hpp) + +Same transformation: 7 `adj_list::edges()` calls → `edge_accessor_()`. + +#### 7.5 Create `test_reverse_traversal.cpp` + +Using an undirected_adjacency_list (where `in_edges == edges`): + +1. **Forward BFS** — `vertices_bfs(g, seed)` produces expected order. +2. **Reverse BFS** — `vertices_bfs(g, seed, void_fn, in_edges_accessor{})` + produces expected order (same as forward for undirected). +3. **Forward DFS** vs **Reverse DFS** — same pattern. +4. **Source-compatibility** — existing call sites `vertices_bfs(g, seed)` and + `vertices_bfs(g, seed, vvf)` still compile unchanged. + +#### 7.6 Build and run full test suite + +### Merge gate + +- [ ] Full test suite passes (zero regressions in existing BFS/DFS/topo tests). +- [ ] Reverse BFS/DFS with `in_edges_accessor` produces correct traversal. +- [ ] Existing factory signatures compile without changes. + +--- + +## Phase 8 — `dynamic_graph` bidirectional support + +**Goal:** Add `bool Bidirectional = false` template parameter to +`dynamic_graph_base`, maintain reverse adjacency lists when enabled, +provide `in_edges()` ADL friend. + +**Why eighth:** This is the most complex container change. All prerequisite +infrastructure (CPOs, concepts, views) is already proven by earlier phases. + +### Files to modify + +| File | Action | +|---|---| +| `include/graph/container/dynamic_graph.hpp` | Add `Bidirectional` param, reverse storage, in_edges friend, mutator updates | +| `tests/container/CMakeLists.txt` | Register new test file | + +### Files to create + +| File | Content | +|---|---| +| `tests/container/test_dynamic_graph_bidirectional.cpp` | Comprehensive tests | + +### Steps + +#### 8.1 Add `Bidirectional` template parameter + +Change `dynamic_graph_base` signature: + +```cpp +template +class dynamic_graph_base; +``` + +Propagate through `dynamic_graph` and all using-aliases. + +#### 8.2 Add reverse edge storage (conditional) + +When `Bidirectional = true`, each vertex needs a second edge container. +Use `if constexpr` or a conditional base class: + +```cpp +// In the vertex type: +if constexpr (Bidirectional) { + edges_type in_edges_; // reverse adjacency container +} +``` + +Add `in_edges()` accessor to the vertex type (conditional on `Bidirectional`). + +#### 8.3 Update mutators + +Update `create_edge(u, v, ...)`: +```cpp +if constexpr (Bidirectional) { + // Insert reverse entry in v.in_edges_ with source = u +} +``` + +Similarly update `erase_edge`, `erase_vertex`, `clear_vertex`, `clear`, +copy constructor, move constructor, and swap. Each must maintain +forward↔reverse consistency. + +#### 8.4 Add `in_edges()` ADL friend + +```cpp +template +requires adj_list::vertex_descriptor_type +friend constexpr auto in_edges(graph_type& g, const U& u) noexcept + requires (Bidirectional) +{ + auto uid = static_cast(u.vertex_id()); + return g.vertices_[uid].in_edges(g, uid); +} +``` + +#### 8.5 Create comprehensive test file + +1. **Basic bidirectional graph construction** — create edges, verify `in_edges` + returns expected source vertices. + +2. **`in_degree` matches expected** — for each vertex. + +3. **Concept satisfaction** — `static_assert(bidirectional_adjacency_list)`. + +4. **Mutator invariants** — after `create_edge(u, v)`: + - `contains_edge(g, u, v) == true` + - `contains_in_edge(g, v, u) == true` + + After `erase_edge(...)`: + - Both forward and reverse entries removed. + +5. **`erase_vertex`** — removes all incident edges from both directions. + +6. **`clear_vertex`** — removes all edges for vertex in both directions. + +7. **Copy/move** — verify reverse adjacency is preserved. + +8. **Non-bidirectional unchanged** — `Bidirectional=false` graph compiles and + works identically to before. `static_assert(!bidirectional_adjacency_list)`. + +9. **Views integration** — `in_incidence(g, u)` and `in_neighbors(g, u)` work + on bidirectional dynamic_graph. + +10. Test with at least 2 trait types (e.g., `vov` and `vol`). + +#### 8.6 Register test and build + +### Merge gate + +- [ ] Full test suite passes. +- [ ] All 6 mutator invariants from design doc §10.5 are tested and pass. +- [ ] Non-bidirectional mode has identical behavior (no regressions). +- [ ] `bidirectional_adjacency_list` concept satisfied. + +--- + +## Phase 9 — Algorithm updates (Kosaraju + transpose_view) + +**Goal:** Optimize Kosaraju's SCC to use `in_edges()` when available; +add `transpose_view` as a zero-cost adaptor. + +**Why ninth:** Algorithms depend on container support (Phase 8) and the +edge accessor infrastructure (Phase 7). + +### Files to modify + +| File | Action | +|---|---| +| `include/graph/algorithm/connected_components.hpp` | Add `if constexpr (bidirectional_adjacency_list)` branch using `in_edges_accessor` | +| `tests/algorithms/CMakeLists.txt` | Register new test file | + +### Files to create + +| File | Content | +|---|---| +| `include/graph/views/transpose.hpp` | `transpose_view` wrapper | +| `tests/algorithms/test_scc_bidirectional.cpp` | SCC tests with bidirectional graph | +| `tests/views/test_transpose.cpp` | Transpose view tests | + +### Steps + +#### 9.1 Add `transpose_view` + +A lightweight wrapper that swaps `edges()` ↔ `in_edges()`: + +```cpp +template +class transpose_view { + G* g_; +public: + // ADL friends: edges(tv, u) → in_edges(*g_, u) + // in_edges(tv, u) → edges(*g_, u) + // Forward all other CPOs to underlying graph +}; +``` + +#### 9.2 Optimize Kosaraju's SCC + +Add a compile-time branch: +```cpp +if constexpr (bidirectional_adjacency_list) { + // Use in_edges_accessor for second DFS pass +} else { + // Keep existing edge-rebuild approach +} +``` + +#### 9.3 Create tests and build + +### Merge gate + +- [ ] Full test suite passes. +- [ ] SCC produces correct results on bidirectional graph. +- [ ] `transpose_view` swaps edge directions correctly. + +--- + +## Phase 10 — Documentation + +**Goal:** Update all documentation per design doc §13. + +### Files to modify + +Per the table in design doc §13.1 (11 files), plus: + +| File | Action | +|---|---| +| `docs/index.md` | Add "Bidirectional edge access" feature | +| `docs/getting-started.md` | Add incoming edges section | +| `docs/user-guide/views.md` | Add `in_incidence`, `in_neighbors` | +| `docs/user-guide/algorithms.md` | Note bidirectional benefits | +| `docs/user-guide/containers.md` | Document `Bidirectional` parameter | +| `docs/reference/concepts.md` | Add `bidirectional_adjacency_list` | +| `docs/reference/cpo-reference.md` | Add all incoming CPOs | +| `docs/reference/type-aliases.md` | Add `in_edge_t` etc. | +| `docs/reference/adjacency-list-interface.md` | Add incoming edge section | +| `docs/contributing/cpo-implementation.md` | Add `in_edges` example | +| `README.md` | Update feature highlights | +| `CHANGELOG.md` | Add entry | + +### Files to create + +| File | Content | +|---|---| +| `docs/user-guide/bidirectional-access.md` | Tutorial guide | + +### Steps + +1. Update each file per the table. +2. Create the tutorial guide with examples using `in_edges`, `in_incidence`, + reverse BFS, and bidirectional `dynamic_graph`. +3. Add CHANGELOG entry. +4. Build docs and verify no broken links. + +### Merge gate + +- [ ] All docs updated. +- [ ] Tutorial compiles (code examples are tested). +- [ ] No broken internal links. + +--- + +## Phase summary + +| Phase | Title | New files | Modified files | Key deliverable | +|---|---|---|---|---| +| 1 | `in_edges`/`in_degree` CPOs + aliases | 1 test | 2 | Core CPOs + type aliases | +| 2 | `find_in_edge`/`contains_in_edge` + traits | 3 tests | 3 | Complete CPO surface | +| 3 | Concepts + re-exports | 1 test | 2 | `bidirectional_adjacency_list` concept | +| 4 | Undirected container support | 1 test | 1 | First real bidirectional container | +| 5 | `in_incidence`/`in_neighbors` views | 2 headers + 2 tests | 1 | Incoming-edge views | +| 6 | Pipe-syntax adaptors | — | 2 | `g \| in_incidence(uid)` | +| 7 | BFS/DFS/topo EdgeAccessor | 1 header + 1 test | 3 | Reverse traversal | +| 8 | `dynamic_graph` bidirectional | 1 test | 1 | Directed bidirectional container | +| 9 | Algorithms | 2 headers + 2 tests | 1 | Kosaraju + transpose | +| 10 | Documentation | 1 guide | 12 | Complete docs | + +**Total estimated effort:** 11-15 days (same as design doc estimate) + +--- + +## Safety principles + +1. **Each phase adds only; nothing is removed or renamed.** + Existing tests pass between every phase. + +2. **Default template arguments preserve source compatibility.** + `EdgeAccessor = out_edges_accessor` means no existing call site changes. + +3. **Stub graphs in early phases isolate CPO testing from container code.** + Phases 1-3 don't touch any container — they use lightweight test stubs. + +4. **The simplest container change comes first (Phase 4).** + Undirected just forwards to the existing `edges()` function. + +5. **The most complex change (Phase 8) comes last in the implementation + sequence**, after all the infrastructure it depends on is proven. + +6. **Each phase has an explicit merge gate** — a checklist of conditions + that must pass before proceeding.