Skip to content

Incoming#4

Merged
pratzl merged 40 commits intomainfrom
incoming
Feb 24, 2026
Merged

Incoming#4
pratzl merged 40 commits intomainfrom
incoming

Conversation

@pratzl
Copy link
Collaborator

@pratzl pratzl commented Feb 23, 2026

Add support for bidirectional graph (e.g. incoming edges)

…larity\n\nPhase 1: in_edges CPO, in_degree CPO, outgoing aliases (out_edges,\nout_degree, find_out_edge), type aliases (in_edge_range_t, in_edge_iterator_t,\nin_edge_t, out_edge_range_t, out_edge_iterator_t, out_edge_t).\n\nPhase 2: find_in_edge and contains_in_edge CPOs with 3-tier resolution\n(member/ADL/default). Default tiers delegate to outgoing edge operations\nin reverse direction. Added has_in_degree, has_find_in_edge,\nhas_contains_in_edge traits.\n\nPhase 3: out_edge_range, in_edge_range, bidirectional_adjacency_list,\nindex_bidirectional_adjacency_list concepts. Updated graph.hpp with full\nre-exports for all Phases 1-3 items.\n\nRenamed for directional clarity:\n- vertex_edge_range concept -> out_edge_range\n- in_vertex_edge_range concept -> in_edge_range\n- out_vertex_edge_range_t -> out_edge_range_t\n- in_vertex_edge_range_t -> in_edge_range_t\n- out_vertex_edge_iterator_t -> out_edge_iterator_t\n- in_vertex_edge_iterator_t -> in_edge_iterator_t\n\nAll 4342 tests pass."
The explicit directional names (out_edges, out_degree, find_out_edge,
out_edge_range_t, out_edge_iterator_t, out_edge_t) are now the primary
CPO definitions and type aliases. The shorter names (edges, degree,
find_vertex_edge, vertex_edge_range_t, vertex_edge_iterator_t, edge_t)
become convenience aliases via inline constexpr auto&.

Code changes:
- graph_cpo.hpp: swap primary/alias for 3 CPOs + 6 type aliases
- adjacency_list_concepts.hpp: use out_edges in concept definitions,
  fix concept ordering (ordered_vertex_edges after adjacency_list)

Documentation updates:
- cpo-reference.md, adjacency-lists.md, type-aliases.md,
  adjacency-list-interface.md, cpo-order.md, coding-guidelines.md
- agents/incoming_edges_design.md, incoming_edges_plan.md

All 4342 tests pass.
… alias

Follows the same pattern as out_edges/edges, out_degree/degree, and
find_out_edge/find_vertex_edge: the explicit directional name is the
primary definition, the shorter name is a convenience alias.

- graph_cpo.hpp: contains_out_edge is primary _fn instance,
  contains_edge is inline constexpr auto& alias
- graph.hpp: re-export both contains_out_edge and contains_edge
- Updated cpo-reference.md, cpo-order.md

All 4342 tests pass.
The plural form better reflects the CPO's semantics: it checks whether the
graph has any edges (plural), not a specific edge.

Internal dispatch unchanged: the CPO still resolves g.has_edge() member
and ADL has_edge(g) customization points on containers.

All 4342 tests pass.
Add in_edges() ADL friends to base_undirected_adjacency_list that forward
to the same edge ranges as edges(), making undirected graphs model
bidirectional_adjacency_list at zero cost. in_degree is handled automatically
by the CPO default tier (size(in_edges(g, u))).

find_in_edge and contains_in_edge also work correctly via their default
tiers which delegate to find_vertex_edge/edges with swapped arguments.

10 new test cases covering:
- Concept satisfaction (bidirectional + index_bidirectional)
- in_edges == edges identity per vertex
- in_degree == degree for all vertices
- find_in_edge with vertex ids, descriptors, and mixed
- contains_in_edge positive and negative cases
- Star and triangle graph topologies

