From 6d40c8fc10f56ed17dd1893ac5a54754ad8ff459 Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Tue, 5 May 2026 18:55:28 +0200 Subject: [PATCH 01/14] cleanup --- benchmarks/README.md | 2 +- docs/gl/architecture.md | 2 +- include/gl/graph.hpp | 2 +- include/gl/util/ranges.hpp | 2 +- include/hgl/hypergraph.hpp | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/benchmarks/README.md b/benchmarks/README.md index c6fdbfd9..4067292e 100644 --- a/benchmarks/README.md +++ b/benchmarks/README.md @@ -30,7 +30,7 @@ cmake -B build_bench -DBUILD_BENCHMARKS=ON -DBENCH_INCLUDE_BGL=ON -DCMAKE_BUILD_ ``` > [!NOTE] -> +> > BGL comparative benchmarks are only defined for specific benchmark suites where a direct equivalent exists in Boost.
diff --git a/docs/gl/architecture.md b/docs/gl/architecture.md index ac525521..8d63477a 100644 --- a/docs/gl/architecture.md +++ b/docs/gl/architecture.md @@ -48,7 +48,7 @@ Formally, a graph $G = (V, E)$ consists of a set of vertices $V$ and a set of ed - [**directed_t**](../cpp-gl/structgl_1_1directed__t.md) : Specifies a **directed graph** configuration where edges are defined as ordered pairs $(u, v)$ such that $u, v \in V$. In this graph type, a connection from vertex $u$ to vertex $v$ is structurally distinct from a connection from $v$ to $u$, and the existence of one directed edge does not imply the existence of the other. -- [**gl::undirected_t**](../cpp-gl/structgl_1_1undirected__t.md) : Edges are unordered pairs $\{u, v\}$ where $u, v \in V$. The library automatically manages the bidirectional nature of these connections, ensuring that an edge between $u$ and $v$ is recognized during both traversal and structural degree calculations regardless of the order of endpoints. +- [**undirected_t**](../cpp-gl/structgl_1_1undirected__t.md) : Edges are unordered pairs $\{u, v\}$ where $u, v \in V$. The library automatically manages the bidirectional nature of these connections, ensuring that an edge between $u$ and $v$ is recognized during both traversal and structural degree calculations regardless of the order of endpoints. ### IDs vs. Descriptors diff --git a/include/gl/graph.hpp b/include/gl/graph.hpp index a41a3e7d..d8830d7b 100644 --- a/include/gl/graph.hpp +++ b/include/gl/graph.hpp @@ -1143,7 +1143,7 @@ class graph final { /// @param is The source input stream. /// @param g The graph instance to populate. /// @return The stream reference for chaining. - friend gl_attr_force_inline std::istream& operator>>(std::istream& is, graph& g) { + gl_attr_force_inline friend std::istream& operator>>(std::istream& is, graph& g) { return g._gsf_read(is); } diff --git a/include/gl/util/ranges.hpp b/include/gl/util/ranges.hpp index 9fdd4b50..bcfa4dfd 100644 --- a/include/gl/util/ranges.hpp +++ b/include/gl/util/ranges.hpp @@ -65,7 +65,7 @@ template /// /// > [!INFO] Time Complexity /// > -/// > $O(N)$ where $N$ is the number of elements in the range. +/// > \f$O(N)\f$ where $N$ is the number of elements in the range. template [[nodiscard]] constexpr bool all_equal( R&& range, const std::ranges::range_value_t& value diff --git a/include/hgl/hypergraph.hpp b/include/hgl/hypergraph.hpp index b18a57f7..b6ef80df 100644 --- a/include/hgl/hypergraph.hpp +++ b/include/hgl/hypergraph.hpp @@ -144,7 +144,7 @@ struct to_impl; /// - **Standard Range Support**: Exposes lightweight views compliant with C++20 `std::ranges`, enabling functional-style iteration and algorithms. /// /// ### Basic Definitions -/// A hypergraph \f$G = (V, E)\f$ consists of a set of vertices \f$V\f$ and a set of hyperedges \f$E\f$. +/// A hypergraph \f$H = (V, E)\f$ consists of a set of vertices \f$V\f$ and a set of hyperedges \f$E\f$. /// /// - For undirected graphs, a hyperedge is a subset of the vertex set. Formally \f$E \subseteq 2^V\f$ and \f$e \in E \implies e \subseteq V\f$. /// - For BF-directed graphs, a hyperedge is an ordered pair of disjoint subsets of the vertex set - the *tail* (sources) and *head* (targets) of the hyperedge. From 461a5da98259e1c81ac13bcc26933ef5f7f772d3 Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Mon, 1 Jun 2026 15:11:39 +0200 Subject: [PATCH 02/14] hg_traversal benchmark --- .github/workflows/benchmarks.yaml | 3 +- benchmarks/CMakeLists.txt | 1 + benchmarks/suites/hg_traversal.cpp | 183 +++++++++++++++++++++++++++++ benchmarks/suites/is_bipartite.cpp | 50 +++----- 4 files changed, 202 insertions(+), 35 deletions(-) create mode 100644 benchmarks/suites/hg_traversal.cpp diff --git a/.github/workflows/benchmarks.yaml b/.github/workflows/benchmarks.yaml index af77b126..b7d9adfb 100644 --- a/.github/workflows/benchmarks.yaml +++ b/.github/workflows/benchmarks.yaml @@ -42,4 +42,5 @@ jobs: run: | ./build_bench/benchmarks/cpp-gl-bench \ --benchmark_repetitions=1 --benchmark_display_aggregates_only=true \ - --bip-v 100 + --bip-v 100 \ + --hg-bfs-e 100 --hg-bfs-esize 5 diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt index 7ac49a7a..db7466dd 100644 --- a/benchmarks/CMakeLists.txt +++ b/benchmarks/CMakeLists.txt @@ -34,6 +34,7 @@ add_executable(cpp-gl-bench source/main.cpp source/runner.cpp suites/is_bipartite.cpp + suites/hg_traversal.cpp ) target_compile_options(cpp-gl-bench PRIVATE diff --git a/benchmarks/suites/hg_traversal.cpp b/benchmarks/suites/hg_traversal.cpp new file mode 100644 index 00000000..a3e0feef --- /dev/null +++ b/benchmarks/suites/hg_traversal.cpp @@ -0,0 +1,183 @@ +#include "gl/directional_tags.hpp" +#include "gl/types/properties.hpp" +#include "hgl/directional_tags.hpp" +#include "runner.hpp" +#include "suite.hpp" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace gl_bench::hypergraph_bfs { + +// --- Hypergraph Topology Generator --- + +template +HypergraphType gen_sliding_window_hypergraph( + const std::size_t num_hyperedges, const std::size_t degree, const std::size_t stride +) { + using id_type = typename HypergraphType::id_type; + + // V = (E - 1) * stride + degree + const auto n_vertices = static_cast((num_hyperedges - 1) * stride + degree); + const auto n_hyperedges = static_cast(num_hyperedges); + + HypergraphType hgraph{n_vertices, n_hyperedges}; + + for (id_type e = 0; e < n_hyperedges; ++e) { + std::vector e_vertices; + e_vertices.reserve(degree); + + const id_type start_v = static_cast(e * stride); + for (std::size_t k = 0; k < degree; ++k) { + e_vertices.push_back(start_v + static_cast(k)); + } + + hgraph.bind(e_vertices, e); + } + + return hgraph; +} + +// --- HGL Benchmark --- + +template +void bm_hgl_bfs(benchmark::State& state) { + const auto n_hedges = static_cast(state.range(0)); + const auto he_size = static_cast(state.range(1)); + const auto stride = static_cast(state.range(2)); + + auto hg = gen_sliding_window_hypergraph(n_hedges, he_size, stride); + + for (auto _ : state) { + auto search_tree = hgl::algorithm::breadth_first_search(hg, 0uz); + benchmark::DoNotOptimize(search_tree); + } + + state.counters["Vertices"] = static_cast(hg.n_vertices()); + state.counters["Hyperedges"] = static_cast(hg.n_hyperedges()); + state.counters["Incidences"] = static_cast(n_hedges * he_size); +} + +// --- GL Incidence Graph Benchmark --- + +template < + hgl::traits::c_undirected_hypergraph Hypergraph, + gl::traits::c_undirected_graph IncidenceGraph> +void bm_gl_incidence_bfs(benchmark::State& state) { + const auto n_hedges = static_cast(state.range(0)); + const auto he_size = static_cast(state.range(1)); + const auto stride = static_cast(state.range(2)); + + if constexpr (hgl::traits::c_incidence_matrix_hypergraph) { + const auto n_vertices = (n_hedges - 1) * stride + he_size; + const auto ig_vertices = n_vertices + n_hedges; + + // If V_ig > 35,000, the matrix takes > 4.5 GB of contiguous RAM. + if (ig_vertices > 35000) { + state.SkipWithError("Matrix requires > 4.5 GB of memory; skipping."); + return; // MUST RETURN HERE to prevent std::bad_alloc + } + } + + auto hg = gen_sliding_window_hypergraph(n_hedges, he_size, stride); + auto ig = hgl::incidence_graph(hg); + + for (auto _ : state) { + auto pred_map = gl::algorithm::breadth_first_search(ig, 0uz); + benchmark::DoNotOptimize(pred_map); + } + + state.counters["IG_Vertices"] = static_cast(ig.n_vertices()); + state.counters["IG_Edges"] = static_cast(ig.n_edges()); +} + +void add_args(argon::argument_parser& parser) { + auto& group = parser.add_group("Hypergraph BFS Benchmark Suite Options (hg-bfs)"); + parser.add_optional_argument(group, "hg-bfs-e") + .default_values(1000uz) + .help("Number of hyperedges"); + parser.add_optional_argument(group, "hg-bfs-esize") + .default_values(100uz) + .help("Hyperedge size"); + parser.add_optional_argument(group, "hg-bfs-stride") + .default_values(2uz) + .help("Vertex shift between consecutive hyperedges (smaller = denser)"); +} + +void register_benchmarks(const argon::argument_parser& parser) { + const auto n_hedges = static_cast(parser.value("hg-bfs-e")); + const auto he_size = static_cast(parser.value("hg-bfs-esize")); + const auto stride = static_cast(parser.value("hg-bfs-stride")); + + using gl_list = gl::list_graph; + using gl_flat_list = gl::flat_list_graph; + using gl_matrix = gl::matrix_graph; + using gl_flat_matrix = gl::flat_matrix_graph; + + using hgl_list = hgl::list_hypergraph; + using hgl_flat_list = hgl::flat_list_hypergraph; + + using hgl_v_matrix = hgl::matrix_hypergraph; + using hgl_e_matrix = hgl::matrix_hypergraph; + + using hgl_v_flat_matrix = + hgl::flat_matrix_hypergraph; + using hgl_e_flat_matrix = + hgl::flat_matrix_hypergraph; + + benchmark::RegisterBenchmark("bfs/HGL/list", bm_hgl_bfs) + ->Args({n_hedges, he_size, stride}) + ->Unit(benchmark::kMillisecond); + benchmark::RegisterBenchmark("bfs/HGL/flat_list", bm_hgl_bfs) + ->Args({n_hedges, he_size, stride}) + ->Unit(benchmark::kMillisecond); + + benchmark::RegisterBenchmark("bfs/HGL/matrix/v_major", bm_hgl_bfs) + ->Args({n_hedges, he_size, stride}) + ->Unit(benchmark::kMillisecond); + benchmark::RegisterBenchmark("bfs/HGL/matrix/e_major", bm_hgl_bfs) + ->Args({n_hedges, he_size, stride}) + ->Unit(benchmark::kMillisecond); + + benchmark::RegisterBenchmark("bfs/HGL/flat_matrix/v_major", bm_hgl_bfs) + ->Args({n_hedges, he_size, stride}) + ->Unit(benchmark::kMillisecond); + benchmark::RegisterBenchmark("bfs/HGL/flat_matrix/e_major", bm_hgl_bfs) + ->Args({n_hedges, he_size, stride}) + ->Unit(benchmark::kMillisecond); + + benchmark::RegisterBenchmark("bfs/INCIDENCE/list", bm_gl_incidence_bfs) + ->Args({n_hedges, he_size, stride}) + ->Unit(benchmark::kMillisecond); + benchmark:: + RegisterBenchmark("bfs/INCIDENCE/flat_list", bm_gl_incidence_bfs) + ->Args({n_hedges, he_size, stride}) + ->Unit(benchmark::kMillisecond); + benchmark::RegisterBenchmark("bfs/INCIDENCE/matrix", bm_gl_incidence_bfs) + ->Args({n_hedges, he_size, stride}) + ->Unit(benchmark::kMillisecond); + benchmark:: + RegisterBenchmark("bfs/INCIDENCE/flat_matrix", bm_gl_incidence_bfs) + ->Args({n_hedges, he_size, stride}) + ->Unit(benchmark::kMillisecond); +} + +namespace { +bool _registered = []() { + gl_bench::runner::get().add_suite( + "hg-bfs", suite{.add_args = add_args, .register_benchmarks = register_benchmarks} + ); + return true; +}(); +} // namespace + +} // namespace gl_bench::hypergraph_bfs diff --git a/benchmarks/suites/is_bipartite.cpp b/benchmarks/suites/is_bipartite.cpp index c1857cc5..e637f85d 100644 --- a/benchmarks/suites/is_bipartite.cpp +++ b/benchmarks/suites/is_bipartite.cpp @@ -78,16 +78,10 @@ void register_benchmarks(const argon::argument_parser& parser) { const auto n_vertices = static_cast(parser.value("bip-v")); // CPP-GL Adjacency List Benchmarks - using gl_list_u32 = gl::graph>; - using gl_list_u64 = gl::graph>; + using gl_list_u32 = + gl::list_graph; + using gl_list_u64 = + gl::list_graph; benchmark::RegisterBenchmark("is_bipartite/CPP-GL/list/u32", bm_gl_is_bipartite) ->Arg(n_vertices) @@ -97,16 +91,10 @@ void register_benchmarks(const argon::argument_parser& parser) { ->Unit(benchmark::kMillisecond); // CPP-GL Flat Adjacency List Benchmarks - using gl_flat_list_u32 = gl::graph>; - using gl_flat_list_u64 = gl::graph>; + using gl_flat_list_u32 = gl:: + flat_list_graph; + using gl_flat_list_u64 = gl:: + flat_list_graph; benchmark:: RegisterBenchmark("is_bipartite/CPP-GL/flat_list/u32", bm_gl_is_bipartite) @@ -118,16 +106,10 @@ void register_benchmarks(const argon::argument_parser& parser) { ->Unit(benchmark::kMillisecond); // CPP-GL Adjacency Matrix Benchmarks - using gl_matrix_u32 = gl::graph>; - using gl_matrix_u64 = gl::graph>; + using gl_matrix_u32 = gl:: + matrix_graph; + using gl_matrix_u64 = gl:: + matrix_graph; benchmark::RegisterBenchmark("is_bipartite/CPP-GL/matrix/u32", bm_gl_is_bipartite) ->Arg(n_vertices) @@ -137,16 +119,16 @@ void register_benchmarks(const argon::argument_parser& parser) { ->Unit(benchmark::kMillisecond); // CPP-GL Flat Adjacency Matrix Benchmarks - using gl_flat_matrix_u32 = gl::graph>; - using gl_flat_matrix_u64 = gl::graph; + using gl_flat_matrix_u64 = gl::flat_matrix_graph< gl::undirected_t, gl::empty_properties, gl::empty_properties, - std::uint64_t>>; + std::uint64_t>; benchmark:: RegisterBenchmark("is_bipartite/CPP-GL/flat_matrix/u32", bm_gl_is_bipartite) From 190ed56de6388ffbb83c8abc4da24d406ef7167b Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Mon, 1 Jun 2026 16:50:41 +0200 Subject: [PATCH 03/14] renamed bench file --- benchmarks/CMakeLists.txt | 2 +- benchmarks/suites/{hg_traversal.cpp => hg_bfs.cpp} | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename benchmarks/suites/{hg_traversal.cpp => hg_bfs.cpp} (98%) diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt index db7466dd..71a43ef8 100644 --- a/benchmarks/CMakeLists.txt +++ b/benchmarks/CMakeLists.txt @@ -34,7 +34,7 @@ add_executable(cpp-gl-bench source/main.cpp source/runner.cpp suites/is_bipartite.cpp - suites/hg_traversal.cpp + suites/hg_bfs.cpp ) target_compile_options(cpp-gl-bench PRIVATE diff --git a/benchmarks/suites/hg_traversal.cpp b/benchmarks/suites/hg_bfs.cpp similarity index 98% rename from benchmarks/suites/hg_traversal.cpp rename to benchmarks/suites/hg_bfs.cpp index a3e0feef..9f3023fd 100644 --- a/benchmarks/suites/hg_traversal.cpp +++ b/benchmarks/suites/hg_bfs.cpp @@ -16,7 +16,7 @@ #include #include -namespace gl_bench::hypergraph_bfs { +namespace gl_bench::hg_bfs { // --- Hypergraph Topology Generator --- @@ -180,4 +180,4 @@ bool _registered = []() { }(); } // namespace -} // namespace gl_bench::hypergraph_bfs +} // namespace gl_bench::hg_bfs From f52b85a9e497c33e1d7084a074b3119070fdb9fe Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Mon, 1 Jun 2026 17:08:00 +0200 Subject: [PATCH 04/14] hg_bbfs benchmark init --- benchmarks/CMakeLists.txt | 1 + benchmarks/suites/hg_bbfs.cpp | 243 ++++++++++++++++++++++++++++++++++ benchmarks/suites/hg_bfs.cpp | 11 +- 3 files changed, 246 insertions(+), 9 deletions(-) create mode 100644 benchmarks/suites/hg_bbfs.cpp diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt index 71a43ef8..1d860452 100644 --- a/benchmarks/CMakeLists.txt +++ b/benchmarks/CMakeLists.txt @@ -35,6 +35,7 @@ add_executable(cpp-gl-bench source/runner.cpp suites/is_bipartite.cpp suites/hg_bfs.cpp + suites/hg_bbfs.cpp ) target_compile_options(cpp-gl-bench PRIVATE diff --git a/benchmarks/suites/hg_bbfs.cpp b/benchmarks/suites/hg_bbfs.cpp new file mode 100644 index 00000000..0e209d27 --- /dev/null +++ b/benchmarks/suites/hg_bbfs.cpp @@ -0,0 +1,243 @@ +#include "runner.hpp" +#include "suite.hpp" + +#include +#include + +#include +#include +#include +#include + +namespace gl_bench::hg_b_bfs { + +// --- BF-Directed Hypergraph Topology Generator --- + +template +HypergraphType gen_bf_chain_hypergraph( + const std::size_t num_hyperedges, const std::size_t layer_width +) { + using id_type = typename HypergraphType::id_type; + + // A layered chain: Hyperedge `e` connects layer `e` (tail) to layer `e+1` (head). + const auto n_vertices = static_cast((num_hyperedges + 1) * layer_width); + const auto n_hyperedges = static_cast(num_hyperedges); + + HypergraphType hgraph{n_vertices, n_hyperedges}; + + for (id_type e = 0; e < n_hyperedges; ++e) { + std::vector tail; + std::vector head; + tail.reserve(layer_width); + head.reserve(layer_width); + + const auto tail_start = static_cast(e * layer_width); + const auto head_start = static_cast((e + 1) * layer_width); + + for (std::size_t k = 0; k < layer_width; ++k) { + tail.push_back(tail_start + static_cast(k)); + head.push_back(head_start + static_cast(k)); + } + + hgraph.bind_tail(tail, e); + hgraph.bind_head(head, e); + } + + return hgraph; +} + +// --- Native HGL Backward BFS Benchmark --- + +template +void bm_hgl_backward_bfs(benchmark::State& state) { + const auto n_hedges = static_cast(state.range(0)); + const auto layer_width = static_cast(state.range(1)); + + auto hg = gen_bf_chain_hypergraph(n_hedges, layer_width); + + // Initial roots: All vertices in Layer 0 + std::vector roots; + roots.reserve(layer_width); + for (std::size_t i = 0; i < layer_width; ++i) { + roots.push_back(static_cast(i)); + } + + for (auto _ : state) { + // Run native B-BFS + auto search_tree = hgl::algorithm::backward_bfs(hg, roots); + benchmark::DoNotOptimize(search_tree); + } + + state.counters["Vertices"] = static_cast(hg.n_vertices()); + state.counters["Hyperedges"] = static_cast(hg.n_hyperedges()); + state.counters["Incidences"] = static_cast(n_hedges * layer_width * 2); +} + +// --- GL Incidence Graph Backward BFS Equivalent --- + +template < + hgl::traits::c_bf_directed_hypergraph Hypergraph, + gl::traits::c_directed_graph IncidenceGraph> +void bm_gl_incidence_backward_bfs(benchmark::State& state) { + using id_type = typename Hypergraph::id_type; + + const auto n_hedges = static_cast(state.range(0)); + const auto layer_width = static_cast(state.range(1)); + + // Memory Guard for Matrix Representations + if constexpr (gl::traits::c_adjacency_matrix_graph) { + const auto n_vertices = (n_hedges + 1) * layer_width; + const auto ig_vertices = n_vertices + n_hedges; + + // Skip if matrix requires > 4.5 GB of contiguous RAM + if (ig_vertices > 35000) { + state.SkipWithError("Matrix requires > 4.5 GB of memory; skipping."); + return; + } + } + + auto hg = gen_bf_chain_hypergraph(n_hedges, layer_width); + auto ig = hgl::incidence_graph(hg); + + // Create the search nodes for the initial queue from Layer 0 + std::vector> root_nodes; + root_nodes.reserve(layer_width); + for (std::size_t i = 0; i < layer_width; ++i) { + root_nodes.emplace_back(static_cast(i)); + } + + for (auto _ : state) { + std::vector visited_v(hg.n_vertices(), false); + std::vector tail_unvisited(hg.n_hyperedges()); + + // The in-degree of a hyperedge node in a BF-directed incidence graph is exactly its tail size + for (std::size_t i = 0; i < hg.n_hyperedges(); ++i) { + tail_unvisited[i] = ig.in_degree(static_cast(hg.n_vertices() + i)); + } + + // 1. Visit Vertex Predicate: Ignore already visited original vertices + auto visit_vertex_pred = [&](typename IncidenceGraph::id_type v) { + if (v < hg.n_vertices()) { + return not visited_v[v]; + } + return true; // Hyperedge nodes bypass this check (handled by enqueue predicate) + }; + + // 2. Visit Callback: Mark original vertices as visited + auto visit = + [&](typename IncidenceGraph::id_type v, typename IncidenceGraph::id_type /*p*/) { + if (v < hg.n_vertices()) { + visited_v[v] = true; + } + return true; + }; + + // 3. Enqueue Predicate: The Blocking B-Reachability Logic + auto enqueue_node_pred = + [&](typename IncidenceGraph::id_type target_id, const auto& /*edge*/) { + if (target_id >= hg.n_vertices()) { + // If it's a hyperedge node, decrement its blocking counter + const auto he_idx = target_id - hg.n_vertices(); + return static_cast(--tail_unvisited[he_idx] == 0); + } + else { + // If it's a vertex node, enqueue only if unvisited + return static_cast(not visited_v[target_id]); + } + }; + + // Execute the custom BFS logic over the incidence graph + bool completed = + gl::algorithm::bfs(ig, root_nodes, visit_vertex_pred, visit, enqueue_node_pred); + benchmark::DoNotOptimize(completed); + } + + state.counters["IG_Vertices"] = static_cast(ig.n_vertices()); + state.counters["IG_Edges"] = static_cast(ig.n_edges()); +} + +// --- Suite Setup & Registration --- + +void add_args(argon::argument_parser& parser) { + auto& group = parser.add_group("Backward BFS Benchmark Suite Options (hg-b-bfs)"); + parser.add_optional_argument(group, "hg-b-bfs-e") + .default_values(1000uz) + .help("Number of BF-directed hyperedges"); + parser.add_optional_argument(group, "hg-b-bfs-width") + .default_values(100uz) + .help("Number of vertices per layer (tail and head sizes)"); +} + +void register_benchmarks(const argon::argument_parser& parser) { + const auto n_hedges = static_cast(parser.value("hg-b-bfs-e")); + const auto layer_width = static_cast(parser.value("hg-b-bfs-width")); + + // Standard Incidence Graphs MUST be directed for BF-Directed Hypergraphs + using gl_list = gl::list_graph; + using gl_flat_list = gl::flat_list_graph; + using gl_matrix = gl::matrix_graph; + using gl_flat_matrix = gl::flat_matrix_graph; + + // BF-Directed Hypergraphs + using hgl_list = hgl::list_hypergraph; + using hgl_flat_list = hgl::flat_list_hypergraph; + + using hgl_v_matrix = hgl::matrix_hypergraph; + using hgl_e_matrix = hgl::matrix_hypergraph; + + using hgl_v_flat_matrix = + hgl::flat_matrix_hypergraph; + using hgl_e_flat_matrix = + hgl::flat_matrix_hypergraph; + + benchmark::RegisterBenchmark("b_bfs/HGL/list", bm_hgl_backward_bfs) + ->Args({n_hedges, layer_width}) + ->Unit(benchmark::kMillisecond); + benchmark::RegisterBenchmark("b_bfs/HGL/flat_list", bm_hgl_backward_bfs) + ->Args({n_hedges, layer_width}) + ->Unit(benchmark::kMillisecond); + + benchmark::RegisterBenchmark("b_bfs/HGL/matrix/v_major", bm_hgl_backward_bfs) + ->Args({n_hedges, layer_width}) + ->Unit(benchmark::kMillisecond); + benchmark::RegisterBenchmark("b_bfs/HGL/matrix/e_major", bm_hgl_backward_bfs) + ->Args({n_hedges, layer_width}) + ->Unit(benchmark::kMillisecond); + + benchmark:: + RegisterBenchmark("b_bfs/HGL/flat_matrix/v_major", bm_hgl_backward_bfs) + ->Args({n_hedges, layer_width}) + ->Unit(benchmark::kMillisecond); + benchmark:: + RegisterBenchmark("b_bfs/HGL/flat_matrix/e_major", bm_hgl_backward_bfs) + ->Args({n_hedges, layer_width}) + ->Unit(benchmark::kMillisecond); + + benchmark:: + RegisterBenchmark("b_bfs/INCIDENCE/list", bm_gl_incidence_backward_bfs) + ->Args({n_hedges, layer_width}) + ->Unit(benchmark::kMillisecond); + benchmark:: + RegisterBenchmark("b_bfs/INCIDENCE/flat_list", bm_gl_incidence_backward_bfs) + ->Args({n_hedges, layer_width}) + ->Unit(benchmark::kMillisecond); + benchmark:: + RegisterBenchmark("b_bfs/INCIDENCE/matrix", bm_gl_incidence_backward_bfs) + ->Args({n_hedges, layer_width}) + ->Unit(benchmark::kMillisecond); + benchmark:: + RegisterBenchmark("b_bfs/INCIDENCE/flat_matrix", bm_gl_incidence_backward_bfs) + ->Args({n_hedges, layer_width}) + ->Unit(benchmark::kMillisecond); +} + +namespace { +bool _registered = []() { + gl_bench::runner::get().add_suite( + "hg-b-bfs", suite{.add_args = add_args, .register_benchmarks = register_benchmarks} + ); + return true; +}(); +} // namespace + +} // namespace gl_bench::hg_b_bfs diff --git a/benchmarks/suites/hg_bfs.cpp b/benchmarks/suites/hg_bfs.cpp index 9f3023fd..1f5fbf4b 100644 --- a/benchmarks/suites/hg_bfs.cpp +++ b/benchmarks/suites/hg_bfs.cpp @@ -1,20 +1,13 @@ -#include "gl/directional_tags.hpp" -#include "gl/types/properties.hpp" -#include "hgl/directional_tags.hpp" #include "runner.hpp" #include "suite.hpp" -#include +#include #include -#include #include -#include +#include #include #include -#include -#include -#include namespace gl_bench::hg_bfs { From 777c37ac50fab0041c9f14179a61583cf4fddcbf Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Mon, 1 Jun 2026 17:09:12 +0200 Subject: [PATCH 05/14] clang-format include patterns alignment --- .clang-format | 8 ++++---- benchmarks/suites/hg_bbfs.cpp | 4 ++-- benchmarks/suites/hg_bfs.cpp | 4 ++-- tests/source/hgl/test_conversion.cpp | 1 - tests/source/hgl/test_hypergraph.cpp | 1 - 5 files changed, 8 insertions(+), 10 deletions(-) diff --git a/.clang-format b/.clang-format index 24437d09..9215a1c4 100644 --- a/.clang-format +++ b/.clang-format @@ -78,13 +78,13 @@ BracedInitializerIndentWidth: 4 IncludeBlocks: Regroup IncludeCategories: - - Regex: '^".*"' # Internal includes inside double quotes + - Regex: '^".*"' # Internal includes inside double quotes Priority: 1 - - Regex: '^]+\.h(pp)?>' # Internal includes inside angle brackets + - Regex: '^<(h)?gl/[^>]+\.h(pp)?>' # Internal includes inside angle brackets Priority: 2 - - Regex: '<.*\.h(pp)?>' # External non-standard includes + - Regex: '<.*\.h(pp)?>' # External non-standard includes Priority: 3 - - Regex: '<.*>' # Standard library includes + - Regex: '<.*>' # Standard library includes Priority: 4 SortIncludes: CaseSensitive SortUsingDeclarations: LexicographicNumeric diff --git a/benchmarks/suites/hg_bbfs.cpp b/benchmarks/suites/hg_bbfs.cpp index 0e209d27..08c47dc4 100644 --- a/benchmarks/suites/hg_bbfs.cpp +++ b/benchmarks/suites/hg_bbfs.cpp @@ -3,12 +3,12 @@ #include #include - -#include #include #include #include +#include + namespace gl_bench::hg_b_bfs { // --- BF-Directed Hypergraph Topology Generator --- diff --git a/benchmarks/suites/hg_bfs.cpp b/benchmarks/suites/hg_bfs.cpp index 1f5fbf4b..cefc76fb 100644 --- a/benchmarks/suites/hg_bfs.cpp +++ b/benchmarks/suites/hg_bfs.cpp @@ -3,12 +3,12 @@ #include #include - -#include #include #include #include +#include + namespace gl_bench::hg_bfs { // --- Hypergraph Topology Generator --- diff --git a/tests/source/hgl/test_conversion.cpp b/tests/source/hgl/test_conversion.cpp index 1606a920..eda67cf4 100644 --- a/tests/source/hgl/test_conversion.cpp +++ b/tests/source/hgl/test_conversion.cpp @@ -2,7 +2,6 @@ #include #include - #include #include #include diff --git a/tests/source/hgl/test_hypergraph.cpp b/tests/source/hgl/test_hypergraph.cpp index 48626347..03c6abe2 100644 --- a/tests/source/hgl/test_hypergraph.cpp +++ b/tests/source/hgl/test_hypergraph.cpp @@ -5,7 +5,6 @@ #include "testing/hgl/types.hpp" #include - #include #include #include From 622bf7a06aaca0057a295a0de3f2169a2e42e7b5 Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Mon, 1 Jun 2026 17:10:39 +0200 Subject: [PATCH 06/14] hg_bbfs -> hg_b_bfs --- benchmarks/CMakeLists.txt | 2 +- benchmarks/suites/{hg_bbfs.cpp => hg_b_bfs.cpp} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename benchmarks/suites/{hg_bbfs.cpp => hg_b_bfs.cpp} (100%) diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt index 1d860452..c82dbcf5 100644 --- a/benchmarks/CMakeLists.txt +++ b/benchmarks/CMakeLists.txt @@ -35,7 +35,7 @@ add_executable(cpp-gl-bench source/runner.cpp suites/is_bipartite.cpp suites/hg_bfs.cpp - suites/hg_bbfs.cpp + suites/hg_b_bfs.cpp ) target_compile_options(cpp-gl-bench PRIVATE diff --git a/benchmarks/suites/hg_bbfs.cpp b/benchmarks/suites/hg_b_bfs.cpp similarity index 100% rename from benchmarks/suites/hg_bbfs.cpp rename to benchmarks/suites/hg_b_bfs.cpp From 8054e2ca1fccaf9aa245583cfe6775661ff1f424 Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Mon, 1 Jun 2026 17:29:06 +0200 Subject: [PATCH 07/14] hg_b_bfs impl refinement --- benchmarks/suites/hg_b_bfs.cpp | 198 ++++++++++++++++----------------- benchmarks/suites/hg_bfs.cpp | 32 ++---- 2 files changed, 110 insertions(+), 120 deletions(-) diff --git a/benchmarks/suites/hg_b_bfs.cpp b/benchmarks/suites/hg_b_bfs.cpp index 08c47dc4..9bfa506d 100644 --- a/benchmarks/suites/hg_b_bfs.cpp +++ b/benchmarks/suites/hg_b_bfs.cpp @@ -1,3 +1,4 @@ +#include "gl/algorithm/core.hpp" #include "runner.hpp" #include "suite.hpp" @@ -8,39 +9,30 @@ #include #include +#include namespace gl_bench::hg_b_bfs { -// --- BF-Directed Hypergraph Topology Generator --- +// --- Hypergraph Topology Generator --- -template -HypergraphType gen_bf_chain_hypergraph( - const std::size_t num_hyperedges, const std::size_t layer_width +template +HypergraphType gen_bf_overlapping_chain_hypergraph( + const std::size_t n_hyperedges, const std::size_t layer_width, const std::size_t stride ) { using id_type = typename HypergraphType::id_type; - // A layered chain: Hyperedge `e` connects layer `e` (tail) to layer `e+1` (head). - const auto n_vertices = static_cast((num_hyperedges + 1) * layer_width); - const auto n_hyperedges = static_cast(num_hyperedges); + const auto n_vertices = static_cast((n_hyperedges * stride) + layer_width); + HypergraphType hgraph{n_vertices, static_cast(n_hyperedges)}; - HypergraphType hgraph{n_vertices, n_hyperedges}; + for (id_type e = 0; e < static_cast(n_hyperedges); ++e) { + const auto tail_start = static_cast(e * stride); + const auto tail_end = tail_start + static_cast(layer_width); - for (id_type e = 0; e < n_hyperedges; ++e) { - std::vector tail; - std::vector head; - tail.reserve(layer_width); - head.reserve(layer_width); + const auto head_start = static_cast((e * stride) + stride); + const auto head_end = head_start + static_cast(layer_width); - const auto tail_start = static_cast(e * layer_width); - const auto head_start = static_cast((e + 1) * layer_width); - - for (std::size_t k = 0; k < layer_width; ++k) { - tail.push_back(tail_start + static_cast(k)); - head.push_back(head_start + static_cast(k)); - } - - hgraph.bind_tail(tail, e); - hgraph.bind_head(head, e); + hgraph.bind_tail(std::views::iota(tail_start, tail_end), e); + hgraph.bind_head(std::views::iota(head_start, head_end), e); } return hgraph; @@ -50,20 +42,18 @@ HypergraphType gen_bf_chain_hypergraph( template void bm_hgl_backward_bfs(benchmark::State& state) { + using id_type = typename Hypergraph::id_type; + const auto n_hedges = static_cast(state.range(0)); const auto layer_width = static_cast(state.range(1)); + const auto stride = static_cast(state.range(2)); - auto hg = gen_bf_chain_hypergraph(n_hedges, layer_width); + auto hg = gen_bf_overlapping_chain_hypergraph(n_hedges, layer_width, stride); - // Initial roots: All vertices in Layer 0 - std::vector roots; - roots.reserve(layer_width); - for (std::size_t i = 0; i < layer_width; ++i) { - roots.push_back(static_cast(i)); - } + auto roots = std::views::iota(id_type{0}, static_cast(layer_width)) + | std::ranges::to>(); for (auto _ : state) { - // Run native B-BFS auto search_tree = hgl::algorithm::backward_bfs(hg, roots); benchmark::DoNotOptimize(search_tree); } @@ -73,82 +63,85 @@ void bm_hgl_backward_bfs(benchmark::State& state) { state.counters["Incidences"] = static_cast(n_hedges * layer_width * 2); } -// --- GL Incidence Graph Backward BFS Equivalent --- +// --- Incidence Graph Utilities --- + +/// @brief Executes a Backward BFS equivalent on an Incidence Graph. +template +bool incidence_backward_bfs( + const IncidenceGraph& ig, + const std::vector& roots, + const typename IncidenceGraph::id_type original_n_vertices +) { + using id_type = typename IncidenceGraph::id_type; + + std::vector visited_v(original_n_vertices, false); + auto tail_unvisited = + ig.in_degree_map() | std::views::drop(original_n_vertices) + | std::ranges::to>(); + + auto root_nodes = + roots | std::views::transform([](const id_type root_id) { + return gl::algorithm::search_node{root_id}; + }) + | std::ranges::to(); + + auto visit_vertex_pred = [&](id_type v) { + if (v < original_n_vertices) + return not visited_v[gl::to_idx(v)]; + return true; + }; + + auto visit = [&](id_type v, id_type /*p*/) { + if (v < original_n_vertices) + visited_v[gl::to_idx(v)] = true; + return true; + }; + + auto enqueue_node_pred = + [&](id_type target_id, const auto& /*edge*/) -> gl::algorithm::decision { + if (target_id >= original_n_vertices) { + const auto he_idx = target_id - original_n_vertices; + return --tail_unvisited[gl::to_idx(he_idx)] == 0uz; + } + else { + return not visited_v[gl::to_idx(target_id)]; + } + }; + + return gl::algorithm::bfs(ig, root_nodes, visit_vertex_pred, visit, enqueue_node_pred); +} + +// --- GL Incidence Graph Backward BFS Benchmark --- template < hgl::traits::c_bf_directed_hypergraph Hypergraph, gl::traits::c_directed_graph IncidenceGraph> void bm_gl_incidence_backward_bfs(benchmark::State& state) { - using id_type = typename Hypergraph::id_type; + using id_type = typename IncidenceGraph::id_type; const auto n_hedges = static_cast(state.range(0)); const auto layer_width = static_cast(state.range(1)); + const auto stride = static_cast(state.range(2)); - // Memory Guard for Matrix Representations if constexpr (gl::traits::c_adjacency_matrix_graph) { - const auto n_vertices = (n_hedges + 1) * layer_width; + const auto n_vertices = (n_hedges * stride) + layer_width; const auto ig_vertices = n_vertices + n_hedges; - // Skip if matrix requires > 4.5 GB of contiguous RAM - if (ig_vertices > 35000) { - state.SkipWithError("Matrix requires > 4.5 GB of memory; skipping."); + // Hardcoded safety limit for ~16 GB of RAM (V_ig = 65,000) + if (ig_vertices > 65000) { + state.SkipWithError("Matrix requires > 16.0 GB of memory; skipping."); return; } } - auto hg = gen_bf_chain_hypergraph(n_hedges, layer_width); + auto hg = gen_bf_overlapping_chain_hypergraph(n_hedges, layer_width, stride); auto ig = hgl::incidence_graph(hg); - // Create the search nodes for the initial queue from Layer 0 - std::vector> root_nodes; - root_nodes.reserve(layer_width); - for (std::size_t i = 0; i < layer_width; ++i) { - root_nodes.emplace_back(static_cast(i)); - } + auto roots = std::views::iota(id_type{0}, static_cast(layer_width)) + | std::ranges::to>(); for (auto _ : state) { - std::vector visited_v(hg.n_vertices(), false); - std::vector tail_unvisited(hg.n_hyperedges()); - - // The in-degree of a hyperedge node in a BF-directed incidence graph is exactly its tail size - for (std::size_t i = 0; i < hg.n_hyperedges(); ++i) { - tail_unvisited[i] = ig.in_degree(static_cast(hg.n_vertices() + i)); - } - - // 1. Visit Vertex Predicate: Ignore already visited original vertices - auto visit_vertex_pred = [&](typename IncidenceGraph::id_type v) { - if (v < hg.n_vertices()) { - return not visited_v[v]; - } - return true; // Hyperedge nodes bypass this check (handled by enqueue predicate) - }; - - // 2. Visit Callback: Mark original vertices as visited - auto visit = - [&](typename IncidenceGraph::id_type v, typename IncidenceGraph::id_type /*p*/) { - if (v < hg.n_vertices()) { - visited_v[v] = true; - } - return true; - }; - - // 3. Enqueue Predicate: The Blocking B-Reachability Logic - auto enqueue_node_pred = - [&](typename IncidenceGraph::id_type target_id, const auto& /*edge*/) { - if (target_id >= hg.n_vertices()) { - // If it's a hyperedge node, decrement its blocking counter - const auto he_idx = target_id - hg.n_vertices(); - return static_cast(--tail_unvisited[he_idx] == 0); - } - else { - // If it's a vertex node, enqueue only if unvisited - return static_cast(not visited_v[target_id]); - } - }; - - // Execute the custom BFS logic over the incidence graph - bool completed = - gl::algorithm::bfs(ig, root_nodes, visit_vertex_pred, visit, enqueue_node_pred); + bool completed = incidence_backward_bfs(ig, roots, static_cast(hg.n_vertices())); benchmark::DoNotOptimize(completed); } @@ -163,14 +156,19 @@ void add_args(argon::argument_parser& parser) { parser.add_optional_argument(group, "hg-b-bfs-e") .default_values(1000uz) .help("Number of BF-directed hyperedges"); - parser.add_optional_argument(group, "hg-b-bfs-width") - .default_values(100uz) + parser.add_optional_argument(group, "hg-b-bfs-layer-width") + .default_values(10uz) .help("Number of vertices per layer (tail and head sizes)"); + parser.add_optional_argument(group, "hg-b-bfs-stride") + .default_values(2uz) + .help("Vertex shift between consecutive hyperedges (smaller = denser)"); } void register_benchmarks(const argon::argument_parser& parser) { const auto n_hedges = static_cast(parser.value("hg-b-bfs-e")); - const auto layer_width = static_cast(parser.value("hg-b-bfs-width")); + const auto layer_width = + static_cast(parser.value("hg-b-bfs-layer-width")); + const auto stride = static_cast(parser.value("hg-b-bfs-stride")); // Standard Incidence Graphs MUST be directed for BF-Directed Hypergraphs using gl_list = gl::list_graph; @@ -191,43 +189,43 @@ void register_benchmarks(const argon::argument_parser& parser) { hgl::flat_matrix_hypergraph; benchmark::RegisterBenchmark("b_bfs/HGL/list", bm_hgl_backward_bfs) - ->Args({n_hedges, layer_width}) + ->Args({n_hedges, layer_width, stride}) ->Unit(benchmark::kMillisecond); benchmark::RegisterBenchmark("b_bfs/HGL/flat_list", bm_hgl_backward_bfs) - ->Args({n_hedges, layer_width}) + ->Args({n_hedges, layer_width, stride}) ->Unit(benchmark::kMillisecond); benchmark::RegisterBenchmark("b_bfs/HGL/matrix/v_major", bm_hgl_backward_bfs) - ->Args({n_hedges, layer_width}) + ->Args({n_hedges, layer_width, stride}) ->Unit(benchmark::kMillisecond); benchmark::RegisterBenchmark("b_bfs/HGL/matrix/e_major", bm_hgl_backward_bfs) - ->Args({n_hedges, layer_width}) + ->Args({n_hedges, layer_width, stride}) ->Unit(benchmark::kMillisecond); benchmark:: RegisterBenchmark("b_bfs/HGL/flat_matrix/v_major", bm_hgl_backward_bfs) - ->Args({n_hedges, layer_width}) + ->Args({n_hedges, layer_width, stride}) ->Unit(benchmark::kMillisecond); benchmark:: RegisterBenchmark("b_bfs/HGL/flat_matrix/e_major", bm_hgl_backward_bfs) - ->Args({n_hedges, layer_width}) + ->Args({n_hedges, layer_width, stride}) ->Unit(benchmark::kMillisecond); benchmark:: RegisterBenchmark("b_bfs/INCIDENCE/list", bm_gl_incidence_backward_bfs) - ->Args({n_hedges, layer_width}) + ->Args({n_hedges, layer_width, stride}) ->Unit(benchmark::kMillisecond); benchmark:: RegisterBenchmark("b_bfs/INCIDENCE/flat_list", bm_gl_incidence_backward_bfs) - ->Args({n_hedges, layer_width}) + ->Args({n_hedges, layer_width, stride}) ->Unit(benchmark::kMillisecond); benchmark:: RegisterBenchmark("b_bfs/INCIDENCE/matrix", bm_gl_incidence_backward_bfs) - ->Args({n_hedges, layer_width}) + ->Args({n_hedges, layer_width, stride}) ->Unit(benchmark::kMillisecond); benchmark:: RegisterBenchmark("b_bfs/INCIDENCE/flat_matrix", bm_gl_incidence_backward_bfs) - ->Args({n_hedges, layer_width}) + ->Args({n_hedges, layer_width, stride}) ->Unit(benchmark::kMillisecond); } diff --git a/benchmarks/suites/hg_bfs.cpp b/benchmarks/suites/hg_bfs.cpp index cefc76fb..020561d6 100644 --- a/benchmarks/suites/hg_bfs.cpp +++ b/benchmarks/suites/hg_bfs.cpp @@ -13,28 +13,20 @@ namespace gl_bench::hg_bfs { // --- Hypergraph Topology Generator --- -template +template HypergraphType gen_sliding_window_hypergraph( - const std::size_t num_hyperedges, const std::size_t degree, const std::size_t stride + const std::size_t n_hyperedges, const std::size_t degree, const std::size_t stride ) { using id_type = typename HypergraphType::id_type; // V = (E - 1) * stride + degree - const auto n_vertices = static_cast((num_hyperedges - 1) * stride + degree); - const auto n_hyperedges = static_cast(num_hyperedges); + const auto n_vertices = static_cast((n_hyperedges - 1) * stride + degree); + HypergraphType hgraph{n_vertices, static_cast(n_hyperedges)}; - HypergraphType hgraph{n_vertices, n_hyperedges}; - - for (id_type e = 0; e < n_hyperedges; ++e) { - std::vector e_vertices; - e_vertices.reserve(degree); - - const id_type start_v = static_cast(e * stride); - for (std::size_t k = 0; k < degree; ++k) { - e_vertices.push_back(start_v + static_cast(k)); - } - - hgraph.bind(e_vertices, e); + for (id_type e = 0; e < static_cast(n_hyperedges); ++e) { + const auto start_v = static_cast(e * stride); + const auto end_v = start_v + static_cast(degree); + hgraph.bind(std::views::iota(start_v, end_v), e); } return hgraph; @@ -74,10 +66,10 @@ void bm_gl_incidence_bfs(benchmark::State& state) { const auto n_vertices = (n_hedges - 1) * stride + he_size; const auto ig_vertices = n_vertices + n_hedges; - // If V_ig > 35,000, the matrix takes > 4.5 GB of contiguous RAM. - if (ig_vertices > 35000) { - state.SkipWithError("Matrix requires > 4.5 GB of memory; skipping."); - return; // MUST RETURN HERE to prevent std::bad_alloc + // Hardcoded safety limit for ~16 GB of RAM (V_ig = 65,000) + if (ig_vertices > 65000) { + state.SkipWithError("Matrix requires > 16.0 GB of memory; skipping."); + return; } } From fcff16037392b14f85d7f6d5d5624ad7e50a417a Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Tue, 2 Jun 2026 09:47:39 +0200 Subject: [PATCH 08/14] hgl benchmarks - flat list topology generator overloads --- benchmarks/suites/hg_b_bfs.cpp | 83 ++++++++++++++++++++------------ benchmarks/suites/hg_bfs.cpp | 86 ++++++++++++++++++++++------------ 2 files changed, 108 insertions(+), 61 deletions(-) diff --git a/benchmarks/suites/hg_b_bfs.cpp b/benchmarks/suites/hg_b_bfs.cpp index 9bfa506d..508cad81 100644 --- a/benchmarks/suites/hg_b_bfs.cpp +++ b/benchmarks/suites/hg_b_bfs.cpp @@ -38,6 +38,22 @@ HypergraphType gen_bf_overlapping_chain_hypergraph( return hgraph; } +template +requires hgl::traits::c_flat_list_hypergraph +HypergraphType gen_bf_overlapping_chain_hypergraph( + const std::size_t n_hyperedges, const std::size_t layer_width, const std::size_t stride +) { + using traits_type = typename HypergraphType::traits_type; + using layout_tag = typename traits_type::layout_tag; + + using list_repr_tag = hgl::repr::list_t; + using list_hypergraph = hgl::traits::swap_repr_tag_t; + + return hgl::to( + gen_bf_overlapping_chain_hypergraph(n_hyperedges, layer_width, stride) + ); +} + // --- Native HGL Backward BFS Benchmark --- template @@ -162,6 +178,8 @@ void add_args(argon::argument_parser& parser) { parser.add_optional_argument(group, "hg-b-bfs-stride") .default_values(2uz) .help("Vertex shift between consecutive hyperedges (smaller = denser)"); + parser.add_flag("hg-b-bfs-l").help("Execute the benchmark for list models"); + parser.add_flag("hg-b-bfs-m").help("Execute the benchmark for matrix models"); } void register_benchmarks(const argon::argument_parser& parser) { @@ -188,45 +206,50 @@ void register_benchmarks(const argon::argument_parser& parser) { using hgl_e_flat_matrix = hgl::flat_matrix_hypergraph; - benchmark::RegisterBenchmark("b_bfs/HGL/list", bm_hgl_backward_bfs) - ->Args({n_hedges, layer_width, stride}) - ->Unit(benchmark::kMillisecond); - benchmark::RegisterBenchmark("b_bfs/HGL/flat_list", bm_hgl_backward_bfs) - ->Args({n_hedges, layer_width, stride}) - ->Unit(benchmark::kMillisecond); - - benchmark::RegisterBenchmark("b_bfs/HGL/matrix/v_major", bm_hgl_backward_bfs) - ->Args({n_hedges, layer_width, stride}) - ->Unit(benchmark::kMillisecond); - benchmark::RegisterBenchmark("b_bfs/HGL/matrix/e_major", bm_hgl_backward_bfs) - ->Args({n_hedges, layer_width, stride}) - ->Unit(benchmark::kMillisecond); - - benchmark:: - RegisterBenchmark("b_bfs/HGL/flat_matrix/v_major", bm_hgl_backward_bfs) + if (parser.value("hg-b-bfs-l")) { + benchmark::RegisterBenchmark("b_bfs/HGL/list", bm_hgl_backward_bfs) ->Args({n_hedges, layer_width, stride}) ->Unit(benchmark::kMillisecond); - benchmark:: - RegisterBenchmark("b_bfs/HGL/flat_matrix/e_major", bm_hgl_backward_bfs) + benchmark::RegisterBenchmark("b_bfs/HGL/flat_list", bm_hgl_backward_bfs) ->Args({n_hedges, layer_width, stride}) ->Unit(benchmark::kMillisecond); - benchmark:: - RegisterBenchmark("b_bfs/INCIDENCE/list", bm_gl_incidence_backward_bfs) - ->Args({n_hedges, layer_width, stride}) - ->Unit(benchmark::kMillisecond); - benchmark:: - RegisterBenchmark("b_bfs/INCIDENCE/flat_list", bm_gl_incidence_backward_bfs) - ->Args({n_hedges, layer_width, stride}) - ->Unit(benchmark::kMillisecond); - benchmark:: - RegisterBenchmark("b_bfs/INCIDENCE/matrix", bm_gl_incidence_backward_bfs) + benchmark:: + RegisterBenchmark("b_bfs/INCIDENCE/list", bm_gl_incidence_backward_bfs) + ->Args({n_hedges, layer_width, stride}) + ->Unit(benchmark::kMillisecond); + benchmark:: + RegisterBenchmark("b_bfs/INCIDENCE/flat_list", bm_gl_incidence_backward_bfs) + ->Args({n_hedges, layer_width, stride}) + ->Unit(benchmark::kMillisecond); + } + + if (parser.value("hg-b-bfs-m")) { + benchmark::RegisterBenchmark("b_bfs/HGL/matrix/v_major", bm_hgl_backward_bfs) ->Args({n_hedges, layer_width, stride}) ->Unit(benchmark::kMillisecond); - benchmark:: - RegisterBenchmark("b_bfs/INCIDENCE/flat_matrix", bm_gl_incidence_backward_bfs) + benchmark::RegisterBenchmark("b_bfs/HGL/matrix/e_major", bm_hgl_backward_bfs) ->Args({n_hedges, layer_width, stride}) ->Unit(benchmark::kMillisecond); + + benchmark:: + RegisterBenchmark("b_bfs/HGL/flat_matrix/v_major", bm_hgl_backward_bfs) + ->Args({n_hedges, layer_width, stride}) + ->Unit(benchmark::kMillisecond); + benchmark:: + RegisterBenchmark("b_bfs/HGL/flat_matrix/e_major", bm_hgl_backward_bfs) + ->Args({n_hedges, layer_width, stride}) + ->Unit(benchmark::kMillisecond); + + benchmark:: + RegisterBenchmark("b_bfs/INCIDENCE/matrix", bm_gl_incidence_backward_bfs) + ->Args({n_hedges, layer_width, stride}) + ->Unit(benchmark::kMillisecond); + benchmark:: + RegisterBenchmark("b_bfs/INCIDENCE/flat_matrix", bm_gl_incidence_backward_bfs) + ->Args({n_hedges, layer_width, stride}) + ->Unit(benchmark::kMillisecond); + } } namespace { diff --git a/benchmarks/suites/hg_bfs.cpp b/benchmarks/suites/hg_bfs.cpp index 020561d6..b323b853 100644 --- a/benchmarks/suites/hg_bfs.cpp +++ b/benchmarks/suites/hg_bfs.cpp @@ -32,6 +32,22 @@ HypergraphType gen_sliding_window_hypergraph( return hgraph; } +template +requires hgl::traits::c_flat_list_hypergraph +HypergraphType gen_sliding_window_hypergraph( + const std::size_t n_hyperedges, const std::size_t degree, const std::size_t stride +) { + using traits_type = typename HypergraphType::traits_type; + using layout_tag = typename traits_type::layout_tag; + + using list_repr_tag = hgl::repr::list_t; + using list_hypergraph = hgl::traits::swap_repr_tag_t; + + return hgl::to( + gen_sliding_window_hypergraph(n_hyperedges, degree, stride) + ); +} + // --- HGL Benchmark --- template @@ -96,6 +112,8 @@ void add_args(argon::argument_parser& parser) { parser.add_optional_argument(group, "hg-bfs-stride") .default_values(2uz) .help("Vertex shift between consecutive hyperedges (smaller = denser)"); + parser.add_flag("hg-bfs-l").help("Execute the benchmark for list models"); + parser.add_flag("hg-bfs-m").help("Execute the benchmark for matrix models"); } void register_benchmarks(const argon::argument_parser& parser) { @@ -119,41 +137,47 @@ void register_benchmarks(const argon::argument_parser& parser) { using hgl_e_flat_matrix = hgl::flat_matrix_hypergraph; - benchmark::RegisterBenchmark("bfs/HGL/list", bm_hgl_bfs) - ->Args({n_hedges, he_size, stride}) - ->Unit(benchmark::kMillisecond); - benchmark::RegisterBenchmark("bfs/HGL/flat_list", bm_hgl_bfs) - ->Args({n_hedges, he_size, stride}) - ->Unit(benchmark::kMillisecond); - - benchmark::RegisterBenchmark("bfs/HGL/matrix/v_major", bm_hgl_bfs) - ->Args({n_hedges, he_size, stride}) - ->Unit(benchmark::kMillisecond); - benchmark::RegisterBenchmark("bfs/HGL/matrix/e_major", bm_hgl_bfs) - ->Args({n_hedges, he_size, stride}) - ->Unit(benchmark::kMillisecond); - - benchmark::RegisterBenchmark("bfs/HGL/flat_matrix/v_major", bm_hgl_bfs) - ->Args({n_hedges, he_size, stride}) - ->Unit(benchmark::kMillisecond); - benchmark::RegisterBenchmark("bfs/HGL/flat_matrix/e_major", bm_hgl_bfs) - ->Args({n_hedges, he_size, stride}) - ->Unit(benchmark::kMillisecond); - - benchmark::RegisterBenchmark("bfs/INCIDENCE/list", bm_gl_incidence_bfs) - ->Args({n_hedges, he_size, stride}) - ->Unit(benchmark::kMillisecond); - benchmark:: - RegisterBenchmark("bfs/INCIDENCE/flat_list", bm_gl_incidence_bfs) + if (parser.value("hg-bfs-l")) { + benchmark::RegisterBenchmark("bfs/HGL/list", bm_hgl_bfs) + ->Args({n_hedges, he_size, stride}) + ->Unit(benchmark::kMillisecond); + benchmark::RegisterBenchmark("bfs/HGL/flat_list", bm_hgl_bfs) ->Args({n_hedges, he_size, stride}) ->Unit(benchmark::kMillisecond); - benchmark::RegisterBenchmark("bfs/INCIDENCE/matrix", bm_gl_incidence_bfs) - ->Args({n_hedges, he_size, stride}) - ->Unit(benchmark::kMillisecond); - benchmark:: - RegisterBenchmark("bfs/INCIDENCE/flat_matrix", bm_gl_incidence_bfs) + + benchmark::RegisterBenchmark("bfs/INCIDENCE/list", bm_gl_incidence_bfs) ->Args({n_hedges, he_size, stride}) ->Unit(benchmark::kMillisecond); + benchmark:: + RegisterBenchmark("bfs/INCIDENCE/flat_list", bm_gl_incidence_bfs) + ->Args({n_hedges, he_size, stride}) + ->Unit(benchmark::kMillisecond); + } + + if (parser.value("hg-bfs-m")) { + benchmark::RegisterBenchmark("bfs/HGL/matrix/v_major", bm_hgl_bfs) + ->Args({n_hedges, he_size, stride}) + ->Unit(benchmark::kMillisecond); + benchmark::RegisterBenchmark("bfs/HGL/matrix/e_major", bm_hgl_bfs) + ->Args({n_hedges, he_size, stride}) + ->Unit(benchmark::kMillisecond); + + benchmark::RegisterBenchmark("bfs/HGL/flat_matrix/v_major", bm_hgl_bfs) + ->Args({n_hedges, he_size, stride}) + ->Unit(benchmark::kMillisecond); + benchmark::RegisterBenchmark("bfs/HGL/flat_matrix/e_major", bm_hgl_bfs) + ->Args({n_hedges, he_size, stride}) + ->Unit(benchmark::kMillisecond); + + benchmark:: + RegisterBenchmark("bfs/INCIDENCE/matrix", bm_gl_incidence_bfs) + ->Args({n_hedges, he_size, stride}) + ->Unit(benchmark::kMillisecond); + benchmark:: + RegisterBenchmark("bfs/INCIDENCE/flat_matrix", bm_gl_incidence_bfs) + ->Args({n_hedges, he_size, stride}) + ->Unit(benchmark::kMillisecond); + } } namespace { From cb2085c757613e4440c16881d063fbb9d4f77829 Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Tue, 2 Jun 2026 11:39:27 +0200 Subject: [PATCH 09/14] aligned the b-bfs benchmark topology --- benchmarks/suites/hg_b_bfs.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/benchmarks/suites/hg_b_bfs.cpp b/benchmarks/suites/hg_b_bfs.cpp index 508cad81..6ae4abf0 100644 --- a/benchmarks/suites/hg_b_bfs.cpp +++ b/benchmarks/suites/hg_b_bfs.cpp @@ -21,14 +21,16 @@ HypergraphType gen_bf_overlapping_chain_hypergraph( ) { using id_type = typename HypergraphType::id_type; - const auto n_vertices = static_cast((n_hyperedges * stride) + layer_width); + // V = maximum index reached by the last hyperedge's head. + const auto n_vertices = static_cast((n_hyperedges - 1) * stride + 2 * layer_width); + HypergraphType hgraph{n_vertices, static_cast(n_hyperedges)}; for (id_type e = 0; e < static_cast(n_hyperedges); ++e) { const auto tail_start = static_cast(e * stride); const auto tail_end = tail_start + static_cast(layer_width); - const auto head_start = static_cast((e * stride) + stride); + const auto head_start = tail_end; const auto head_end = head_start + static_cast(layer_width); hgraph.bind_tail(std::views::iota(tail_start, tail_end), e); From 8f06cfe03a9e89104b9bf98667c818c1bdfab7e7 Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Tue, 2 Jun 2026 11:58:04 +0200 Subject: [PATCH 10/14] hardened bf binding --- include/hgl/impl/flat_incidence_list.hpp | 34 ++-- include/hgl/impl/flat_incidence_matrix.hpp | 28 ++- include/hgl/impl/incidence_list.hpp | 34 ++-- include/hgl/impl/incidence_matrix.hpp | 28 ++- tests/source/hgl/test_flat_incidence_list.cpp | 128 +++++++------ .../source/hgl/test_flat_incidence_matrix.cpp | 168 +++++++++++++----- tests/source/hgl/test_incidence_list.cpp | 129 ++++++++------ tests/source/hgl/test_incidence_matrix.cpp | 168 +++++++++++++----- 8 files changed, 497 insertions(+), 220 deletions(-) diff --git a/include/hgl/impl/flat_incidence_list.hpp b/include/hgl/impl/flat_incidence_list.hpp index e2e1c738..ab5284fc 100644 --- a/include/hgl/impl/flat_incidence_list.hpp +++ b/include/hgl/impl/flat_incidence_list.hpp @@ -407,19 +407,33 @@ class flat_incidence_list final { // --- binding methods --- - gl_attr_force_inline void bind_tail( - const id_type vertex_id, const id_type hyperedge_id - ) noexcept { + gl_attr_force_inline void bind_tail(const id_type vertex_id, const id_type hyperedge_id) { const auto [major_id, minor_id] = layout_tag::majmin(vertex_id, hyperedge_id); - this->_remove_no_align(this->_head_storage, major_id, minor_id); + + if (detail::contains(this->_head_storage[to_idx(major_id)], minor_id)) { + throw std::logic_error(std::format( + "Tail and head sets must be disjoint: vertex {} is already bound to the head of " + "hyperedge {}.", + vertex_id, + hyperedge_id + )); + } + detail::unique_insert(this->_tail_storage, major_id, minor_id); } - gl_attr_force_inline void bind_head( - const id_type vertex_id, const id_type hyperedge_id - ) noexcept { + gl_attr_force_inline void bind_head(const id_type vertex_id, const id_type hyperedge_id) { const auto [major_id, minor_id] = layout_tag::majmin(vertex_id, hyperedge_id); - this->_remove_no_align(this->_tail_storage, major_id, minor_id); + + if (detail::contains(this->_tail_storage[to_idx(major_id)], minor_id)) { + throw std::logic_error(std::format( + "Tail and head sets must be disjoint: vertex {} is already bound to the tail of " + "hyperedge {}.", + vertex_id, + hyperedge_id + )); + } + detail::unique_insert(this->_head_storage, major_id, minor_id); } @@ -807,14 +821,14 @@ class flat_incidence_list final { return this->_e_list.are_bound(vertex_id, hyperedge_id); } - gl_attr_force_inline void bind_tail(const id_type vertex_id, const id_type hyperedge_id) noexcept + gl_attr_force_inline void bind_tail(const id_type vertex_id, const id_type hyperedge_id) requires std::same_as { this->_v_list.bind_tail(vertex_id, hyperedge_id); this->_e_list.bind_tail(vertex_id, hyperedge_id); } - gl_attr_force_inline void bind_head(const id_type vertex_id, const id_type hyperedge_id) noexcept + gl_attr_force_inline void bind_head(const id_type vertex_id, const id_type hyperedge_id) requires std::same_as { this->_v_list.bind_head(vertex_id, hyperedge_id); diff --git a/include/hgl/impl/flat_incidence_matrix.hpp b/include/hgl/impl/flat_incidence_matrix.hpp index 4a305eb0..8778c4b7 100644 --- a/include/hgl/impl/flat_incidence_matrix.hpp +++ b/include/hgl/impl/flat_incidence_matrix.hpp @@ -348,17 +348,33 @@ class flat_incidence_matrix final { // --- binding methods --- - gl_attr_force_inline void bind_tail( - const id_type vertex_id, const id_type hyperedge_id - ) noexcept { + gl_attr_force_inline void bind_tail(const id_type vertex_id, const id_type hyperedge_id) { const auto [major_id, minor_id] = layout_tag::majmin(vertex_id, hyperedge_id); + + if (this->_matrix[to_idx(major_id), to_idx(minor_id)] == bf_incidence::forward) { + throw std::logic_error(std::format( + "Tail and head sets must be disjoint: vertex {} is already bound to the head of " + "hyperedge {}.", + vertex_id, + hyperedge_id + )); + } + this->_matrix[to_idx(major_id), to_idx(minor_id)] = bf_incidence::backward; } - gl_attr_force_inline void bind_head( - const id_type vertex_id, const id_type hyperedge_id - ) noexcept { + gl_attr_force_inline void bind_head(const id_type vertex_id, const id_type hyperedge_id) { const auto [major_id, minor_id] = layout_tag::majmin(vertex_id, hyperedge_id); + + if (this->_matrix[to_idx(major_id), to_idx(minor_id)] == bf_incidence::backward) { + throw std::logic_error(std::format( + "Tail and head sets must be disjoint: vertex {} is already bound to the tail of " + "hyperedge {}.", + vertex_id, + hyperedge_id + )); + } + this->_matrix[to_idx(major_id), to_idx(minor_id)] = bf_incidence::forward; } diff --git a/include/hgl/impl/incidence_list.hpp b/include/hgl/impl/incidence_list.hpp index a2028475..075fa85e 100644 --- a/include/hgl/impl/incidence_list.hpp +++ b/include/hgl/impl/incidence_list.hpp @@ -362,21 +362,35 @@ class incidence_list final { // --- binding methods --- - gl_attr_force_inline void bind_tail( - const id_type vertex_id, const id_type hyperedge_id - ) noexcept { + gl_attr_force_inline void bind_tail(const id_type vertex_id, const id_type hyperedge_id) { const auto [major_id, minor_id] = layout_tag::majmin(vertex_id, hyperedge_id); const auto major_idx = to_idx(major_id); - this->_remove_no_align(this->_head_storage[major_idx], minor_id); + + if (this->_contains(this->_head_storage[major_idx], minor_id)) { + throw std::logic_error(std::format( + "Tail and head sets must be disjoint: vertex {} is already bound to the head of " + "hyperedge {}.", + vertex_id, + hyperedge_id + )); + } + this->_unique_insert(this->_tail_storage[major_idx], minor_id); } - gl_attr_force_inline void bind_head( - const id_type vertex_id, const id_type hyperedge_id - ) noexcept { + gl_attr_force_inline void bind_head(const id_type vertex_id, const id_type hyperedge_id) { const auto [major_id, minor_id] = layout_tag::majmin(vertex_id, hyperedge_id); const auto major_idx = to_idx(major_id); - this->_remove_no_align(this->_tail_storage[major_idx], minor_id); + + if (this->_contains(this->_tail_storage[major_idx], minor_id)) { + throw std::logic_error(std::format( + "Tail and head sets must be disjoint: vertex {} is already bound to the tail of " + "hyperedge {}.", + vertex_id, + hyperedge_id + )); + } + this->_unique_insert(this->_head_storage[major_idx], minor_id); } @@ -763,14 +777,14 @@ class incidence_list final { return this->_e_list.are_bound(vertex_id, hyperedge_id); } - gl_attr_force_inline void bind_tail(const id_type vertex_id, const id_type hyperedge_id) noexcept + gl_attr_force_inline void bind_tail(const id_type vertex_id, const id_type hyperedge_id) requires std::same_as { this->_v_list.bind_tail(vertex_id, hyperedge_id); this->_e_list.bind_tail(vertex_id, hyperedge_id); } - gl_attr_force_inline void bind_head(const id_type vertex_id, const id_type hyperedge_id) noexcept + gl_attr_force_inline void bind_head(const id_type vertex_id, const id_type hyperedge_id) requires std::same_as { this->_v_list.bind_head(vertex_id, hyperedge_id); diff --git a/include/hgl/impl/incidence_matrix.hpp b/include/hgl/impl/incidence_matrix.hpp index 3f3c96c6..c745c3f8 100644 --- a/include/hgl/impl/incidence_matrix.hpp +++ b/include/hgl/impl/incidence_matrix.hpp @@ -365,17 +365,33 @@ class incidence_matrix final { // --- binding methods --- - gl_attr_force_inline void bind_tail( - const id_type vertex_id, const id_type hyperedge_id - ) noexcept { + gl_attr_force_inline void bind_tail(const id_type vertex_id, const id_type hyperedge_id) { const auto [major_id, minor_id] = layout_tag::majmin(vertex_id, hyperedge_id); + + if (this->_matrix[to_idx(major_id)][to_idx(minor_id)] == bf_incidence::forward) { + throw std::logic_error(std::format( + "Tail and head sets must be disjoint: vertex {} is already bound to the head of " + "hyperedge {}.", + vertex_id, + hyperedge_id + )); + } + this->_matrix[to_idx(major_id)][to_idx(minor_id)] = bf_incidence::backward; } - gl_attr_force_inline void bind_head( - const id_type vertex_id, const id_type hyperedge_id - ) noexcept { + gl_attr_force_inline void bind_head(const id_type vertex_id, const id_type hyperedge_id) { const auto [major_id, minor_id] = layout_tag::majmin(vertex_id, hyperedge_id); + + if (this->_matrix[to_idx(major_id)][to_idx(minor_id)] == bf_incidence::backward) { + throw std::logic_error(std::format( + "Tail and head sets must be disjoint: vertex {} is already bound to the tail of " + "hyperedge {}.", + vertex_id, + hyperedge_id + )); + } + this->_matrix[to_idx(major_id)][to_idx(minor_id)] = bf_incidence::forward; } diff --git a/tests/source/hgl/test_flat_incidence_list.cpp b/tests/source/hgl/test_flat_incidence_list.cpp index 4f1e29ae..75744558 100644 --- a/tests/source/hgl/test_flat_incidence_list.cpp +++ b/tests/source/hgl/test_flat_incidence_list.cpp @@ -927,7 +927,7 @@ TEST_CASE_FIXTURE( TEST_CASE_FIXTURE( test_bf_directed_vertex_major_flat_incidence_list, - "binding methods should rebind the elements if they are already bound" + "binding methods should throw if they are already bound" ) { sut_type sut{constants::n_vertices, constants::n_hyperedges}; constexpr auto vertex_id = constants::id1, hyperedge_id = constants::id2; @@ -936,23 +936,33 @@ TEST_CASE_FIXTURE( REQUIRE_FALSE(sut.is_head(vertex_id, hyperedge_id)); REQUIRE_FALSE(sut.is_head(vertex_id, hyperedge_id)); - // initial bind + // initial bind head sut.bind_head(vertex_id, hyperedge_id); CHECK(sut.are_bound(vertex_id, hyperedge_id)); CHECK(sut.is_head(vertex_id, hyperedge_id)); CHECK_FALSE(sut.is_tail(vertex_id, hyperedge_id)); // rebind tail + CHECK_THROWS_AS(sut.bind_tail(vertex_id, hyperedge_id), std::logic_error); + CHECK(sut.are_bound(vertex_id, hyperedge_id)); + CHECK(sut.is_head(vertex_id, hyperedge_id)); + CHECK_FALSE(sut.is_tail(vertex_id, hyperedge_id)); + + // unbind + sut.unbind(vertex_id, hyperedge_id); + CHECK_FALSE(sut.are_bound(vertex_id, hyperedge_id)); + + // initial bind tail sut.bind_tail(vertex_id, hyperedge_id); CHECK(sut.are_bound(vertex_id, hyperedge_id)); CHECK(sut.is_tail(vertex_id, hyperedge_id)); CHECK_FALSE(sut.is_head(vertex_id, hyperedge_id)); // rebind head - sut.bind_head(vertex_id, hyperedge_id); + CHECK_THROWS_AS(sut.bind_head(vertex_id, hyperedge_id), std::logic_error); CHECK(sut.are_bound(vertex_id, hyperedge_id)); - CHECK(sut.is_head(vertex_id, hyperedge_id)); - CHECK_FALSE(sut.is_tail(vertex_id, hyperedge_id)); + CHECK(sut.is_tail(vertex_id, hyperedge_id)); + CHECK_FALSE(sut.is_head(vertex_id, hyperedge_id)); } TEST_CASE_FIXTURE( @@ -1060,32 +1070,34 @@ TEST_CASE_FIXTURE( CHECK(std::ranges::all_of(sut.tail_size_map(n_elements), is_zero)); } - // diagonal = tail, everything else is head - for (auto i = 0u; i < n_elements; i++) { - for (auto j = 0u; j <= i; j++) { - if (i == j) - sut.bind_tail(i, j); - else - sut.bind_head(i, j); + SUBCASE("mixed bind") { + // diagonal = tail, everything else is head + for (auto i = 0u; i < n_elements; i++) { + for (auto j = 0u; j <= i; j++) { + if (i == j) + sut.bind_tail(i, j); + else + sut.bind_head(i, j); + } } - } - const auto deg_map = sut.degree_map(n_elements); - const auto out_deg_map = sut.out_degree_map(n_elements); - const auto in_deg_map = sut.in_degree_map(n_elements); + const auto deg_map = sut.degree_map(n_elements); + const auto out_deg_map = sut.out_degree_map(n_elements); + const auto in_deg_map = sut.in_degree_map(n_elements); - const auto esize_map = sut.hyperedge_size_map(n_elements); - const auto tsize_map = sut.tail_size_map(n_elements); - const auto hsize_map = sut.head_size_map(n_elements); + const auto esize_map = sut.hyperedge_size_map(n_elements); + const auto tsize_map = sut.tail_size_map(n_elements); + const auto hsize_map = sut.head_size_map(n_elements); - for (std::size_t i = 0uz; i < n_elements; i++) { - CHECK_EQ(deg_map[i], i + 1uz); - CHECK_EQ(out_deg_map[i], 1uz); - CHECK_EQ(in_deg_map[i], i); + for (std::size_t i = 0uz; i < n_elements; i++) { + CHECK_EQ(deg_map[i], i + 1uz); + CHECK_EQ(out_deg_map[i], 1uz); + CHECK_EQ(in_deg_map[i], i); - CHECK_EQ(esize_map[i], n_elements - i); - CHECK_EQ(tsize_map[i], 1uz); - CHECK_EQ(hsize_map[i], n_elements - i - 1uz); + CHECK_EQ(esize_map[i], n_elements - i); + CHECK_EQ(tsize_map[i], 1uz); + CHECK_EQ(hsize_map[i], n_elements - i - 1uz); + } } } @@ -1411,7 +1423,7 @@ TEST_CASE_FIXTURE( TEST_CASE_FIXTURE( test_bf_directed_hyperedge_major_flat_incidence_list, - "binding methods should rebind the elements if they are already bound" + "binding methods should throw if they are already bound" ) { sut_type sut{constants::n_vertices, constants::n_hyperedges}; constexpr auto vertex_id = constants::id1, hyperedge_id = constants::id2; @@ -1420,23 +1432,33 @@ TEST_CASE_FIXTURE( REQUIRE_FALSE(sut.is_head(vertex_id, hyperedge_id)); REQUIRE_FALSE(sut.is_head(vertex_id, hyperedge_id)); - // initial bind + // initial bind head sut.bind_head(vertex_id, hyperedge_id); CHECK(sut.are_bound(vertex_id, hyperedge_id)); CHECK(sut.is_head(vertex_id, hyperedge_id)); CHECK_FALSE(sut.is_tail(vertex_id, hyperedge_id)); // rebind tail + CHECK_THROWS_AS(sut.bind_tail(vertex_id, hyperedge_id), std::logic_error); + CHECK(sut.are_bound(vertex_id, hyperedge_id)); + CHECK(sut.is_head(vertex_id, hyperedge_id)); + CHECK_FALSE(sut.is_tail(vertex_id, hyperedge_id)); + + // unbind + sut.unbind(vertex_id, hyperedge_id); + CHECK_FALSE(sut.are_bound(vertex_id, hyperedge_id)); + + // initial bind tail sut.bind_tail(vertex_id, hyperedge_id); CHECK(sut.are_bound(vertex_id, hyperedge_id)); CHECK(sut.is_tail(vertex_id, hyperedge_id)); CHECK_FALSE(sut.is_head(vertex_id, hyperedge_id)); // rebind head - sut.bind_head(vertex_id, hyperedge_id); + CHECK_THROWS_AS(sut.bind_head(vertex_id, hyperedge_id), std::logic_error); CHECK(sut.are_bound(vertex_id, hyperedge_id)); - CHECK(sut.is_head(vertex_id, hyperedge_id)); - CHECK_FALSE(sut.is_tail(vertex_id, hyperedge_id)); + CHECK(sut.is_tail(vertex_id, hyperedge_id)); + CHECK_FALSE(sut.is_head(vertex_id, hyperedge_id)); } TEST_CASE_FIXTURE( @@ -1544,32 +1566,34 @@ TEST_CASE_FIXTURE( CHECK(std::ranges::all_of(sut.tail_size_map(n_elements), is_zero)); } - // diagonal = tail, everything else is head - for (auto i = 0u; i < n_elements; i++) { - for (auto j = 0u; j <= i; j++) { - if (i == j) - sut.bind_tail(i, j); - else - sut.bind_head(i, j); + SUBCASE("mixed bind") { + // diagonal = tail, everything else is head + for (auto i = 0u; i < n_elements; i++) { + for (auto j = 0u; j <= i; j++) { + if (i == j) + sut.bind_tail(i, j); + else + sut.bind_head(i, j); + } } - } - const auto deg_map = sut.degree_map(n_elements); - const auto out_deg_map = sut.out_degree_map(n_elements); - const auto in_deg_map = sut.in_degree_map(n_elements); + const auto deg_map = sut.degree_map(n_elements); + const auto out_deg_map = sut.out_degree_map(n_elements); + const auto in_deg_map = sut.in_degree_map(n_elements); - const auto esize_map = sut.hyperedge_size_map(n_elements); - const auto tsize_map = sut.tail_size_map(n_elements); - const auto hsize_map = sut.head_size_map(n_elements); + const auto esize_map = sut.hyperedge_size_map(n_elements); + const auto tsize_map = sut.tail_size_map(n_elements); + const auto hsize_map = sut.head_size_map(n_elements); - for (std::size_t i = 0uz; i < n_elements; i++) { - CHECK_EQ(deg_map[i], i + 1uz); - CHECK_EQ(out_deg_map[i], 1uz); - CHECK_EQ(in_deg_map[i], i); + for (std::size_t i = 0uz; i < n_elements; i++) { + CHECK_EQ(deg_map[i], i + 1uz); + CHECK_EQ(out_deg_map[i], 1uz); + CHECK_EQ(in_deg_map[i], i); - CHECK_EQ(esize_map[i], n_elements - i); - CHECK_EQ(tsize_map[i], 1uz); - CHECK_EQ(hsize_map[i], n_elements - i - 1uz); + CHECK_EQ(esize_map[i], n_elements - i); + CHECK_EQ(tsize_map[i], 1uz); + CHECK_EQ(hsize_map[i], n_elements - i - 1uz); + } } } diff --git a/tests/source/hgl/test_flat_incidence_matrix.cpp b/tests/source/hgl/test_flat_incidence_matrix.cpp index e4064362..9e01f67b 100644 --- a/tests/source/hgl/test_flat_incidence_matrix.cpp +++ b/tests/source/hgl/test_flat_incidence_matrix.cpp @@ -1013,6 +1013,46 @@ TEST_CASE_FIXTURE( CHECK(std::ranges::contains(vertices, constants::id1)); } +TEST_CASE_FIXTURE( + test_bf_directed_vertex_major_flat_incidence_matrix, + "binding methods should throw if they are already bound" +) { + sut_type sut{constants::n_vertices, constants::n_hyperedges}; + constexpr auto vertex_id = constants::id1, hyperedge_id = constants::id2; + + REQUIRE_FALSE(sut.are_bound(vertex_id, hyperedge_id)); + REQUIRE_FALSE(sut.is_head(vertex_id, hyperedge_id)); + REQUIRE_FALSE(sut.is_head(vertex_id, hyperedge_id)); + + // initial bind head + sut.bind_head(vertex_id, hyperedge_id); + CHECK(sut.are_bound(vertex_id, hyperedge_id)); + CHECK(sut.is_head(vertex_id, hyperedge_id)); + CHECK_FALSE(sut.is_tail(vertex_id, hyperedge_id)); + + // rebind tail + CHECK_THROWS_AS(sut.bind_tail(vertex_id, hyperedge_id), std::logic_error); + CHECK(sut.are_bound(vertex_id, hyperedge_id)); + CHECK(sut.is_head(vertex_id, hyperedge_id)); + CHECK_FALSE(sut.is_tail(vertex_id, hyperedge_id)); + + // unbind + sut.unbind(vertex_id, hyperedge_id); + CHECK_FALSE(sut.are_bound(vertex_id, hyperedge_id)); + + // initial bind tail + sut.bind_tail(vertex_id, hyperedge_id); + CHECK(sut.are_bound(vertex_id, hyperedge_id)); + CHECK(sut.is_tail(vertex_id, hyperedge_id)); + CHECK_FALSE(sut.is_head(vertex_id, hyperedge_id)); + + // rebind head + CHECK_THROWS_AS(sut.bind_head(vertex_id, hyperedge_id), std::logic_error); + CHECK(sut.are_bound(vertex_id, hyperedge_id)); + CHECK(sut.is_tail(vertex_id, hyperedge_id)); + CHECK_FALSE(sut.is_head(vertex_id, hyperedge_id)); +} + TEST_CASE_FIXTURE( test_bf_directed_vertex_major_flat_incidence_matrix, "unbind should clear the corresponding bit" ) { @@ -1113,32 +1153,34 @@ TEST_CASE_FIXTURE( CHECK(std::ranges::all_of(sut.tail_size_map(n_elements), is_zero)); } - // diagonal = tail, everything else is head - for (auto i = 0u; i < n_elements; i++) { - for (auto j = 0u; j <= i; j++) { - if (i == j) - sut.bind_tail(i, j); - else - sut.bind_head(i, j); + SUBCASE("mixed bind") { + // diagonal = tail, everything else is head + for (auto i = 0u; i < n_elements; i++) { + for (auto j = 0u; j <= i; j++) { + if (i == j) + sut.bind_tail(i, j); + else + sut.bind_head(i, j); + } } - } - const auto deg_map = sut.degree_map(n_elements); - const auto out_deg_map = sut.out_degree_map(n_elements); - const auto in_deg_map = sut.in_degree_map(n_elements); + const auto deg_map = sut.degree_map(n_elements); + const auto out_deg_map = sut.out_degree_map(n_elements); + const auto in_deg_map = sut.in_degree_map(n_elements); - const auto esize_map = sut.hyperedge_size_map(n_elements); - const auto tsize_map = sut.tail_size_map(n_elements); - const auto hsize_map = sut.head_size_map(n_elements); + const auto esize_map = sut.hyperedge_size_map(n_elements); + const auto tsize_map = sut.tail_size_map(n_elements); + const auto hsize_map = sut.head_size_map(n_elements); - for (auto i = 0uz; i < n_elements; i++) { - CHECK_EQ(deg_map[i], i + 1uz); - CHECK_EQ(out_deg_map[i], 1uz); - CHECK_EQ(in_deg_map[i], i); + for (auto i = 0uz; i < n_elements; i++) { + CHECK_EQ(deg_map[i], i + 1uz); + CHECK_EQ(out_deg_map[i], 1uz); + CHECK_EQ(in_deg_map[i], i); - CHECK_EQ(esize_map[i], n_elements - i); - CHECK_EQ(tsize_map[i], 1uz); - CHECK_EQ(hsize_map[i], n_elements - i - 1uz); + CHECK_EQ(esize_map[i], n_elements - i); + CHECK_EQ(tsize_map[i], 1uz); + CHECK_EQ(hsize_map[i], n_elements - i - 1uz); + } } } @@ -1468,6 +1510,46 @@ TEST_CASE_FIXTURE( CHECK(std::ranges::contains(vertices, constants::id1)); } +TEST_CASE_FIXTURE( + test_bf_directed_hyperedge_major_flat_incidence_matrix, + "binding methods should throw if they are already bound" +) { + sut_type sut{constants::n_vertices, constants::n_hyperedges}; + constexpr auto vertex_id = constants::id1, hyperedge_id = constants::id2; + + REQUIRE_FALSE(sut.are_bound(vertex_id, hyperedge_id)); + REQUIRE_FALSE(sut.is_head(vertex_id, hyperedge_id)); + REQUIRE_FALSE(sut.is_head(vertex_id, hyperedge_id)); + + // initial bind head + sut.bind_head(vertex_id, hyperedge_id); + CHECK(sut.are_bound(vertex_id, hyperedge_id)); + CHECK(sut.is_head(vertex_id, hyperedge_id)); + CHECK_FALSE(sut.is_tail(vertex_id, hyperedge_id)); + + // rebind tail + CHECK_THROWS_AS(sut.bind_tail(vertex_id, hyperedge_id), std::logic_error); + CHECK(sut.are_bound(vertex_id, hyperedge_id)); + CHECK(sut.is_head(vertex_id, hyperedge_id)); + CHECK_FALSE(sut.is_tail(vertex_id, hyperedge_id)); + + // unbind + sut.unbind(vertex_id, hyperedge_id); + CHECK_FALSE(sut.are_bound(vertex_id, hyperedge_id)); + + // initial bind tail + sut.bind_tail(vertex_id, hyperedge_id); + CHECK(sut.are_bound(vertex_id, hyperedge_id)); + CHECK(sut.is_tail(vertex_id, hyperedge_id)); + CHECK_FALSE(sut.is_head(vertex_id, hyperedge_id)); + + // rebind head + CHECK_THROWS_AS(sut.bind_head(vertex_id, hyperedge_id), std::logic_error); + CHECK(sut.are_bound(vertex_id, hyperedge_id)); + CHECK(sut.is_tail(vertex_id, hyperedge_id)); + CHECK_FALSE(sut.is_head(vertex_id, hyperedge_id)); +} + TEST_CASE_FIXTURE( test_bf_directed_hyperedge_major_flat_incidence_matrix, "unbind should clear the corresponding bit" @@ -1569,32 +1651,34 @@ TEST_CASE_FIXTURE( CHECK(std::ranges::all_of(sut.tail_size_map(n_elements), is_zero)); } - // diagonal = tail, everything else is head - for (auto i = 0u; i < n_elements; i++) { - for (auto j = 0u; j <= i; j++) { - if (i == j) - sut.bind_tail(i, j); - else - sut.bind_head(i, j); + SUBCASE("mixed bind") { + // diagonal = tail, everything else is head + for (auto i = 0u; i < n_elements; i++) { + for (auto j = 0u; j <= i; j++) { + if (i == j) + sut.bind_tail(i, j); + else + sut.bind_head(i, j); + } } - } - const auto deg_map = sut.degree_map(n_elements); - const auto out_deg_map = sut.out_degree_map(n_elements); - const auto in_deg_map = sut.in_degree_map(n_elements); + const auto deg_map = sut.degree_map(n_elements); + const auto out_deg_map = sut.out_degree_map(n_elements); + const auto in_deg_map = sut.in_degree_map(n_elements); - const auto esize_map = sut.hyperedge_size_map(n_elements); - const auto tsize_map = sut.tail_size_map(n_elements); - const auto hsize_map = sut.head_size_map(n_elements); + const auto esize_map = sut.hyperedge_size_map(n_elements); + const auto tsize_map = sut.tail_size_map(n_elements); + const auto hsize_map = sut.head_size_map(n_elements); - for (auto i = 0uz; i < n_elements; i++) { - CHECK_EQ(deg_map[i], i + 1uz); - CHECK_EQ(out_deg_map[i], 1uz); - CHECK_EQ(in_deg_map[i], i); + for (auto i = 0uz; i < n_elements; i++) { + CHECK_EQ(deg_map[i], i + 1uz); + CHECK_EQ(out_deg_map[i], 1uz); + CHECK_EQ(in_deg_map[i], i); - CHECK_EQ(esize_map[i], n_elements - i); - CHECK_EQ(tsize_map[i], 1uz); - CHECK_EQ(hsize_map[i], n_elements - i - 1uz); + CHECK_EQ(esize_map[i], n_elements - i); + CHECK_EQ(tsize_map[i], 1uz); + CHECK_EQ(hsize_map[i], n_elements - i - 1uz); + } } } diff --git a/tests/source/hgl/test_incidence_list.cpp b/tests/source/hgl/test_incidence_list.cpp index 4cd92169..4b80479a 100644 --- a/tests/source/hgl/test_incidence_list.cpp +++ b/tests/source/hgl/test_incidence_list.cpp @@ -7,6 +7,7 @@ #include #include +#include namespace hgl_testing { @@ -912,7 +913,7 @@ TEST_CASE_FIXTURE( TEST_CASE_FIXTURE( test_bf_directed_vertex_major_incidence_list, - "binding methods should rebind the elements if they are already bound" + "binding methods should throw if they are already bound" ) { sut_type sut{constants::n_vertices, constants::n_hyperedges}; constexpr auto vertex_id = constants::id1, hyperedge_id = constants::id2; @@ -921,23 +922,33 @@ TEST_CASE_FIXTURE( REQUIRE_FALSE(sut.is_head(vertex_id, hyperedge_id)); REQUIRE_FALSE(sut.is_head(vertex_id, hyperedge_id)); - // initial bind + // initial bind head sut.bind_head(vertex_id, hyperedge_id); CHECK(sut.are_bound(vertex_id, hyperedge_id)); CHECK(sut.is_head(vertex_id, hyperedge_id)); CHECK_FALSE(sut.is_tail(vertex_id, hyperedge_id)); // rebind tail + CHECK_THROWS_AS(sut.bind_tail(vertex_id, hyperedge_id), std::logic_error); + CHECK(sut.are_bound(vertex_id, hyperedge_id)); + CHECK(sut.is_head(vertex_id, hyperedge_id)); + CHECK_FALSE(sut.is_tail(vertex_id, hyperedge_id)); + + // unbind + sut.unbind(vertex_id, hyperedge_id); + CHECK_FALSE(sut.are_bound(vertex_id, hyperedge_id)); + + // initial bind tail sut.bind_tail(vertex_id, hyperedge_id); CHECK(sut.are_bound(vertex_id, hyperedge_id)); CHECK(sut.is_tail(vertex_id, hyperedge_id)); CHECK_FALSE(sut.is_head(vertex_id, hyperedge_id)); // rebind head - sut.bind_head(vertex_id, hyperedge_id); + CHECK_THROWS_AS(sut.bind_head(vertex_id, hyperedge_id), std::logic_error); CHECK(sut.are_bound(vertex_id, hyperedge_id)); - CHECK(sut.is_head(vertex_id, hyperedge_id)); - CHECK_FALSE(sut.is_tail(vertex_id, hyperedge_id)); + CHECK(sut.is_tail(vertex_id, hyperedge_id)); + CHECK_FALSE(sut.is_head(vertex_id, hyperedge_id)); } TEST_CASE_FIXTURE( @@ -1045,32 +1056,34 @@ TEST_CASE_FIXTURE( CHECK(std::ranges::all_of(sut.tail_size_map(n_elements), is_zero)); } - // diagonal = tail, everything else is head - for (auto i = 0u; i < n_elements; i++) { - for (auto j = 0u; j <= i; j++) { - if (i == j) - sut.bind_tail(i, j); - else - sut.bind_head(i, j); + SUBCASE("mixed bind") { + // diagonal = tail, everything else is head + for (auto i = 0u; i < n_elements; i++) { + for (auto j = 0u; j <= i; j++) { + if (i == j) + sut.bind_tail(i, j); + else + sut.bind_head(i, j); + } } - } - const auto deg_map = sut.degree_map(n_elements); - const auto out_deg_map = sut.out_degree_map(n_elements); - const auto in_deg_map = sut.in_degree_map(n_elements); + const auto deg_map = sut.degree_map(n_elements); + const auto out_deg_map = sut.out_degree_map(n_elements); + const auto in_deg_map = sut.in_degree_map(n_elements); - const auto esize_map = sut.hyperedge_size_map(n_elements); - const auto tsize_map = sut.tail_size_map(n_elements); - const auto hsize_map = sut.head_size_map(n_elements); + const auto esize_map = sut.hyperedge_size_map(n_elements); + const auto tsize_map = sut.tail_size_map(n_elements); + const auto hsize_map = sut.head_size_map(n_elements); - for (auto i = 0uz; i < n_elements; i++) { - CHECK_EQ(deg_map[i], i + 1uz); - CHECK_EQ(out_deg_map[i], 1uz); - CHECK_EQ(in_deg_map[i], i); + for (auto i = 0uz; i < n_elements; i++) { + CHECK_EQ(deg_map[i], i + 1uz); + CHECK_EQ(out_deg_map[i], 1uz); + CHECK_EQ(in_deg_map[i], i); - CHECK_EQ(esize_map[i], n_elements - i); - CHECK_EQ(tsize_map[i], 1uz); - CHECK_EQ(hsize_map[i], n_elements - i - 1uz); + CHECK_EQ(esize_map[i], n_elements - i); + CHECK_EQ(tsize_map[i], 1uz); + CHECK_EQ(hsize_map[i], n_elements - i - 1uz); + } } } @@ -1394,7 +1407,7 @@ TEST_CASE_FIXTURE( TEST_CASE_FIXTURE( test_bf_directed_hyperedge_major_incidence_list, - "binding methods should rebind the elements if they are already bound" + "binding methods should throw if they are already bound" ) { sut_type sut{constants::n_vertices, constants::n_hyperedges}; constexpr auto vertex_id = constants::id1, hyperedge_id = constants::id2; @@ -1403,23 +1416,33 @@ TEST_CASE_FIXTURE( REQUIRE_FALSE(sut.is_head(vertex_id, hyperedge_id)); REQUIRE_FALSE(sut.is_head(vertex_id, hyperedge_id)); - // initial bind + // initial bind head sut.bind_head(vertex_id, hyperedge_id); CHECK(sut.are_bound(vertex_id, hyperedge_id)); CHECK(sut.is_head(vertex_id, hyperedge_id)); CHECK_FALSE(sut.is_tail(vertex_id, hyperedge_id)); // rebind tail + CHECK_THROWS_AS(sut.bind_tail(vertex_id, hyperedge_id), std::logic_error); + CHECK(sut.are_bound(vertex_id, hyperedge_id)); + CHECK(sut.is_head(vertex_id, hyperedge_id)); + CHECK_FALSE(sut.is_tail(vertex_id, hyperedge_id)); + + // unbind + sut.unbind(vertex_id, hyperedge_id); + CHECK_FALSE(sut.are_bound(vertex_id, hyperedge_id)); + + // initial bind tail sut.bind_tail(vertex_id, hyperedge_id); CHECK(sut.are_bound(vertex_id, hyperedge_id)); CHECK(sut.is_tail(vertex_id, hyperedge_id)); CHECK_FALSE(sut.is_head(vertex_id, hyperedge_id)); // rebind head - sut.bind_head(vertex_id, hyperedge_id); + CHECK_THROWS_AS(sut.bind_head(vertex_id, hyperedge_id), std::logic_error); CHECK(sut.are_bound(vertex_id, hyperedge_id)); - CHECK(sut.is_head(vertex_id, hyperedge_id)); - CHECK_FALSE(sut.is_tail(vertex_id, hyperedge_id)); + CHECK(sut.is_tail(vertex_id, hyperedge_id)); + CHECK_FALSE(sut.is_head(vertex_id, hyperedge_id)); } TEST_CASE_FIXTURE( @@ -1526,32 +1549,34 @@ TEST_CASE_FIXTURE( CHECK(std::ranges::all_of(sut.tail_size_map(n_elements), is_zero)); } - // diagonal = tail, everything else is head - for (auto i = 0u; i < n_elements; i++) { - for (auto j = 0u; j <= i; j++) { - if (i == j) - sut.bind_tail(i, j); - else - sut.bind_head(i, j); + SUBCASE("mixed bind") { + // diagonal = tail, everything else is head + for (auto i = 0u; i < n_elements; i++) { + for (auto j = 0u; j <= i; j++) { + if (i == j) + sut.bind_tail(i, j); + else + sut.bind_head(i, j); + } } - } - const auto deg_map = sut.degree_map(n_elements); - const auto out_deg_map = sut.out_degree_map(n_elements); - const auto in_deg_map = sut.in_degree_map(n_elements); + const auto deg_map = sut.degree_map(n_elements); + const auto out_deg_map = sut.out_degree_map(n_elements); + const auto in_deg_map = sut.in_degree_map(n_elements); - const auto esize_map = sut.hyperedge_size_map(n_elements); - const auto tsize_map = sut.tail_size_map(n_elements); - const auto hsize_map = sut.head_size_map(n_elements); + const auto esize_map = sut.hyperedge_size_map(n_elements); + const auto tsize_map = sut.tail_size_map(n_elements); + const auto hsize_map = sut.head_size_map(n_elements); - for (auto i = 0uz; i < n_elements; i++) { - CHECK_EQ(deg_map[i], i + 1uz); - CHECK_EQ(out_deg_map[i], 1uz); - CHECK_EQ(in_deg_map[i], i); + for (auto i = 0uz; i < n_elements; i++) { + CHECK_EQ(deg_map[i], i + 1uz); + CHECK_EQ(out_deg_map[i], 1uz); + CHECK_EQ(in_deg_map[i], i); - CHECK_EQ(esize_map[i], n_elements - i); - CHECK_EQ(tsize_map[i], 1uz); - CHECK_EQ(hsize_map[i], n_elements - i - 1uz); + CHECK_EQ(esize_map[i], n_elements - i); + CHECK_EQ(tsize_map[i], 1uz); + CHECK_EQ(hsize_map[i], n_elements - i - 1uz); + } } } diff --git a/tests/source/hgl/test_incidence_matrix.cpp b/tests/source/hgl/test_incidence_matrix.cpp index f0edcaae..ca6f179d 100644 --- a/tests/source/hgl/test_incidence_matrix.cpp +++ b/tests/source/hgl/test_incidence_matrix.cpp @@ -1006,6 +1006,46 @@ TEST_CASE_FIXTURE( CHECK(std::ranges::contains(vertices, constants::id1)); } +TEST_CASE_FIXTURE( + test_bf_directed_vertex_major_incidence_matrix, + "binding methods should throw if they are already bound" +) { + sut_type sut{constants::n_vertices, constants::n_hyperedges}; + constexpr auto vertex_id = constants::id1, hyperedge_id = constants::id2; + + REQUIRE_FALSE(sut.are_bound(vertex_id, hyperedge_id)); + REQUIRE_FALSE(sut.is_head(vertex_id, hyperedge_id)); + REQUIRE_FALSE(sut.is_head(vertex_id, hyperedge_id)); + + // initial bind head + sut.bind_head(vertex_id, hyperedge_id); + CHECK(sut.are_bound(vertex_id, hyperedge_id)); + CHECK(sut.is_head(vertex_id, hyperedge_id)); + CHECK_FALSE(sut.is_tail(vertex_id, hyperedge_id)); + + // rebind tail + CHECK_THROWS_AS(sut.bind_tail(vertex_id, hyperedge_id), std::logic_error); + CHECK(sut.are_bound(vertex_id, hyperedge_id)); + CHECK(sut.is_head(vertex_id, hyperedge_id)); + CHECK_FALSE(sut.is_tail(vertex_id, hyperedge_id)); + + // unbind + sut.unbind(vertex_id, hyperedge_id); + CHECK_FALSE(sut.are_bound(vertex_id, hyperedge_id)); + + // initial bind tail + sut.bind_tail(vertex_id, hyperedge_id); + CHECK(sut.are_bound(vertex_id, hyperedge_id)); + CHECK(sut.is_tail(vertex_id, hyperedge_id)); + CHECK_FALSE(sut.is_head(vertex_id, hyperedge_id)); + + // rebind head + CHECK_THROWS_AS(sut.bind_head(vertex_id, hyperedge_id), std::logic_error); + CHECK(sut.are_bound(vertex_id, hyperedge_id)); + CHECK(sut.is_tail(vertex_id, hyperedge_id)); + CHECK_FALSE(sut.is_head(vertex_id, hyperedge_id)); +} + TEST_CASE_FIXTURE( test_bf_directed_vertex_major_incidence_matrix, "unbind should clear the corresponding bit" ) { @@ -1106,32 +1146,34 @@ TEST_CASE_FIXTURE( CHECK(std::ranges::all_of(sut.tail_size_map(n_elements), is_zero)); } - // diagonal = tail, everything else is head - for (auto i = 0u; i < n_elements; i++) { - for (auto j = 0u; j <= i; j++) { - if (i == j) - sut.bind_tail(i, j); - else - sut.bind_head(i, j); + SUBCASE("mixed bind") { + // diagonal = tail, everything else is head + for (auto i = 0u; i < n_elements; i++) { + for (auto j = 0u; j <= i; j++) { + if (i == j) + sut.bind_tail(i, j); + else + sut.bind_head(i, j); + } } - } - const auto deg_map = sut.degree_map(n_elements); - const auto out_deg_map = sut.out_degree_map(n_elements); - const auto in_deg_map = sut.in_degree_map(n_elements); + const auto deg_map = sut.degree_map(n_elements); + const auto out_deg_map = sut.out_degree_map(n_elements); + const auto in_deg_map = sut.in_degree_map(n_elements); - const auto esize_map = sut.hyperedge_size_map(n_elements); - const auto tsize_map = sut.tail_size_map(n_elements); - const auto hsize_map = sut.head_size_map(n_elements); + const auto esize_map = sut.hyperedge_size_map(n_elements); + const auto tsize_map = sut.tail_size_map(n_elements); + const auto hsize_map = sut.head_size_map(n_elements); - for (auto i = 0uz; i < n_elements; i++) { - CHECK_EQ(deg_map[i], i + 1uz); - CHECK_EQ(out_deg_map[i], 1uz); - CHECK_EQ(in_deg_map[i], i); + for (auto i = 0uz; i < n_elements; i++) { + CHECK_EQ(deg_map[i], i + 1uz); + CHECK_EQ(out_deg_map[i], 1uz); + CHECK_EQ(in_deg_map[i], i); - CHECK_EQ(esize_map[i], n_elements - i); - CHECK_EQ(tsize_map[i], 1uz); - CHECK_EQ(hsize_map[i], n_elements - i - 1uz); + CHECK_EQ(esize_map[i], n_elements - i); + CHECK_EQ(tsize_map[i], 1uz); + CHECK_EQ(hsize_map[i], n_elements - i - 1uz); + } } } @@ -1460,6 +1502,46 @@ TEST_CASE_FIXTURE( CHECK(std::ranges::contains(vertices, constants::id1)); } +TEST_CASE_FIXTURE( + test_bf_directed_hyperedge_major_incidence_matrix, + "binding methods should throw if they are already bound" +) { + sut_type sut{constants::n_vertices, constants::n_hyperedges}; + constexpr auto vertex_id = constants::id1, hyperedge_id = constants::id2; + + REQUIRE_FALSE(sut.are_bound(vertex_id, hyperedge_id)); + REQUIRE_FALSE(sut.is_head(vertex_id, hyperedge_id)); + REQUIRE_FALSE(sut.is_head(vertex_id, hyperedge_id)); + + // initial bind head + sut.bind_head(vertex_id, hyperedge_id); + CHECK(sut.are_bound(vertex_id, hyperedge_id)); + CHECK(sut.is_head(vertex_id, hyperedge_id)); + CHECK_FALSE(sut.is_tail(vertex_id, hyperedge_id)); + + // rebind tail + CHECK_THROWS_AS(sut.bind_tail(vertex_id, hyperedge_id), std::logic_error); + CHECK(sut.are_bound(vertex_id, hyperedge_id)); + CHECK(sut.is_head(vertex_id, hyperedge_id)); + CHECK_FALSE(sut.is_tail(vertex_id, hyperedge_id)); + + // unbind + sut.unbind(vertex_id, hyperedge_id); + CHECK_FALSE(sut.are_bound(vertex_id, hyperedge_id)); + + // initial bind tail + sut.bind_tail(vertex_id, hyperedge_id); + CHECK(sut.are_bound(vertex_id, hyperedge_id)); + CHECK(sut.is_tail(vertex_id, hyperedge_id)); + CHECK_FALSE(sut.is_head(vertex_id, hyperedge_id)); + + // rebind head + CHECK_THROWS_AS(sut.bind_head(vertex_id, hyperedge_id), std::logic_error); + CHECK(sut.are_bound(vertex_id, hyperedge_id)); + CHECK(sut.is_tail(vertex_id, hyperedge_id)); + CHECK_FALSE(sut.is_head(vertex_id, hyperedge_id)); +} + TEST_CASE_FIXTURE( test_bf_directed_hyperedge_major_incidence_matrix, "unbind should clear the corresponding bit" ) { @@ -1560,32 +1642,34 @@ TEST_CASE_FIXTURE( CHECK(std::ranges::all_of(sut.tail_size_map(n_elements), is_zero)); } - // diagonal = tail, everything else is head - for (auto i = 0u; i < n_elements; i++) { - for (auto j = 0u; j <= i; j++) { - if (i == j) - sut.bind_tail(i, j); - else - sut.bind_head(i, j); + SUBCASE("mixed bind") { + // diagonal = tail, everything else is head + for (auto i = 0u; i < n_elements; i++) { + for (auto j = 0u; j <= i; j++) { + if (i == j) + sut.bind_tail(i, j); + else + sut.bind_head(i, j); + } } - } - const auto deg_map = sut.degree_map(n_elements); - const auto out_deg_map = sut.out_degree_map(n_elements); - const auto in_deg_map = sut.in_degree_map(n_elements); + const auto deg_map = sut.degree_map(n_elements); + const auto out_deg_map = sut.out_degree_map(n_elements); + const auto in_deg_map = sut.in_degree_map(n_elements); - const auto esize_map = sut.hyperedge_size_map(n_elements); - const auto tsize_map = sut.tail_size_map(n_elements); - const auto hsize_map = sut.head_size_map(n_elements); + const auto esize_map = sut.hyperedge_size_map(n_elements); + const auto tsize_map = sut.tail_size_map(n_elements); + const auto hsize_map = sut.head_size_map(n_elements); - for (auto i = 0uz; i < n_elements; i++) { - CHECK_EQ(deg_map[i], i + 1uz); - CHECK_EQ(out_deg_map[i], 1uz); - CHECK_EQ(in_deg_map[i], i); + for (auto i = 0uz; i < n_elements; i++) { + CHECK_EQ(deg_map[i], i + 1uz); + CHECK_EQ(out_deg_map[i], 1uz); + CHECK_EQ(in_deg_map[i], i); - CHECK_EQ(esize_map[i], n_elements - i); - CHECK_EQ(tsize_map[i], 1uz); - CHECK_EQ(hsize_map[i], n_elements - i - 1uz); + CHECK_EQ(esize_map[i], n_elements - i); + CHECK_EQ(tsize_map[i], 1uz); + CHECK_EQ(hsize_map[i], n_elements - i - 1uz); + } } } From 3427077878096ad045ceec17f7244949fecea57b Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Wed, 3 Jun 2026 09:55:04 +0200 Subject: [PATCH 11/14] resolved comments --- .github/workflows/benchmarks.yaml | 3 ++- include/gl/graph.hpp | 2 +- include/hgl/impl/flat_incidence_list.hpp | 6 +++--- include/hgl/impl/flat_incidence_matrix.hpp | 6 +++--- include/hgl/impl/incidence_list.hpp | 6 +++--- include/hgl/impl/incidence_matrix.hpp | 6 +++--- 6 files changed, 15 insertions(+), 14 deletions(-) diff --git a/.github/workflows/benchmarks.yaml b/.github/workflows/benchmarks.yaml index b7d9adfb..e6ba7ac2 100644 --- a/.github/workflows/benchmarks.yaml +++ b/.github/workflows/benchmarks.yaml @@ -43,4 +43,5 @@ jobs: ./build_bench/benchmarks/cpp-gl-bench \ --benchmark_repetitions=1 --benchmark_display_aggregates_only=true \ --bip-v 100 \ - --hg-bfs-e 100 --hg-bfs-esize 5 + --hg-bfs-l --hg-bfs-m --hg-bfs-e 100 --hg-bfs-esize 5 --hg-bfs-stride 2 \ + --hg-b-bfs-l --hg-b-bfs-m --hg-b-bfs-e 100 --hg-b-bfs-layer-width 5 --hg-b-bfs-stride 2 diff --git a/include/gl/graph.hpp b/include/gl/graph.hpp index d8830d7b..a41a3e7d 100644 --- a/include/gl/graph.hpp +++ b/include/gl/graph.hpp @@ -1143,7 +1143,7 @@ class graph final { /// @param is The source input stream. /// @param g The graph instance to populate. /// @return The stream reference for chaining. - gl_attr_force_inline friend std::istream& operator>>(std::istream& is, graph& g) { + friend gl_attr_force_inline std::istream& operator>>(std::istream& is, graph& g) { return g._gsf_read(is); } diff --git a/include/hgl/impl/flat_incidence_list.hpp b/include/hgl/impl/flat_incidence_list.hpp index ab5284fc..8a6c55c1 100644 --- a/include/hgl/impl/flat_incidence_list.hpp +++ b/include/hgl/impl/flat_incidence_list.hpp @@ -407,7 +407,7 @@ class flat_incidence_list final { // --- binding methods --- - gl_attr_force_inline void bind_tail(const id_type vertex_id, const id_type hyperedge_id) { + void bind_tail(const id_type vertex_id, const id_type hyperedge_id) { const auto [major_id, minor_id] = layout_tag::majmin(vertex_id, hyperedge_id); if (detail::contains(this->_head_storage[to_idx(major_id)], minor_id)) { @@ -422,7 +422,7 @@ class flat_incidence_list final { detail::unique_insert(this->_tail_storage, major_id, minor_id); } - gl_attr_force_inline void bind_head(const id_type vertex_id, const id_type hyperedge_id) { + void bind_head(const id_type vertex_id, const id_type hyperedge_id) { const auto [major_id, minor_id] = layout_tag::majmin(vertex_id, hyperedge_id); if (detail::contains(this->_tail_storage[to_idx(major_id)], minor_id)) { @@ -437,7 +437,7 @@ class flat_incidence_list final { detail::unique_insert(this->_head_storage, major_id, minor_id); } - gl_attr_force_inline void unbind(const id_type vertex_id, const id_type hyperedge_id) noexcept { + void unbind(const id_type vertex_id, const id_type hyperedge_id) noexcept { const auto [major_id, minor_id] = layout_tag::majmin(vertex_id, hyperedge_id); this->_remove_no_align(this->_tail_storage, major_id, minor_id); this->_remove_no_align(this->_head_storage, major_id, minor_id); diff --git a/include/hgl/impl/flat_incidence_matrix.hpp b/include/hgl/impl/flat_incidence_matrix.hpp index 8778c4b7..b73552f5 100644 --- a/include/hgl/impl/flat_incidence_matrix.hpp +++ b/include/hgl/impl/flat_incidence_matrix.hpp @@ -348,7 +348,7 @@ class flat_incidence_matrix final { // --- binding methods --- - gl_attr_force_inline void bind_tail(const id_type vertex_id, const id_type hyperedge_id) { + void bind_tail(const id_type vertex_id, const id_type hyperedge_id) { const auto [major_id, minor_id] = layout_tag::majmin(vertex_id, hyperedge_id); if (this->_matrix[to_idx(major_id), to_idx(minor_id)] == bf_incidence::forward) { @@ -363,7 +363,7 @@ class flat_incidence_matrix final { this->_matrix[to_idx(major_id), to_idx(minor_id)] = bf_incidence::backward; } - gl_attr_force_inline void bind_head(const id_type vertex_id, const id_type hyperedge_id) { + void bind_head(const id_type vertex_id, const id_type hyperedge_id) { const auto [major_id, minor_id] = layout_tag::majmin(vertex_id, hyperedge_id); if (this->_matrix[to_idx(major_id), to_idx(minor_id)] == bf_incidence::backward) { @@ -378,7 +378,7 @@ class flat_incidence_matrix final { this->_matrix[to_idx(major_id), to_idx(minor_id)] = bf_incidence::forward; } - gl_attr_force_inline void unbind(const id_type vertex_id, const id_type hyperedge_id) noexcept { + void unbind(const id_type vertex_id, const id_type hyperedge_id) noexcept { const auto [major_id, minor_id] = layout_tag::majmin(vertex_id, hyperedge_id); this->_matrix[to_idx(major_id), to_idx(minor_id)] = bf_incidence::none; } diff --git a/include/hgl/impl/incidence_list.hpp b/include/hgl/impl/incidence_list.hpp index 075fa85e..624339f0 100644 --- a/include/hgl/impl/incidence_list.hpp +++ b/include/hgl/impl/incidence_list.hpp @@ -362,7 +362,7 @@ class incidence_list final { // --- binding methods --- - gl_attr_force_inline void bind_tail(const id_type vertex_id, const id_type hyperedge_id) { + void bind_tail(const id_type vertex_id, const id_type hyperedge_id) { const auto [major_id, minor_id] = layout_tag::majmin(vertex_id, hyperedge_id); const auto major_idx = to_idx(major_id); @@ -378,7 +378,7 @@ class incidence_list final { this->_unique_insert(this->_tail_storage[major_idx], minor_id); } - gl_attr_force_inline void bind_head(const id_type vertex_id, const id_type hyperedge_id) { + void bind_head(const id_type vertex_id, const id_type hyperedge_id) { const auto [major_id, minor_id] = layout_tag::majmin(vertex_id, hyperedge_id); const auto major_idx = to_idx(major_id); @@ -394,7 +394,7 @@ class incidence_list final { this->_unique_insert(this->_head_storage[major_idx], minor_id); } - gl_attr_force_inline void unbind(const id_type vertex_id, const id_type hyperedge_id) noexcept { + void unbind(const id_type vertex_id, const id_type hyperedge_id) noexcept { const auto [major_id, minor_id] = layout_tag::majmin(vertex_id, hyperedge_id); const auto major_idx = to_idx(major_id); this->_remove_no_align(this->_tail_storage[major_idx], minor_id); diff --git a/include/hgl/impl/incidence_matrix.hpp b/include/hgl/impl/incidence_matrix.hpp index c745c3f8..9179e2b5 100644 --- a/include/hgl/impl/incidence_matrix.hpp +++ b/include/hgl/impl/incidence_matrix.hpp @@ -365,7 +365,7 @@ class incidence_matrix final { // --- binding methods --- - gl_attr_force_inline void bind_tail(const id_type vertex_id, const id_type hyperedge_id) { + void bind_tail(const id_type vertex_id, const id_type hyperedge_id) { const auto [major_id, minor_id] = layout_tag::majmin(vertex_id, hyperedge_id); if (this->_matrix[to_idx(major_id)][to_idx(minor_id)] == bf_incidence::forward) { @@ -380,7 +380,7 @@ class incidence_matrix final { this->_matrix[to_idx(major_id)][to_idx(minor_id)] = bf_incidence::backward; } - gl_attr_force_inline void bind_head(const id_type vertex_id, const id_type hyperedge_id) { + void bind_head(const id_type vertex_id, const id_type hyperedge_id) { const auto [major_id, minor_id] = layout_tag::majmin(vertex_id, hyperedge_id); if (this->_matrix[to_idx(major_id)][to_idx(minor_id)] == bf_incidence::backward) { @@ -395,7 +395,7 @@ class incidence_matrix final { this->_matrix[to_idx(major_id)][to_idx(minor_id)] = bf_incidence::forward; } - gl_attr_force_inline void unbind(const id_type vertex_id, const id_type hyperedge_id) noexcept { + void unbind(const id_type vertex_id, const id_type hyperedge_id) noexcept { const auto [major_id, minor_id] = layout_tag::majmin(vertex_id, hyperedge_id); this->_matrix[to_idx(major_id)][to_idx(minor_id)] = bf_incidence::none; } From 4f8f3415468a72bd4477ab16cf086c3b1e6c8193 Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Wed, 3 Jun 2026 16:17:56 +0200 Subject: [PATCH 12/14] fixed gl_attr_force_inline placement --- include/gl/graph.hpp | 2 +- include/hgl/hypergraph.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/gl/graph.hpp b/include/gl/graph.hpp index a41a3e7d..d8830d7b 100644 --- a/include/gl/graph.hpp +++ b/include/gl/graph.hpp @@ -1143,7 +1143,7 @@ class graph final { /// @param is The source input stream. /// @param g The graph instance to populate. /// @return The stream reference for chaining. - friend gl_attr_force_inline std::istream& operator>>(std::istream& is, graph& g) { + gl_attr_force_inline friend std::istream& operator>>(std::istream& is, graph& g) { return g._gsf_read(is); } diff --git a/include/hgl/hypergraph.hpp b/include/hgl/hypergraph.hpp index b6ef80df..0d9b89d2 100644 --- a/include/hgl/hypergraph.hpp +++ b/include/hgl/hypergraph.hpp @@ -1975,7 +1975,7 @@ class hypergraph final { /// @param is The source input stream. /// @param g The hypergraph instance to populate. /// @return The stream reference for chaining. - friend gl_attr_force_inline std::istream& operator>>(std::istream& is, hypergraph& hg) { + gl_attr_force_inline friend std::istream& operator>>(std::istream& is, hypergraph& hg) { return hg._hgsf_read(is); } From e0447dffbaa250cce98ee39a10a7ac1a8167c5c4 Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Wed, 3 Jun 2026 17:25:32 +0200 Subject: [PATCH 13/14] added asymmetric list model benchmark execution for HGL benchmarks --- .github/workflows/benchmarks.yaml | 4 ++-- benchmarks/suites/hg_b_bfs.cpp | 38 +++++++++++++++++++++++++++---- benchmarks/suites/hg_bfs.cpp | 33 ++++++++++++++++++++++++--- 3 files changed, 65 insertions(+), 10 deletions(-) diff --git a/.github/workflows/benchmarks.yaml b/.github/workflows/benchmarks.yaml index e6ba7ac2..2f23bce9 100644 --- a/.github/workflows/benchmarks.yaml +++ b/.github/workflows/benchmarks.yaml @@ -43,5 +43,5 @@ jobs: ./build_bench/benchmarks/cpp-gl-bench \ --benchmark_repetitions=1 --benchmark_display_aggregates_only=true \ --bip-v 100 \ - --hg-bfs-l --hg-bfs-m --hg-bfs-e 100 --hg-bfs-esize 5 --hg-bfs-stride 2 \ - --hg-b-bfs-l --hg-b-bfs-m --hg-b-bfs-e 100 --hg-b-bfs-layer-width 5 --hg-b-bfs-stride 2 + --hg-bfs-l --hg-bfs-al --hg-bfs-m --hg-bfs-e 100 --hg-bfs-esize 5 --hg-bfs-stride 2 \ + --hg-b-bfs-l --hg-b-bfs-al --hg-b-bfs-m --hg-b-bfs-e 100 --hg-b-bfs-layer-width 5 --hg-b-bfs-stride 2 diff --git a/benchmarks/suites/hg_b_bfs.cpp b/benchmarks/suites/hg_b_bfs.cpp index 6ae4abf0..fb4c969e 100644 --- a/benchmarks/suites/hg_b_bfs.cpp +++ b/benchmarks/suites/hg_b_bfs.cpp @@ -1,4 +1,5 @@ #include "gl/algorithm/core.hpp" +#include "hgl/directional_tags.hpp" #include "runner.hpp" #include "suite.hpp" @@ -180,7 +181,10 @@ void add_args(argon::argument_parser& parser) { parser.add_optional_argument(group, "hg-b-bfs-stride") .default_values(2uz) .help("Vertex shift between consecutive hyperedges (smaller = denser)"); - parser.add_flag("hg-b-bfs-l").help("Execute the benchmark for list models"); + parser.add_flag("hg-b-bfs-l").help("Execute the benchmark for list models (bidirectional)"); + parser.add_flag("hg-b-bfs-al") + .help("Execute the benchmark for asymmetric list models (WARNING: High execution times " + "expected)"); parser.add_flag("hg-b-bfs-m").help("Execute the benchmark for matrix models"); } @@ -190,16 +194,22 @@ void register_benchmarks(const argon::argument_parser& parser) { static_cast(parser.value("hg-b-bfs-layer-width")); const auto stride = static_cast(parser.value("hg-b-bfs-stride")); - // Standard Incidence Graphs MUST be directed for BF-Directed Hypergraphs using gl_list = gl::list_graph; using gl_flat_list = gl::flat_list_graph; using gl_matrix = gl::matrix_graph; using gl_flat_matrix = gl::flat_matrix_graph; - // BF-Directed Hypergraphs using hgl_list = hgl::list_hypergraph; using hgl_flat_list = hgl::flat_list_hypergraph; + using hgl_v_list = hgl::list_hypergraph; + using hgl_v_flat_list = + hgl::flat_list_hypergraph; + + using hgl_e_list = hgl::list_hypergraph; + using hgl_e_flat_list = + hgl::flat_list_hypergraph; + using hgl_v_matrix = hgl::matrix_hypergraph; using hgl_e_matrix = hgl::matrix_hypergraph; @@ -209,10 +219,10 @@ void register_benchmarks(const argon::argument_parser& parser) { hgl::flat_matrix_hypergraph; if (parser.value("hg-b-bfs-l")) { - benchmark::RegisterBenchmark("b_bfs/HGL/list", bm_hgl_backward_bfs) + benchmark::RegisterBenchmark("b_bfs/HGL/list/bidir", bm_hgl_backward_bfs) ->Args({n_hedges, layer_width, stride}) ->Unit(benchmark::kMillisecond); - benchmark::RegisterBenchmark("b_bfs/HGL/flat_list", bm_hgl_backward_bfs) + benchmark::RegisterBenchmark("b_bfs/HGL/flat_list/bidir", bm_hgl_backward_bfs) ->Args({n_hedges, layer_width, stride}) ->Unit(benchmark::kMillisecond); @@ -226,6 +236,24 @@ void register_benchmarks(const argon::argument_parser& parser) { ->Unit(benchmark::kMillisecond); } + if (parser.value("hg-b-bfs-al")) { + benchmark::RegisterBenchmark("bfs/HGL/list/v_major", bm_hgl_backward_bfs) + ->Args({n_hedges, layer_width, stride}) + ->Unit(benchmark::kMillisecond); + benchmark:: + RegisterBenchmark("bfs/HGL/flat_list/v_major", bm_hgl_backward_bfs) + ->Args({n_hedges, layer_width, stride}) + ->Unit(benchmark::kMillisecond); + + benchmark::RegisterBenchmark("bfs/HGL/list/e_major", bm_hgl_backward_bfs) + ->Args({n_hedges, layer_width, stride}) + ->Unit(benchmark::kMillisecond); + benchmark:: + RegisterBenchmark("bfs/HGL/flat_list/e_major", bm_hgl_backward_bfs) + ->Args({n_hedges, layer_width, stride}) + ->Unit(benchmark::kMillisecond); + } + if (parser.value("hg-b-bfs-m")) { benchmark::RegisterBenchmark("b_bfs/HGL/matrix/v_major", bm_hgl_backward_bfs) ->Args({n_hedges, layer_width, stride}) diff --git a/benchmarks/suites/hg_bfs.cpp b/benchmarks/suites/hg_bfs.cpp index b323b853..e0c76bd2 100644 --- a/benchmarks/suites/hg_bfs.cpp +++ b/benchmarks/suites/hg_bfs.cpp @@ -1,3 +1,4 @@ +#include "hgl/repr/layout_tags.hpp" #include "runner.hpp" #include "suite.hpp" @@ -112,7 +113,10 @@ void add_args(argon::argument_parser& parser) { parser.add_optional_argument(group, "hg-bfs-stride") .default_values(2uz) .help("Vertex shift between consecutive hyperedges (smaller = denser)"); - parser.add_flag("hg-bfs-l").help("Execute the benchmark for list models"); + parser.add_flag("hg-bfs-l").help("Execute the benchmark for list models (bidirectional)"); + parser.add_flag("hg-bfs-al") + .help("Execute the benchmark for asymmetric list models (WARNING: High execution times " + "expected)"); parser.add_flag("hg-bfs-m").help("Execute the benchmark for matrix models"); } @@ -129,6 +133,13 @@ void register_benchmarks(const argon::argument_parser& parser) { using hgl_list = hgl::list_hypergraph; using hgl_flat_list = hgl::flat_list_hypergraph; + using hgl_v_list = hgl::list_hypergraph; + using hgl_v_flat_list = hgl::flat_list_hypergraph; + + using hgl_e_list = hgl::list_hypergraph; + using hgl_e_flat_list = + hgl::flat_list_hypergraph; + using hgl_v_matrix = hgl::matrix_hypergraph; using hgl_e_matrix = hgl::matrix_hypergraph; @@ -138,10 +149,10 @@ void register_benchmarks(const argon::argument_parser& parser) { hgl::flat_matrix_hypergraph; if (parser.value("hg-bfs-l")) { - benchmark::RegisterBenchmark("bfs/HGL/list", bm_hgl_bfs) + benchmark::RegisterBenchmark("bfs/HGL/list/bidir", bm_hgl_bfs) ->Args({n_hedges, he_size, stride}) ->Unit(benchmark::kMillisecond); - benchmark::RegisterBenchmark("bfs/HGL/flat_list", bm_hgl_bfs) + benchmark::RegisterBenchmark("bfs/HGL/flat_list/bidir", bm_hgl_bfs) ->Args({n_hedges, he_size, stride}) ->Unit(benchmark::kMillisecond); @@ -154,6 +165,22 @@ void register_benchmarks(const argon::argument_parser& parser) { ->Unit(benchmark::kMillisecond); } + if (parser.value("hg-bfs-al")) { + benchmark::RegisterBenchmark("bfs/HGL/list/v_major", bm_hgl_bfs) + ->Args({n_hedges, he_size, stride}) + ->Unit(benchmark::kMillisecond); + benchmark::RegisterBenchmark("bfs/HGL/flat_list/v_major", bm_hgl_bfs) + ->Args({n_hedges, he_size, stride}) + ->Unit(benchmark::kMillisecond); + + benchmark::RegisterBenchmark("bfs/HGL/list/e_major", bm_hgl_bfs) + ->Args({n_hedges, he_size, stride}) + ->Unit(benchmark::kMillisecond); + benchmark::RegisterBenchmark("bfs/HGL/flat_list/e_major", bm_hgl_bfs) + ->Args({n_hedges, he_size, stride}) + ->Unit(benchmark::kMillisecond); + } + if (parser.value("hg-bfs-m")) { benchmark::RegisterBenchmark("bfs/HGL/matrix/v_major", bm_hgl_bfs) ->Args({n_hedges, he_size, stride}) From 91bef639a7e14bda78ab188aad8ee4310cbdef4c Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Sun, 7 Jun 2026 12:32:43 +0200 Subject: [PATCH 14/14] readme update: docs pipeline --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d431e53c..cff4e06b 100644 --- a/README.md +++ b/README.md @@ -160,7 +160,13 @@ python scripts/format.py -m -exe clang-format-18 ### Building the Documentation -The documentation is generated using Doxygen (for XML extraction), a custom Python script for concept parsing, and MkDocs (via `mike` for versioning). The process is automated via a Makefile. +The documentation build process utilizes the following toolchain: + +- **[Doxygen](https://www.doxygen.nl/)**: Extracts the initial C++ API structure and documentation into XML format. +- **Concept Parser**: A [custom Python script](/docs/scripts/gen_concept_docs.py) processes the XML to generate dedicated concept documentation pages. +- **[MkDoxy (Custom Fork)](https://github.com/SpectraL519/MkDoxy)**: Injects the Doxygen XML data directly into the MkDocs build lifecycle. +- **[MkDocs](https://www.mkdocs.org/) & [Material Theme](https://squidfunk.github.io/mkdocs-material/)**: Renders the final, searchable static HTML website. +- **[mike](https://github.com/jimporter/mike)**: Manages versioning, allowing multiple versions of the documentation to coexist and be deployed simultaneously. To build and serve the documentation locally: