diff --git a/.vscode/settings.json b/.vscode/settings.json index d1d6d85..129b157 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,8 +1,8 @@ -{ - "cmake.configureOnOpen": false, - "cmake.preferredGenerators": [ - "Ninja" - ], - "cmake.generator": "Ninja", - "cmake.cmakePath": "cmake" -} +{ + "cmake.configureOnOpen": false, + "cmake.preferredGenerators": [ + "Ninja" + ], + "cmake.generator": "Ninja", + "cmake.cmakePath": "cmake" +} diff --git a/agents/algorithm_doc_guide.md b/agents/algorithm_doc_guide.md index 6de8a3c..11d12b5 100644 --- a/agents/algorithm_doc_guide.md +++ b/agents/algorithm_doc_guide.md @@ -46,7 +46,10 @@ are included only when the algorithm has the relevant feature. - [Examples](#examples) - [Complexity](#complexity) - [Preconditions](#preconditions) -- [Notes](#notes) ← (if applicable) +- [Postconditions](#postconditions) ← (if applicable) +- [Throws](#throws) ← (if applicable) +- [Error Conditions](#error-conditions) ← (if applicable) +- [Remarks](#remarks) ← (if applicable) - [See Also](#see-also) ``` @@ -56,9 +59,10 @@ are included only when the algorithm has the relevant feature. Components has 3 algorithms): Replace the single Signature/Parameters sections with per-algorithm subsections under an `## Algorithms` or named `## Kruskal's Algorithm` / `## Prim's Algorithm` pattern. -- **Visitor Events**: Include this section only for algorithms that accept a - visitor (currently: Dijkstra, Bellman-Ford, BFS, DFS). -- **Notes**: Include when there are important behavioral details that don't +- **Visitor Events**: Include this section for any algorithm whose signature + accepts a `Visitor` parameter. Check the actual header; do not rely on a + fixed list of algorithm names. +- **Remarks**: Include when there are important behavioral details that don't fit in Preconditions (e.g., self-loop handling differences, in-place modification warnings, convergence properties). @@ -98,8 +102,11 @@ are included only when the algorithm has the relevant feature. - Show all public overloads in a single fenced C++ code block. - Include `constexpr`, `[[nodiscard]]`, and template constraints if present in the actual header. -- After the code block, document the **return type** in bold if non-void - (e.g., "**Returns** `std::pair` — total weight and component count."). +- After the code block, document the **return type and return value semantics** + in bold if non-void: state the type, its value category, and what it + represents, including any sentinel values (e.g., "**Returns** + `std::pair` — the total MST edge weight and the number of edges + included. Returns `{EV{}, 0}` if the graph has no edges."). - Use "Signature" (singular) if there is only one overload; "Signatures" (plural) if there are multiple. @@ -110,6 +117,9 @@ are included only when the algorithm has the relevant feature. - Note default values inline (e.g., "Default: `std::less<>{}`"). - For output parameters, state the sizing requirement (e.g., "Random-access range sized to `num_vertices(g)`"). +- For template parameters, document the concept requirements (e.g., + "`G` must satisfy `index_adjacency_list`; `WF` must be callable as + `WF(edge_reference_t) -> EV`"). ### Visitor Events (if applicable) @@ -119,7 +129,10 @@ are included only when the algorithm has the relevant feature. ### Examples -**Quantity**: At minimum 5 examples per page. More is acceptable and +**Quantity**: At minimum 5 examples per page. This minimum ensures coverage +of: the basic happy path, at least one additional overload or mode, a +topology-specific case that illuminates algorithm properties, an advanced +feature, and one edge case or real-world scenario. More is acceptable and encouraged when the algorithm has multiple overloads, modes, or edge cases. **Progressive complexity**: Order examples from simplest to most advanced: @@ -178,6 +191,18 @@ One-sentence explanation of what this example demonstrates and *why* it matters. ### Preconditions +This section covers **three kinds of requirements** that the C++ standard +distinguishes separately, presented here as a unified list for users: + +- *Constraints* — template conditions enforced silently at overload resolution + (the function is simply not viable if unsatisfied). +- *Mandates* — conditions whose violation makes the program ill-formed + (`static_assert` or similar; a compiler diagnostic is issued). +- *Preconditions* — runtime conditions whose violation causes undefined behavior. + +Document all three as a single bullet list; users need to know what must hold +before calling the function, regardless of which category applies. + - Bullet list of every requirement the caller must satisfy. - Always include the `index_adjacency_list` requirement. - For undirected graph algorithms, state: "For undirected graphs, **both @@ -186,7 +211,50 @@ One-sentence explanation of what this example demonstrates and *why* it matters. - Mention self-loop behavior, valid vertex ID ranges, and sizing requirements for output containers. -### Notes (if applicable) +### Postconditions (if applicable) + +Include this section when the algorithm writes results into caller-supplied +output ranges or produces a return value whose state needs precise +specification beyond a single sentence in Signatures. + +- Bullet list of conditions established by the function upon successful return. +- Focus on output ranges: state what every element contains after the call + (e.g., "`distances[v]` holds the shortest-path distance from `source` to `v`, + or `numeric_limits::max()` if `v` is unreachable."). +- State the postcondition for the return value here if it is too detailed for + the Signatures section. +- Omit this section if postconditions are fully captured by the Returns + description and output-parameter notes in Parameters. + +### Throws (if applicable) + +Include this section when the algorithm can throw exceptions. + +- Bullet list of exception types and the conditions that cause each one. +- Always state the exception guarantee: **strong** (no observable effects on + failure), **basic** (output is in a valid but unspecified state), or **none**. +- If the algorithm is `noexcept` or only propagates exceptions from + user-supplied callables, state that explicitly (e.g., "Does not throw + directly; exceptions from `wf` or `visitor` callbacks are propagated + unchanged. No partial results are written (strong guarantee)."). + +### Error Conditions (if applicable) + +Include this section only for algorithms that report errors via +`std::error_code` or `std::expected` rather than (or in addition to) +exceptions. + +- Bullet list of `std::errc` constants (or domain-specific codes) the + algorithm can set, with a brief explanation of each condition. +- Note whether errors are reported via a return value or an output parameter. +- Omit this section entirely if the algorithm never reports errors by error + code. + +### Remarks (if applicable) + +This section uses the term **Remarks** from the C++ standard function-semantics +vocabulary: additional semantic constraints or behavioral details that don't +belong in Preconditions, Postconditions, or Throws. - Bullet list of behavioral details, caveats, or design decisions. - Good candidates: in-place modification warnings, self-loop counting behavior @@ -246,8 +314,11 @@ When creating a documentation page for a new algorithm: - Template constraints and concepts - `constexpr`, `[[nodiscard]]`, and other attributes - Default parameter values - - Return types + - Return types and return value semantics (including sentinel values) - Visitor event names (if any) + - `noexcept` specifiers and any documented exception types + - Error codes reported via `std::error_code` or `std::expected` (if any) + - Postconditions on output ranges (from comments or contracts, if present) 2. **Read the test file** (`tests/algorithms/test_xxx.cpp`) to identify: - Graph topologies tested (star, path, cycle, complete, disconnected, etc.) @@ -273,9 +344,15 @@ When creating a documentation page for a new algorithm: - The `index_adjacency_list` requirement is stated in Overview and Preconditions. - All cross-links resolve (test by checking file paths). - - At least 5 examples are provided. + - At least 5 examples are provided, covering each overload and at least one + edge case. - Examples progress from simple to advanced. - Expected output values are shown in comments for every example. + - Throws section is present if the algorithm can throw, and states the + exception guarantee; absent (or noted as `noexcept`) otherwise. + - Postconditions section is present if output-range postconditions are not + fully covered by the Parameters and Returns descriptions. + - Error Conditions section is present if the algorithm uses `std::error_code`. --- @@ -284,16 +361,16 @@ When creating a documentation page for a new algorithm: Use these as models. The pages vary slightly based on algorithm features but all follow the structure above. -| Page | Notable features to model | -|------|---------------------------| -| `dijkstra.md` | Multiple overloads, visitor events, `constexpr` signatures, `_id` variant examples, error handling note | -| `bellman_ford.md` | `[[nodiscard]]` attribute, `find_negative_cycle` helper, unique visitor events (`on_edge_minimized`) | -| `dfs.md` | Richest visitor events (9), single-source limitation note, subtree computation example | -| `connected_components.md` | Multi-algorithm structure (3 sub-algorithms), `compress()` helper, per-algorithm selection guidance | -| `mst.md` | Two distinct algorithms (Kruskal + Prim) with separate signatures, `inplace_kruskal` modification warning, cross-validation example | -| `label_propagation.md` | RNG parameter, convergence notes, self-loop counting behavior, reproducibility example | -| `triangle_count.md` | Additional concept requirement (`ordered_vertex_edges`), pre-sorting workaround example | -| `mis.md` | Seed sensitivity, maximal vs. maximum distinction, self-loop exclusion | +| Page | Notable features to model | +| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | +| `dijkstra.md` | Multiple overloads, visitor events, `constexpr` signatures, `_id` variant examples, error handling note | +| `bellman_ford.md` | `[[nodiscard]]` attribute, `find_negative_cycle` helper, unique visitor events (`on_edge_minimized`) | +| `dfs.md` | Richest visitor events (9), single-source limitation note, subtree computation example | +| `connected_components.md` | Multi-algorithm structure (3 sub-algorithms), `compress()` helper, per-algorithm selection guidance | +| `mst.md` | Two distinct algorithms (Kruskal + Prim) with separate signatures, `inplace_kruskal` modification warning, cross-validation example | +| `label_propagation.md` | RNG parameter, convergence notes, self-loop counting behavior, reproducibility example | +| `triangle_count.md` | Additional concept requirement (`ordered_vertex_edges`), pre-sorting workaround example | +| `mis.md` | Seed sensitivity, maximal vs. maximum distinction, self-loop exclusion | --- @@ -315,3 +392,10 @@ all follow the structure above. assume based on other algorithms. - **Don't forget to update the catalog** — the landing page at `docs/user-guide/algorithms.md` must list every algorithm with complexity. +- **Don't omit Throws** — if an algorithm can throw, document it with the + exception guarantee; if it is `noexcept` or only propagates user-callable + exceptions, state that explicitly. +- **Don't confuse Preconditions and Postconditions** — Preconditions are what + must hold *before* the call; Postconditions describe the output state *after*. +- **Don't omit Error Conditions** — if the algorithm returns `std::error_code` + or uses `std::expected`, document the error codes in their own section. diff --git a/agents/doc_revision_plan.md b/agents/doc_revision_plan.md new file mode 100644 index 0000000..bc93e28 --- /dev/null +++ b/agents/doc_revision_plan.md @@ -0,0 +1,534 @@ +# Algorithm Documentation Revision Plan + +This plan describes the changes required to bring all algorithm documentation pages +(`docs/user-guide/algorithms/*.md`) and their corresponding header files +(`include/graph/algorithm/*.hpp`) into full compliance with `algorithm_doc_guide.md`. +The revised pages will serve as the basis for the Standards proposal (P1709 / D3128). + +--- + +## How to Use This Plan + +Work through the pages in the order listed under **Per-Page Work Items**. +For each page: +1. Read the header file to verify current signatures, throws, postconditions. +2. Apply the **Global Changes** that apply to every page. +3. Apply the **Page-Specific Changes** listed for that page. +4. Verify against the **Checklist** at the end of this document. + +--- + +## Global Changes (Apply to Every Page) + +These changes are required on all 13 algorithm pages regardless of algorithm. + +### G1 — Rename "Notes" → "Remarks" + +Five pages already have a `## Notes` section. Rename it to `## Remarks` to match +the C++ standard vocabulary adopted in `algorithm_doc_guide.md`. + +Affected: `jaccard.md`, `label_propagation.md`, `mis.md`, `mst.md`, `triangle_count.md` + +Also update the TOC anchor in each affected page: +- `- [Notes](#notes)` → `- [Remarks](#remarks)` + +### G2 — Add "Remarks" section to pages that currently lack it + +Eight pages have no Notes/Remarks section. Add one wherever there are behavioral +details worth stating for a Standards reader (see page-specific items below). + +Affected: `articulation_points.md`, `bellman_ford.md`, `bfs.md`, +`biconnected_components.md`, `connected_components.md`, `dfs.md`, `dijkstra.md`, +`topological_sort.md` + +### G3 — Add "Throws" section to every page + +Every algorithm header documents exception behavior. Extract it into a dedicated +`## Throws` section (between Postconditions and Remarks in the section order). +For algorithms that are `noexcept` or only propagate allocator exceptions, +state that explicitly rather than omitting the section. + +See per-page items for exact exception types and guarantees. + +### G4 — Add "Postconditions" section to pages with output ranges + +For every algorithm that writes result values into caller-supplied ranges, add a +`## Postconditions` section (between Preconditions and Throws) that precisely states +what each output range contains after a successful return. + +### G5 — Expand "Returns" descriptions in Signatures + +Where the return type is non-void, the Signatures section must document both the +type and its full value semantics — including what each member means and any +sentinel/special values. See per-page items. + +### G6 — Add template-parameter concept requirements to Parameters tables + +Every Parameters table must document the concept constraints on template parameters +(e.g., "`G` must satisfy `index_adjacency_list`"). Currently these appear only +in the Overview or Preconditions sections. + +### G7 — Update TOC in every page + +Add the new sections to each page's Table of Contents in the correct order: + +``` +- [Postconditions](#postconditions) ← (if applicable) +- [Throws](#throws) +- [Remarks](#remarks) ← (if applicable) +``` + +--- + +## Per-Page Work Items + +### 1. `dijkstra.md` ← `dijkstra_shortest_paths.hpp` + +**Priority: High** — most complex page; canonical model for the Standards proposal. + +#### Sections to add / change + +**Throws** (new section, extract from inline "Error handling" note in Parameters): +- `std::out_of_range` — any source vertex id is outside `[0, num_vertices(g))`. + Basic exception guarantee: no distances or predecessors are modified. +- `std::logic_error` — internal invariant violation (should not arise in + correct usage); indicates a library bug. +- `std::bad_alloc` — priority queue allocation fails. +- Weight function and visitor callback exceptions are propagated unchanged. + +**Postconditions** (new section): +- For every vertex `v` reachable from the source(s): `distances[v]` holds the + shortest-path distance; `predecessors[v]` holds the id of `v`'s predecessor + on the shortest path, or `v` itself if `v` is a source. +- For every unreachable vertex `v`: `distances[v]` is unchanged from its + pre-call value (typically `numeric_limits::max()` after `init_shortest_paths`). +- For `dijkstra_shortest_distances` overloads: only `distances` is guaranteed; + no predecessor information is available. + +**Returns** (update in Signatures): +- All overloads are `void`. Add a note in the Signatures section: "Results are + written into the caller-supplied `distances` and `predecessors` output ranges. + See Postconditions." + +**Remarks** (new — move "Error handling" prose out of Parameters): +- The "Error handling" paragraph currently embedded in Parameters (line 125) + should become the Throws section. Remove it from Parameters. +- Note that `dijkstra_shortest_distances` internally uses a null-predecessor + range, avoiding the cost of maintaining predecessor state. + +**Parameters**: +- Add concept constraints for `G`, `Sources`, `WF`, `Visitor`, `Compare`, + `Combine` template parameters. + +--- + +### 2. `bellman_ford.md` ← `bellman_ford_shortest_paths.hpp` + +**Priority: High** — non-trivial return type; negative-cycle detection. + +#### Sections to add / change + +**Throws** (new section): +- `std::out_of_range` — any source vertex id is outside `[0, num_vertices(g))`. + Basic guarantee. +- `std::bad_alloc` — internal allocation fails. +- Weight function exceptions are propagated unchanged. + +**Postconditions** (new section): +- Returns `nullopt`: `distances[v]` and `predecessors[v]` hold shortest-path + values for all reachable vertices (same guarantees as Dijkstra postconditions). +- Returns `optional> id`: `id` is a vertex on a negative-weight + cycle; the cycle can be recovered by following `predecessors` from `id`. + `distances` contents are unspecified when a negative cycle is detected. + +**Returns** (expand in Signatures): +- `[[nodiscard]] constexpr optional>` — returns `nullopt` if no + negative cycle is detected, or the id of a vertex on a negative-weight cycle. + +**Remarks** (new): +- Weight function must be pure (no side effects, no graph modification). +- The V·E time bound holds regardless of graph connectivity. + +**Parameters**: add concept constraints. + +--- + +### 3. `bfs.md` ← `breadth_first_search.hpp` + +**Priority: Medium** + +#### Sections to add / change + +**Throws** (new section): +- `std::bad_alloc` — visited array or internal queue allocation fails. + Basic guarantee: visited markers and output ranges are in a valid but + unspecified state. +- Visitor callback exceptions are propagated unchanged. + +**Postconditions** (new section): +- Each vertex reachable from the source is visited exactly once. +- Visitor callbacks are invoked in breadth-first order; `on_discover_vertex` + precedes `on_examine_vertex` for the same vertex. + +**Remarks** (new): +- Vertices unreachable from the source are never visited and no callbacks are + invoked for them. +- For multi-source BFS, all sources are enqueued simultaneously at distance 0 + before traversal begins. + +**Parameters**: add concept constraints for `G`, `Visitor`. + +--- + +### 4. `dfs.md` ← `depth_first_search.hpp` + +**Priority: Medium** + +#### Sections to add / change + +**Throws** (new section): +- `std::bad_alloc` — color array or internal stack allocation fails. + Basic guarantee. +- Visitor callback exceptions are propagated unchanged. + +**Postconditions** (new section): +- Every vertex is visited and colored; callbacks follow LIFO (stack-based) + depth-first order. +- `on_finish_vertex` is called for every vertex after all its descendants + have been explored. + +**Remarks** (new): +- The implementation is iterative (stack-based), not recursive. Maximum + nesting depth is bounded by the implementation's stack, not the graph depth. +- Single-source limitation: the single-source overload only visits vertices + reachable from the specified source. + +**Parameters**: add concept constraints. + +--- + +### 5. `connected_components.md` ← `connected_components.hpp` + +**Priority: Medium** — three sub-algorithms (Kosaraju, Union-Find, Afforest). + +#### Sections to add / change + +**Throws** (new section, per sub-algorithm): +- All three: `std::bad_alloc` — internal allocation fails. Basic guarantee. + +**Postconditions** (new section, per sub-algorithm): +- For every vertex `v`: `component[v]` contains a non-negative integer label + identifying its connected component. All vertices in the same component have + the same label. +- Labels are not guaranteed to be contiguous or in any particular order unless + `compress()` is called afterward. +- Kosaraju (directed graph): labels reflect strongly connected components. +- Union-Find / Afforest (undirected): labels reflect weakly connected components. + +**Remarks** (new): +- Call `compress(component)` after the algorithm to normalize labels to the + range `[0, k)` where `k` is the number of components. +- Afforest is designed for very large sparse graphs; it uses sampling to skip + the most expensive union operations. + +**Parameters**: add concept constraints. + +--- + +### 6. `mst.md` ← `mst.hpp` + +**Priority: Medium** — two sub-algorithms (Kruskal + Prim); `inplace_kruskal` warning. + +#### Sections to add / change + +**Rename** `## Notes` → `## Remarks` (Global G1). + +**Throws** (new section, per sub-algorithm): +- Kruskal: `std::bad_alloc`. May propagate exceptions from comparison operators + (recommend `noexcept` comparators). +- Prim: `std::out_of_range` — seed vertex id is outside `[0, num_vertices(g))`. + `std::bad_alloc`. Basic guarantee. + +**Postconditions** (new section, per sub-algorithm): +- Kruskal output range contains the edges of a minimum spanning tree (or forest) + sorted by weight. The range is a subset of the graph's edges. +- Prim `weight[v]` contains the weight of the edge connecting `v` to its MST + parent; `predecessor[v]` is the parent vertex id. `weight[seed] = EV{}`. + +**Returns** (expand): +- Kruskal: iterators/range; state what the output edge descriptor contains. +- Prim: `void`. + +**Remarks**: keep existing content; add note that `inplace_kruskal` modifies +the caller's edge range in-place (sort order is not preserved). + +**Parameters**: add concept constraints for `G`, `WF`, `OutIter`. + +--- + +### 7. `topological_sort.md` ← `topological_sort.hpp` + +**Priority: Medium** — non-void return type (`bool`); three overloads. + +#### Sections to add / change + +**Throws** (new section): +- `std::bad_alloc` — internal stack or color array allocation fails. + Basic guarantee: output range and color array are in a valid but unspecified + state. + +**Postconditions** (new section): +- Returns `true` (no cycle): the output range contains all vertex ids in + topological order — for every directed edge `(u, v)`, `u` appears before `v`. +- Returns `false` (cycle detected): the output range is partially filled and + its contents are unspecified; the cycle itself is not reported. + +**Returns** (expand in Signatures): +- `bool` — `true` if the graph is a DAG and the sort succeeded; `false` if a + cycle was detected. The return value is `[[nodiscard]]`; ignoring it silently + discards cycle-detection information. + +**Remarks** (new): +- Returning `false` rather than throwing on cycle detection is intentional: + cycles are expected conditions in many applications (e.g., iterative + dependency resolution). See header comments for rationale. +- For small graphs, recursive DFS may be slightly faster; the implementation + uses iterative DFS to avoid stack overflow on deep graphs. + +**Parameters**: add concept constraints. + +--- + +### 8. `articulation_points.md` ← `articulation_points.hpp` + +**Priority: Low-Medium** + +#### Sections to add / change + +**Throws** (new section): +- `std::bad_alloc` — internal `visited`, `disc`, `low`, `parent` arrays. + Basic exception safety: output iterator may have received partial results. + +**Postconditions** (new section): +- The output iterator receives every vertex id that is an articulation point + (cut vertex) of the undirected graph exactly once, in no guaranteed order. +- If the graph is biconnected, no vertex ids are written to the output. + +**Remarks** (new): +- An articulation point is a vertex whose removal increases the number of + connected components. +- The algorithm treats the graph as undirected; for directed graphs, results + are undefined. + +**Parameters**: add concept constraints for `G`, `Iter`. + +--- + +### 9. `biconnected_components.md` ← `biconnected_components.hpp` + +**Priority: Low-Medium** + +#### Sections to add / change + +**Throws** (new section): +- `std::bad_alloc` — internal stack or component container allocation fails. + Basic guarantee. + +**Postconditions** (new section): +- `components` is an outer container of edge-lists. Each inner list contains + the edges of one biconnected component. Every edge of the graph belongs to + exactly one component. +- Bridge edges (single-edge biconnected components) appear as singleton inner + lists. + +**Remarks** (new): +- A biconnected component is a maximal subgraph with no articulation points. +- The algorithm treats the graph as undirected. + +**Parameters**: add concept constraints for `G`, `OuterContainer`. + +--- + +### 10. `jaccard.md` ← `jaccard.hpp` + +**Priority: Low-Medium** + +#### Sections to add / change + +**Rename** `## Notes` → `## Remarks` (Global G1). + +**Throws** (new section): +- `std::bad_alloc` — internal neighbor-set allocation fails. Basic guarantee. + +**Postconditions** (new section): +- The output callable `out` is invoked once for every edge `(u, v)` with + `u < v`, receiving the edge reference and the Jaccard coefficient in `[0, 1]`. +- Coefficient is `0` for edges where the endpoint neighborhoods are disjoint; + `1` for edges where endpoints have identical neighborhoods. + +**Returns** (expand in Signatures): +- `void`. Results delivered via the `out` callback. State callback signature. + +**Parameters**: add concept constraints for `G`, `OutOp`. + +--- + +### 11. `label_propagation.md` ← `label_propagation.hpp` + +**Priority: Low-Medium** + +#### Sections to add / change + +**Rename** `## Notes` → `## Remarks` (Global G1). + +**Throws** (new section): +- `std::bad_alloc` — internal label copy allocation fails. Basic guarantee. +- RNG exceptions are propagated unchanged. + +**Postconditions** (new section): +- `labels[v]` contains a community label in `[0, num_vertices(g))` for every + vertex `v`. +- Upon convergence, no vertex has a label different from the majority label + among its neighbors. Convergence is not guaranteed in all cases (see Remarks). + +**Parameters**: add concept constraints for `G`, `RNG`. + +--- + +### 12. `mis.md` ← `mis.hpp` + +**Priority: Low** + +#### Sections to add / change + +**Rename** `## Notes` → `## Remarks` (Global G1). + +**Throws** (new section): +- `std::bad_alloc` — internal allocation fails. Basic guarantee. + +**Postconditions** (new section): +- `labels[v]` is `1` if vertex `v` is in the independent set, `0` otherwise. +- The result is a **maximal** independent set: no non-member vertex can be added + without violating independence. It is not necessarily **maximum** (largest + possible). + +**Parameters**: add concept constraints. + +--- + +### 13. `triangle_count.md` ← `tc.hpp` + +**Priority: Low** + +#### Sections to add / change + +**Rename** `## Notes` → `## Remarks` (Global G1). + +**Throws** (new section): +- Never throws. The algorithm uses only non-throwing arithmetic and iteration. + **Strong exception safety** (effectively `noexcept`). + +**Postconditions** (new section): +- The return value is the exact number of triangles in the graph. Each triangle + `{u, v, w}` is counted once regardless of graph representation. + +**Returns** (expand in Signatures): +- `size_t` (or the algorithm's actual return type) — the total number of + triangles. Equals zero for graphs with fewer than 3 vertices or no cycles. + +**Remarks**: keep existing content (pre-sorting requirement for +`ordered_vertex_edges`). + +**Parameters**: add concept constraints for `G` including the additional +`ordered_vertex_edges` requirement. + +--- + +## Source File (Header) Changes + +The header files contain the canonical documentation for a Standards proposal. +The following header-level changes are needed in parallel with the doc-page updates. + +### H1 — Normalize exception documentation format across all headers + +Current headers use inconsistent formats (`@throws`, `* - May throw`, `**Throws:**`). +Standardize to a single format compatible with the Standards proposal style. +Suggested format in header comments: + +``` +* Throws: +* std::out_of_range if . +* std::bad_alloc if internal allocation fails. +``` + +Headers needing normalization: all 13 (each has a different style). + +### H2 — Add postcondition annotations where missing + +Headers for `dijkstra_shortest_paths.hpp`, `bellman_ford_shortest_paths.hpp`, +`topological_sort.hpp`, and `connected_components.hpp` partially document +postconditions inline. Align them with the postconditions written for the doc pages. + +### H3 — Verify `[[nodiscard]]` placement + +- `bellman_ford_shortest_paths.hpp`: `[[nodiscard]]` ✅ present. +- `topological_sort.hpp`: verify `[[nodiscard]]` is present on all three overloads + that return `bool`. +- `tc.hpp`: verify return-type annotation is present. +- All `void`-returning functions: `[[nodiscard]]` is not applicable; confirm absent. + +### H4 — Add `noexcept` to `triangle_count` (`tc.hpp`) + +The header states the function never throws and uses only non-throwing operations. +Mark the function `noexcept` to make this part of the interface contract. + +--- + +## Revision Order (Recommended) + +Process pages in this order to maximize reuse of patterns established by earlier pages: + +| Order | Page | Reason | +| ----- | --------------------------- | -------------------------------------------------------- | +| 1 | `dijkstra.md` | Canonical model; most complete starting point | +| 2 | `bellman_ford.md` | Similar structure; adds `optional` return | +| 3 | `topological_sort.md` | Non-void `bool` return; establishes pattern | +| 4 | `bfs.md` | Simpler; establishes visitor postcondition pattern | +| 5 | `dfs.md` | Mirror of BFS with richer visitor events | +| 6 | `connected_components.md` | Multi-algorithm; adds postcondition-per-sub-algo pattern | +| 7 | `mst.md` | Multi-algorithm; Throws per sub-algo | +| 8 | `articulation_points.md` | Simpler output-iterator pattern | +| 9 | `biconnected_components.md` | Container-output pattern | +| 10 | `jaccard.md` | Callback-output pattern | +| 11 | `label_propagation.md` | Convergence postconditions | +| 12 | `mis.md` | Simple labeling postcondition | +| 13 | `triangle_count.md` | Simplest: noexcept, scalar return | + +--- + +## Verification Checklist + +After completing each page, verify the following against `algorithm_doc_guide.md`: + +- [ ] TOC lists all sections in the correct order including Postconditions, Throws, + Error Conditions (if applicable), and Remarks (if applicable). +- [ ] Section heading uses "Remarks" (not "Notes"). +- [ ] Signatures: return type and value semantics fully described; sentinel values + stated; `[[nodiscard]]` shown where present in header. +- [ ] Parameters table: template parameter concept constraints documented. +- [ ] Postconditions section present if algorithm writes output ranges; states + exactly what each element contains after a successful return. +- [ ] Throws section present; exception type, triggering condition, and exception + guarantee (strong / basic / noexcept) all stated. +- [ ] Error Conditions section present if algorithm uses `std::error_code` + (currently none; verify this remains true). +- [ ] Remarks section present for any behavioral details, caveats, or design + rationale beyond Preconditions / Postconditions / Throws. +- [ ] At least 5 examples; progress from basic to advanced; expected output in + comments for every example. +- [ ] All content matches the actual header file exactly (signatures, attributes, + visitor event names). +- [ ] `index_adjacency_list` requirement stated in both Overview and + Preconditions. +- [ ] All cross-links resolve. +- [ ] Algorithm catalog (`algorithms.md`) complexity row matches the page. diff --git a/agents/function_semantics.md b/agents/function_semantics.md new file mode 100644 index 0000000..6c6cb5b --- /dev/null +++ b/agents/function_semantics.md @@ -0,0 +1,69 @@ +# Definition of Function Semantics Used in the C++ Standards Specification + +(Copied from section 16.3.2.4 of the C++26 Standard Specification) + +Descriptions of function semantics contain the following elements (as appropriate) (see Note 135) +- **Constraints**: the conditions for the function’s participation in overload resolution (12.2). + [Note 1 : Failure to meet such a condition results in the function’s silent non-viability. —end note] + [Example 1 : An implementation can express such a condition via a constraint-expression (13.5.3). —end + example] +- **Mandates**: the conditions that, if not met, render the program ill-formed. + [Example 2 : An implementation can express such a condition via the constant-expression in a static_assert declaration + (9.1). If the diagnostic is to be emitted only after the function has been selected by overload + resolution, an implementation can express such a condition via a constraint-expression (13.5.3) and also define + the function as deleted. —end example] +- **Constant When**: the conditions that are required for a call to the function to be a constant subexpression. +- **Preconditions**: conditions that the function assumes to hold whenever it is called; violation of any + preconditions results in undefined behavior. + [Example 3 : An implementation can express some such conditions via the use of a contract assertion, such as a + precondition assertion (9.4.1). —end example] +- **Hardened preconditions**: conditions that the function assumes to hold whenever it is called. + - When invoking the function in a hardened implementation, prior to any other observable side + effects of the function, one or more contract assertions whose predicates are as described in the + hardened precondition are evaluated with a checking semantic (6.11.2). If any of these assertions + is evaluated with a non-terminating semantic and the contract-violation handler returns, the + program has undefined behavior. + - When invoking the function in a non-hardened implementation, if any hardened precondition is violated, + the program has undefined behavior. +- **Effects**: the actions performed by the function. +- **Synchronization**: the synchronization operations (6.10.2) applicable to the function. +- **Postconditions**: the conditions (sometimes termed observable results) established by the function. + [Example 4 : An implementation can express some such conditions via the use of a contract assertion, such as a + postcondition assertion (9.4.1). —end example] +- **Result**: for a typename-specifier, a description of the named type; for an expression, a description of the + type and value category of the expression; the expression is an lvalue if the type is an lvalue reference + type, an xvalue if the type is an rvalue reference type, and a prvalue otherwise. +- **Returns**: a description of the value(s) returned by the function. +- **Throws**: any exceptions thrown by the function, and the conditions that would cause the exception. +- **Complexity**: the time and/or space complexity of the function. +- **Remarks**: additional semantic constraints on the function. +- **Error conditions**: the error conditions for error codes reported by the function. + +Whenever the Effects element specifies that the semantics of some function F are Equivalent to some code +sequence, then the various elements are interpreted as follows. If F’s semantics specifies any Constraints +or Mandates elements, then those requirements are logically imposed prior to the equivalent-to semantics. +Next, the semantics of the code sequence are determined by the Constraints, Mandates, Constant When, +Preconditions, Hardened preconditions, Effects, Synchronization, Postconditions, Returns, Throws, Complexity, +Remarks, and Error conditions specified for the function invocations contained in the code sequence. The +value returned from F is specified by F’s Returns element, or if F has no Returns element, a non-void return +from F is specified by the return statements (8.8.4) in the code sequence. If F’s semantics contains a Throws, +Postconditions, or Complexity element, then that supersedes any occurrences of that element in the code +sequence. + +For non-reserved replacement and handler functions, Clause 17 specifies two behaviors for the functions in +question: their required and default behavior. The default behavior describes a function definition provided +by the implementation. The required behavior describes the semantics of a function definition provided by +either the implementation or a C++ program. Where no distinction is explicitly made in the description, the +behavior described is the required behavior. + +If the formulation of a complexity requirement calls for a negative number of operations, the actual requirement +is zero operations.136 + +Complexity requirements specified in the library clauses are upper bounds, and implementations that provide +better complexity guarantees meet the requirements. + +Error conditions specify conditions where a function may fail. The conditions are listed, together with a +suitable explanation, as the enum class errc constants (19.5). + +Note 135) To save space, elements that do not apply to a function are omitted. For example, if a function specifies no preconditions, +there will be no Preconditions: element. diff --git a/include/graph/algorithm/articulation_points.hpp b/include/graph/algorithm/articulation_points.hpp index 3d1eca8..c9f9a6c 100644 --- a/include/graph/algorithm/articulation_points.hpp +++ b/include/graph/algorithm/articulation_points.hpp @@ -96,9 +96,10 @@ using adj_list::find_vertex; * @post Output contains all articulation points, each emitted exactly once. * @post The graph g is not modified. * - * **Exception Safety:** Basic exception safety. May throw std::bad_alloc if internal - * vector allocation fails. The graph g remains unchanged; output iterator may be - * partially written. + * Throws: + * std::bad_alloc if internal vector allocation fails. + * Basic exception guarantee: the graph g remains unchanged; output iterator may be + * partially written. * * ## Example Usage * @@ -192,7 +193,7 @@ void articulation_points(G&& g, Iter cut_vertices) { if (parent[par_uid].has_value()) { // Non-root rule: child v has low[v] >= disc[u] if (low[uid] >= disc[par_uid] && !emitted[par_uid]) { - *cut_vertices++ = par_uid; + *cut_vertices++ = par_uid; emitted[par_uid] = true; } } @@ -227,7 +228,7 @@ void articulation_points(G&& g, Iter cut_vertices) { } } - // Root rule: root is an articulation point iff it has >= 2 DFS children + // Root rule: root is an articulation point iff it has >= 2 DFS children if (child_count[start] >= 2 && !emitted[start]) { *cut_vertices++ = start; emitted[start] = true; diff --git a/include/graph/algorithm/biconnected_components.hpp b/include/graph/algorithm/biconnected_components.hpp index d2e7101..7bc2892 100644 --- a/include/graph/algorithm/biconnected_components.hpp +++ b/include/graph/algorithm/biconnected_components.hpp @@ -102,9 +102,10 @@ using adj_list::find_vertex; * @post Each component's induced subgraph is biconnected. * @post The graph g is not modified. * - * **Exception Safety:** Basic exception safety. May throw std::bad_alloc if internal - * vector or set allocation fails. The graph g remains unchanged; components may be - * partially written. + * Throws: + * std::bad_alloc if internal vector or set allocation fails. + * Basic exception guarantee: the graph g remains unchanged; components may be + * partially written. * * ## Example Usage * diff --git a/include/graph/algorithm/connected_components.hpp b/include/graph/algorithm/connected_components.hpp index 9e25ba3..08a0112 100644 --- a/include/graph/algorithm/connected_components.hpp +++ b/include/graph/algorithm/connected_components.hpp @@ -99,7 +99,9 @@ using adj_list::target_id; * @post Component IDs are assigned 0, 1, 2, ..., num_components-1 * @post Vertices in the same SCC have the same component ID * - * @throws May throw std::bad_alloc if internal allocations fail + * Throws: + * std::bad_alloc if internal allocations fail. + * Basic exception guarantee. * * @par Example * @code @@ -408,7 +410,9 @@ void kosaraju(G&& g, // bidirectional graph * @post Return value equals the number of distinct component IDs * @post Isolated vertices (no edges) are assigned unique component IDs * - * @throws May throw std::bad_alloc if internal allocations fail + * Throws: + * std::bad_alloc if internal allocations fail. + * Basic exception guarantee. * * @par Example * @code @@ -687,7 +691,9 @@ static vertex_id_t sample_frequent_element(Component& component, size_t num_samp * @post Vertices in the same component have the same component ID * @post Component IDs form a union-find forest (may need compression for queries) * - * @throws May throw std::bad_alloc if internal allocations fail + * Throws: + * std::bad_alloc if internal allocations fail. + * Basic exception guarantee. * * @par Example * @code diff --git a/include/graph/algorithm/depth_first_search.hpp b/include/graph/algorithm/depth_first_search.hpp index 896a992..8163604 100644 --- a/include/graph/algorithm/depth_first_search.hpp +++ b/include/graph/algorithm/depth_first_search.hpp @@ -115,17 +115,12 @@ using adj_list::find_vertex; * * @par Exception Safety * - * **Guarantee:** Basic exception safety - * - * **Throws:** - * - May throw `std::bad_alloc` if color array or stack cannot allocate memory - * - May propagate exceptions from visitor callbacks - * - May propagate exceptions from container operations - * - * **State after exception:** - * - Graph `g` remains unchanged - * - Visitor state depends on implementation - * - Partial traversal may have occurred + * Throws: + * std::bad_alloc if color array or stack cannot allocate memory. + * Any exception propagated from visitor callbacks. + * Any exception propagated from container operations. + * Basic exception guarantee: graph `g` remains unchanged; visitor state depends on + * implementation; partial traversal may have occurred. * * @par Visitor Callbacks * diff --git a/include/graph/algorithm/jaccard.hpp b/include/graph/algorithm/jaccard.hpp index 7710936..cd9f672 100644 --- a/include/graph/algorithm/jaccard.hpp +++ b/include/graph/algorithm/jaccard.hpp @@ -93,9 +93,11 @@ using adj_list::num_vertices; * @post All reported coefficient values lie in [0.0, 1.0]. * @post The graph g is not modified. * - * **Exception Safety:** Basic exception safety. May throw std::bad_alloc if internal - * container allocation fails. The graph g remains unchanged; `out` may have been - * partially invoked. + * Throws: + * std::bad_alloc if internal container allocation fails. + * Any exception propagated from the user-provided callback `out`. + * Basic exception guarantee: the graph g remains unchanged; `out` may have been + * partially invoked. * * @note T = double is the recommended default. Using integral types will truncate * results to 0 or 1. @@ -171,8 +173,7 @@ void jaccard_coefficient(G&& g, OutOp out) { // |N(u) ∪ N(v)| = |N(u)| + |N(v)| - |N(u) ∩ N(v)| size_t union_size = nbrs[uid].size() + nbrs[vid].size() - intersect_size; - T val = (union_size == 0) ? T{0} - : static_cast(intersect_size) / static_cast(union_size); + T val = (union_size == 0) ? T{0} : static_cast(intersect_size) / static_cast(union_size); out(uid, vid, uv, val); } diff --git a/include/graph/algorithm/mis.hpp b/include/graph/algorithm/mis.hpp index b56c340..b50f0de 100755 --- a/include/graph/algorithm/mis.hpp +++ b/include/graph/algorithm/mis.hpp @@ -95,9 +95,10 @@ using adj_list::num_vertices; * @post For empty graphs, returns 0 with no output * @post The graph g is not modified * - * **Exception Safety:** Basic exception safety. May throw std::bad_alloc if internal - * vector allocation fails. The graph g remains unchanged; output iterator may be - * partially written. + * Throws: + * std::bad_alloc if internal vector allocation fails. + * Basic exception guarantee: the graph g remains unchanged; output iterator may be + * partially written. * * @note Vertices with self-loops cannot be in any independent set and are excluded. * @note The algorithm is deterministic for a given seed but produces different results @@ -145,9 +146,9 @@ using adj_list::num_vertices; template requires output_iterator> -size_t maximal_independent_set(G&& g, // graph - Iter mis, // out: maximal independent set - const vertex_id_t& seed = 0 // seed vtx +size_t maximal_independent_set(G&& g, // graph + Iter mis, // out: maximal independent set + const vertex_id_t& seed = 0 // seed vtx ) { size_t N = num_vertices(g); if (N == 0) { diff --git a/include/graph/algorithm/tc.hpp b/include/graph/algorithm/tc.hpp index 3a3df63..7e256b5 100644 --- a/include/graph/algorithm/tc.hpp +++ b/include/graph/algorithm/tc.hpp @@ -232,7 +232,7 @@ using adj_list::num_vertices; */ template requires ordered_vertex_edges -size_t triangle_count(G&& g) { +[[nodiscard]] size_t triangle_count(G&& g) noexcept { size_t triangles = 0; // ============================================================================