All 4352 tests pass.
Phase 5 now renames existing incidence.hpp/neighbors.hpp to
out_incidence.hpp/out_neighbors.hpp (primary), with thin forwarding
headers as aliases. in_incidence.hpp/in_neighbors.hpp are the new
incoming-edge views.

Phase 6 similarly renames adaptor instances to out_* primary,
incidence/neighbors as aliases, then adds in_* adaptor closures.
Keep out_*, in_*, and alias views all in the same incidence.hpp and
neighbors.hpp files. No file renames or forwarding headers needed.
Mirrors how graph_cpo.hpp keeps out_edges, in_edges, and edges alias
together.
Introduce out_edge_accessor / in_edge_accessor in Phase 5 as a single
parameterization point. Views (incidence, neighbors) gain an Accessor
template parameter defaulting to out_edge_accessor — no class duplication.
Phase 7 reuses the same accessor types for BFS/DFS/topo-sort, eliminating
the need for a separate edge_accessor.hpp in that phase.
Introduce out_edge_accessor and in_edge_accessor as stateless policy
structs that bundle edge-range fetching, neighbor-id extraction, and
neighbor-vertex access.  All four incidence view classes and all four
neighbors view classes gain an Accessor template parameter (defaulting
to out_edge_accessor) so existing code is source-compatible.

New factory functions:
  - out_incidence / in_incidence / basic_out_incidence / basic_in_incidence
  - out_neighbors / in_neighbors / basic_out_neighbors / basic_in_neighbors

Files changed:
  - NEW  include/graph/views/edge_accessor.hpp
  - MOD  include/graph/views/incidence.hpp   (parameterized + factories)
  - MOD  include/graph/views/neighbors.hpp   (parameterized + factories)
  - MOD  include/graph/graph.hpp             (include edge_accessor.hpp)
  - NEW  tests/views/test_in_incidence.cpp   (14 test sections)
  - NEW  tests/views/test_in_neighbors.cpp   (14 test sections)
  - MOD  tests/views/CMakeLists.txt          (register new tests)

All 4352 tests pass (461 views tests, 6238 assertions).
Add incoming adaptor closures so pipe syntax works for in_edges:
  g | in_incidence(uid)          g | in_incidence(uid, evf)
  g | in_neighbors(uid)          g | in_neighbors(uid, vvf)
  g | basic_in_incidence(uid)    g | basic_in_neighbors(uid)

Outgoing adaptors gain explicit out_* names via type aliases:
  out_incidence_adaptor_fn = incidence_adaptor_fn  (etc.)

Short names are now inline constexpr references to out_* objects:
  incidence = out_incidence, neighbors = out_neighbors
  basic_incidence = basic_out_incidence, basic_neighbors = basic_out_neighbors

Files changed:
  - MOD include/graph/views/adaptors.hpp    (8 new adaptor types + instances)
  - MOD include/graph/views/neighbors.hpp   (static_cast fix for Clang)
  - MOD include/graph/views/edge_accessor.hpp (minor formatting)
  - MOD tests/views/test_adaptors.cpp       (16 new test cases, 25 assertions)

All 4352 tests pass on both GCC and Clang.
Complete Phase 8 of incoming edges plan — direction-aware edge_descriptor,
in_edges ADL friends, and all CPO fixes for bidirectional dynamic_graph.

Direction tag design (Option A):
- Added out_edge_tag / in_edge_tag in descriptor.hpp
- EdgeDirection 3rd template parameter on edge_descriptor (default out_edge_tag)
- is_in_edge / is_out_edge static constexpr members
- requires constraints on tag-dependent member functions:
  - source_id(vertex_data) requires(is_in_edge)
  - target_id(vertex_data) split into requires(is_in_edge) / requires(is_out_edge)

Mirror principle (A1) implementation:
- source_id(vertex_data): navigates .in_edges() for actual source (in-edge only)
- target_id(vertex_data): trivial return for in-edges, navigates .edges() for out-edges
- underlying_value/inner_value: direction-aware container selection

Infrastructure changes:
- edge_descriptor_view: EdgeDirection parameter, enable_borrowed_range for 3-param
- descriptor_traits: all 11 specializations updated, is_in_edge_descriptor trait
- edge_cpo.hpp: source_id tier 4 with if constexpr (is_in_edge) + fallback safety
- graph_cpo.hpp: _wrap_in_edge helper in _in_edges namespace
- dynamic_graph.hpp: in_edges(g,u) ADL friends with in_edge_tag descriptors,
  direction-aware edge_value ADL friend

Bidirectional template parameter (steps 8.1-8.4):
- Propagated through all classes, 27 traits files, all test files
- dynamic_vertex_bidir_base with conditional in_edges_ storage
- load_edges populates both containers when Bidirectional
- _has_default_uid CPO fix for in_edges

4391/4391 tests pass on GCC-15 and Clang, zero regressions.
- Add include/graph/views/transpose.hpp: zero-cost bidirectional graph
  adaptor that swaps edges<->in_edges and target<->source via ADL friends.
  Documented limitation for forward-iterator containers (CPO tier-1 bypass).

- Add kosaraju(G&&, Component&) overload in connected_components.hpp,
  constrained on index_bidirectional_adjacency_list. Uses manual iterative
  stack-based DFS with in_edges/source_id for the second pass — works
  with ALL container types (vov + vol). O(V+E) with no transpose overhead.

- Add tests/views/test_transpose.cpp: 9 tests verifying edge/degree swap,
  double-transpose identity, edge_value forwarding, edge cases.

- Add tests/algorithms/test_scc_bidirectional.cpp: 14 tests covering
  single vertex, cycles, multi-SCC, DAG, self-loops, weighted, disconnected,
  and agreement with the two-graph overload — tested on both vov and vol.

- Update graph.hpp to include transpose.hpp.
- Update CMakeLists for both test targets.

4405/4405 tests pass on GCC-15 and Clang."
Phase 7 — BFS/DFS/topological sort Accessor parameterization:
- Add Accessor template param (default out_edge_accessor) to all
  BFS, DFS, and topological sort view classes and factory functions
- Fix in_edge_accessor::neighbor() — was calling adj_list::source()
  which returns owning vertex for in-edge descriptors; now uses
  find_vertex(g, source_id(g, e)) for correct resolution
- Add 22 new tests in test_reverse_traversal.cpp covering forward
  and reverse traversal for all search view types

Phase 10 — Documentation:
- Create docs/user-guide/bidirectional-access.md tutorial guide
- Update 11 existing doc files: index, getting-started, views,
  algorithms, containers, concepts, cpo-reference, type-aliases,
  adjacency-list-interface, cpo-implementation, README
- Add CHANGELOG entry for full bidirectional edge access feature
- Document bidirectional_adjacency_list concept, in_edge_* type
  aliases, Bidirectional template parameter, in_incidence/in_neighbors
  views, and Accessor parameter on search views

4405/4405 tests passing.
…directed support

migration-from-v2.md:
- Add undirected_adjacency_list section with template signature, complexity
  table, iterator invalidation rules, usage example, and when-to-use guidance
- Add Bidirectional Graphs section covering dynamic_graph<...,true,true,...>,
  incoming-edge CPO table (in_edges, in_degree, find_in_edge, contains_in_edge,
  source_id), in_incidence/in_neighbors view table with structured-binding
  yields, and algorithms note (SCC, reverse BFS/DFS)
- Add bidirectional graphs row to Key Changes at a Glance table
- Add migration checklist item for predecessor/incoming-edge queries

docs/status/coverage.md:
- Regenerate from clean build (2026-02-22, 4405 tests, 96.0% line / 91.8% func)
- Add edge_accessor.hpp and transpose.hpp (new files in views/)
- Update all per-file line and function counts

cmake/CodeCoverage.cmake:
- Write raw data to coverage_raw.info then filter into coverage.info to avoid
  overwriting in place
- Add --ignore-errors unused to lcov --remove to fix failure with lcov >= 2.x
  when the /usr/* exclude pattern matches nothing
- Views: 7 → 10 (added in_incidence, in_neighbors, transpose)
- Trait combinations: 26 → 27
- Test count: 4261 → 4405 (2026-02-19 → 2026-02-23)
- Coverage: 96.0% line / 91.8% function
- Connected components row now notes Kosaraju SCC + test_scc_bidirectional.cpp
- Removed stale note about README using graph::graph target (already graph::graph3)
…- Add in_edges_type derived alias fallback rule (detected_or_t)\n- Fix Phase 2 sequencing: temporary constructibility dispatch\n- Fix Phase 4c: in_edges_type omission from standard traits\n- Add compile-time safety risk row for fallback aliases\n- Add Definition of Done section\n- Remove stale Sourced=true and uniform-mode references"
…lan with precise file/line references, safety mitigations,\ntemporary bridge patterns, and status tracker. Derived from\ndynamic_edge_refactor_strategy.md."
11 issues identified in deep cross-review of plan vs source code:

- 4a.4: CRITICAL — remove dynamic_edge_source from dynamic_out_edge
  base list after Sourced=false empty spec is deleted; promote surviving
  Sourced=false specs to primary template (not just 'keep')
- 4a.5: drop Sourced arg from dynamic_in_edge's dynamic_edge_value base
- 4b.2: call out dynamic_vertex_base internal edge_type/edges_type aliases
  (L721-722) that also need Sourced removed
- 4b.4: add edge_allocator_type audit + in_edge_allocator_type alias
  for non-uniform bidirectional mode
- 4b.6 (new): edge_value direction-aware friend function needs Sourced
  removed and type-based dispatch check
- 4d.1: clarify that all 18 emplace_edge calls across 3 load_edges paths
  must be audited; Sourced=false branch promoted, Sourced=true deleted
- 4g: Phase 4g build gate replaced with example-target build (no
  library-only CMake target exists)
- Overview: add test_dynamic_edge_comparison.cpp to Files of Interest
- 5.1.1: warn that Sourced→Bidirectional at position 5 is a semantic
  change, not just a rename; callers passing true/false must be audited
- 5.2: add detailed test_dynamic_edge_comparison.cpp migration steps
- Risk table: add row for missing library-only CMake target"
Purely additive changes to dynamic_graph.hpp (198 insertions, 0 deletions):

1. Forward declaration for dynamic_in_edge<EV,VV,GV,VId,Bidirectional,Traits>
   (6 params, no Sourced) after the dynamic_edge forward declaration.

2. graph::container::detail namespace with detection-idiom helpers:
   - detected_or_t<Default, Op, Args...> — returns Op<Args...> if
     well-formed, otherwise Default (no Boost dependency)
   - detect_in_edge_type<T>  — detects T::in_edge_type
   - detect_in_edges_type<T> — detects T::in_edges_type

3. dynamic_in_edge_source<EV,VV,GV,VId,Bidirectional,Traits> — temporary
   unconditional source-id base (no Sourced param). Replaces in Phase 4a.

4. dynamic_in_edge — 2 specializations (EV!=void, EV=void):
   - Primary: inherits dynamic_in_edge_source + dynamic_edge_value
     (Sourced=true pass-through). Constructors: (src), (src,val), (src,val&&).
     operator<=> and operator== compare by source_id.
   - void specialization: inherits dynamic_in_edge_source only.
     Constructor: (src). Same comparison operators.

5. In dynamic_graph_base: three new dead aliases (activated in Phase 2):
   - out_edge_type = edge_type
   - in_edge_type  = detected_or_t<edge_type, detect_in_edge_type, Traits>
   - in_edges_type = detected_or_t<edges_type, detect_in_edges_type, Traits>
   - static_assert verifying non-uniform traits define both in_edge_type and
     in_edges_type if either is defined.

6. std::hash<dynamic_in_edge<...>> — hashes source_id only.

Phase 1 gate: cmake --build build/linux-gcc-debug succeeded,
4405/4405 tests passed, zero failures."
Three changes to dynamic_graph.hpp (all existing tests still pass):

1. dynamic_vertex_bidir_base<..., true>: edges_type (in-edge storage) now
   derived via detection idiom:
     detail::detected_or_t<Traits::edges_type, detect_in_edges_type, Traits>
   Falls back to Traits::edges_type for all 27 standard traits — zero
   runtime behaviour change. Enables future non-uniform traits to supply
   a distinct in_edges_type.

2. make_in_edge private helpers in dynamic_graph_base (temporary bridge,
   removed in Phase 4d). Dispatch based on constructibility:
     - if InEdge is constructible from (VId, VId [, Val]):
         legacy path for edge_type when Sourced=true
     - otherwise:
         future path for dynamic_in_edge which takes (VId [, Val])
   Two overloads: void-EV (no value) and non-void-EV (copy value).

3. All 6 in-edge emplace_edge sites across the 3 load_edges paths changed
   from edge_type{src, tgt} to make_in_edge(src, tgt) (and analogously
   for the value-bearing variant). Out-edge construction unchanged.

Phase 2 gate: cmake --build build/linux-gcc-debug succeeded,
4405/4405 tests passed, zero failures."
…s (Phase 3)

- Renamed all dynamic_edge class declarations to dynamic_out_edge in
  dynamic_graph.hpp (80 occurrences: class decls, edge_type aliases,
  hash specializations)
- Added deprecated alias 'using dynamic_edge = dynamic_out_edge<...>'
  for backward compatibility; will be removed in Phase 4
- Renamed forward declarations and edge_type aliases in all 27 traits
  headers from dynamic_edge to dynamic_out_edge
- test_dynamic_edge_comparison.cpp continues to compile via the alias

4405/4405 tests pass
… 4→2 specs (Phase 4a)

Phase 4a of the dynamic_edge refactor plan:
- 4a.1: Remove bool Sourced from dynamic_edge_target template params
- 4a.2: Remove bool Sourced from dynamic_edge_source, delete empty
  Sourced=false specialization (now unconditionally stores source_id)
- 4a.3: Remove bool Sourced from dynamic_edge_value (both specs)
- 4a.4: Collapse dynamic_out_edge from 4 to 2 specializations (keyed
  on EV only). dynamic_out_edge no longer inherits dynamic_edge_source.
- 4a.5: Switch dynamic_in_edge bases from dynamic_in_edge_source to
  the repurposed dynamic_edge_source; delete dynamic_in_edge_source
- 4a.6: Update dynamic_out_edge forward declaration (6 params)
- 4a.7: Remove deprecated dynamic_edge alias; collapse 2 hash specs
  into 1 (hashes target_id only)

Does NOT compile yet — vertex/graph classes still reference Sourced.
Continues in Phase 4b.
- dynamic_vertex_bidir_base: Remove Sourced from both specializations;
  delete static_assert(Sourced, ...) in Bidirectional=true spec
- dynamic_vertex_base: Remove Sourced from template params, bidir_base
  reference, and graph_type/vertex_type/edge_type aliases
- dynamic_vertex: Remove Sourced from both specializations (primary and
  VV=void) including all type aliases and base class references
- dynamic_graph_base: Remove Sourced from template params and all type
  aliases (graph_type, vertex_type, edge_type)
- dynamic_graph: Remove Sourced from both specializations (primary and
  GV=void); remove 'constexpr ... sourced = Sourced' constant
- Forward declarations: Remove 'bool Sourced = false' from dynamic_graph
  and 'bool Sourced' from dynamic_vertex
- dynamic_adjacency_graph alias: Remove Traits::sourced
- edge_value friend function: Verified no Sourced dependency (uses
  is_in_edge type trait for direction dispatch)

Remaining Sourced references (to be addressed in later phases):
- Lines 58-82: traits forward declarations (Phase 4c)
- Lines 1311,1406,1444: if constexpr(Sourced) in load_edges (Phase 4d)

Does NOT compile yet — traits files still pass Sourced.
- All 27 traits files (vofl, vol, vov, vod, vos, vous, vom, voum,
  dofl, dol, dov, dod, dos, dous, mofl, mol, mov, mod, mos, mous,
  mom, uofl, uol, uov, uod, uos, uous):
  * Remove 'bool Sourced' from forward declarations (6→5 params)
  * Remove 'bool Sourced = false' from struct template params
  * Remove 'static constexpr bool sourced = Sourced;' member
  * Remove Sourced from edge_type/vertex_type/graph_type aliases
  * Update comments referencing Sourced

- dynamic_graph.hpp: Remove 'bool Sourced' from all 9 traits
  forward declarations (lines 58-82)

Remaining Sourced references (Phase 4d):
- Lines 1311,1406,1444: if constexpr(Sourced) in load_edges
- Line 242: comment in dynamic_edge_source

Does NOT compile yet — load_edges still references Sourced.
- All 3 load_edges paths (associative, sequential-forward, fallback):
  * Remove if constexpr(Sourced) guards — collapse to unconditional path
  * Out-edge construction: always edge_type(target_id [, value])
  * In-edge construction: direct in_edge_type(source_id [, value]) guarded
    by if constexpr(Bidirectional) — replaces make_in_edge bridge
- Delete make_in_edge helper (2 overloads, added in Phase 2)

27 insertions, 74 deletions. No Sourced references remain in code
(only 1 documentation comment at line 242 in dynamic_edge_source).

Does NOT compile yet — Phase 4e-4g pending.
- 4f: Hash specializations already Sourced-free (done in Phase 4a)
- 4g: Fixed example files that still used old 7-param dynamic_graph:
  * dijkstra_clrs_example.cpp: Remove Sourced from dynamic_graph and
    vol_graph_traits template args
  * mst_usage_example.cpp: Remove Sourced from dynamic_graph and
    vov_graph_traits template args
- Build verification: dijkstra_example and basic_usage compile cleanly

Phase 4 complete. Tests still reference Sourced (Phase 5).
…iases

- All 27 tag structs: rename 'bool Sourced' -> 'bool Bidirectional = false'
  in template alias, update 5th traits arg to Bidirectional
- graph_test_types struct:
  * Drop 7-param dynamic_graph (Sourced removed) -> 6-param
  * Remove sourced_void / sourced_int / sourced_all aliases
  * Add bidir_void / bidir_int / bidir_all aliases (Bidirectional=true)
  * Update doxygen comments
- File header: update 'sourced' -> 'bidirectional' in description
@pratzl pratzl merged commit 8ab9416 into main Feb 24, 2026
11 checks passed
@pratzl pratzl deleted the incoming branch February 24, 2026 00:58
pratzl added a commit that referenced this pull request Feb 26, 2026
Conflicts resolved:
- adjacency_list_traits.hpp: kept const& on new find_in_edge concepts
- edge_descriptor.hpp: kept origin/main's auto return types (not vertex_id params)
- connected_components.hpp: kept const& on lambda param
- bfs.hpp, dfs.hpp: kept const& on seed params in new factory functions
- incidence.hpp, neighbors.hpp: kept const& on uid params including new in_* factories
- transpose.hpp: kept const& on find_vertex uid params
- test_dynamic_graph_integration.cpp: kept our static_assert block

All 4 presets green (GCC/Clang debug/release, 4343-4344 tests, 0 failures).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant