From b534005592da6e129f990f827338d4302a63acfd Mon Sep 17 00:00:00 2001 From: SAURAV SINGLA Date: Fri, 28 Aug 2026 21:48:49 +0530 Subject: [PATCH 01/21] bench: add normalized native BFS validation harness --- tools/validate_normalized_native_bfs.py | 117 ++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 tools/validate_normalized_native_bfs.py diff --git a/tools/validate_normalized_native_bfs.py b/tools/validate_normalized_native_bfs.py new file mode 100644 index 0000000..27b14ee --- /dev/null +++ b/tools/validate_normalized_native_bfs.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python3 +import argparse +import json +import subprocess +import sys +from pathlib import Path + + +def run_report(repo: Path, dataset: Path, source: int, directed: bool, repeat: int, + framework: str, *, external_name=None, external_command=None): + cmd = [ + sys.executable, + str(repo / "tools" / "competitor_benchmark.py"), + "--dataset", str(dataset), + "--framework", framework, + "--source", str(source), + "--repeat", str(repeat), + ] + if directed: + cmd.append("--directed") + if framework == "external": + cmd.extend([ + "--external-name", external_name, + "--external-command", external_command, + ]) + proc = subprocess.run(cmd, text=True, capture_output=True, check=False, cwd=repo) + if proc.returncode != 0: + detail = proc.stderr.strip() or proc.stdout.strip() + raise RuntimeError(f"{external_name or framework} failed: {detail}") + return json.loads(proc.stdout) + + +def require_equal(label, reports, key): + values = {name: report[key] for name, report in reports.items()} + if len(set(values.values())) != 1: + raise RuntimeError(f"normalized BFS mismatch for {label}: {values}") + return next(iter(values.values())) + + +def main(): + p = argparse.ArgumentParser(description="Validate normalized BFS semantics across builtin, LAGraph and GAP native adapters.") + p.add_argument("--dataset", type=Path, required=True) + p.add_argument("--source", type=int, default=0) + p.add_argument("--directed", action="store_true") + p.add_argument("--repeat", type=int, default=3) + p.add_argument("--lagraph-command", required=True) + p.add_argument("--gap-command", required=True) + p.add_argument("--output", type=Path) + args = p.parse_args() + + if args.repeat < 1: + raise ValueError("--repeat must be at least 1") + if not args.dataset.is_file(): + raise ValueError(f"dataset does not exist: {args.dataset}") + + repo = Path(__file__).resolve().parents[1] + reports = { + "builtin": run_report(repo, args.dataset, args.source, args.directed, args.repeat, "builtin"), + "lagraph": run_report( + repo, args.dataset, args.source, args.directed, args.repeat, "external", + external_name="SuiteSparse:GraphBLAS/LAGraph", + external_command=args.lagraph_command, + ), + "gap": run_report( + repo, args.dataset, args.source, args.directed, args.repeat, "external", + external_name="GAP Benchmark Suite", + external_command=args.gap_command, + ), + } + + normalized = { + "dataset_sha256": require_equal("dataset checksum", reports, "dataset_sha256"), + "source": require_equal("source", reports, "source"), + "directed": require_equal("directedness", reports, "directed"), + "vertices": require_equal("vertex count", reports, "vertices"), + "edges": require_equal("edge count", reports, "edges"), + "result_digest": require_equal("full BFS distance digest", reports, "result_digest"), + "reachable_vertices": require_equal("reachable vertex count", reports, "reachable_vertices"), + } + + payload = { + "schema_version": 1, + "artifact_type": "velographx-normalized-native-bfs", + "algorithm": "bfs", + "normalized_cross_engine_claim": True, + "correctness_gate": { + "passed": True, + "same_dataset": True, + "same_source": True, + "same_directedness": True, + "same_full_distance_digest": True, + }, + "normalized": normalized, + "reports": reports, + "research_claim": False, + "publication_grade": False, + "claim_gate": { + "publication_ready": False, + "allowed_claim": "Normalized same-dataset/source/directedness BFS correctness and hosted engineering timing only; no publication-grade superiority claim.", + }, + } + + text = json.dumps(payload, indent=2, sort_keys=True) + if args.output: + args.output.parent.mkdir(parents=True, exist_ok=True) + args.output.write_text(text + "\n", encoding="utf-8") + else: + print(text) + return 0 + + +if __name__ == "__main__": + try: + raise SystemExit(main()) + except Exception as exc: + print(f"error: {exc}", file=sys.stderr) + raise SystemExit(2) From 51c19a3ed6144119dbecdc6246e32e04066b4b94 Mon Sep 17 00:00:00 2001 From: SAURAV SINGLA Date: Fri, 28 Aug 2026 21:49:05 +0530 Subject: [PATCH 02/21] docs: define normalized native BFS gate --- docs/normalized-native-bfs.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 docs/normalized-native-bfs.md diff --git a/docs/normalized-native-bfs.md b/docs/normalized-native-bfs.md new file mode 100644 index 0000000..f6103fe --- /dev/null +++ b/docs/normalized-native-bfs.md @@ -0,0 +1,35 @@ +# Normalized native BFS comparison + +This stage closes the semantic gap between VeloGraphX hosted engineering evidence and like-for-like native competitor evidence. + +The validator `tools/validate_normalized_native_bfs.py` runs the builtin reference plus the existing external LAGraph and GAP adapter contracts and refuses to produce a normalized result unless every engine agrees on: + +- dataset SHA-256; +- source vertex; +- directedness; +- vertex and edge counts; +- reachable-vertex count; and +- the SHA-256 digest of the complete BFS distance vector. + +A successful run may set `normalized_cross_engine_claim: true` only for correctness normalization and same-runner engineering timing. It must continue to emit `research_claim: false`, `publication_grade: false`, and `publication_ready: false` on GitHub-hosted hardware. + +## Native runner contract + +Both native runners must accept: + +```text +--dataset PATH --source N --vertices N [--directed] +``` + +and emit exactly one JSON object containing a `distances` array with one integer per vertex (`-1` for unreachable vertices) and an immutable framework version/commit identifier where available. + +The existing shims are: + +- `tools/native_competitors/lagraph_wrapper.py` +- `tools/native_competitors/gap_wrapper.py` + +## Remaining implementation + +The hosted workflow still needs native runner binaries that expose full BFS distance vectors from the pinned LAGraph/GraphBLAS and GAP implementations. Once those runners are wired in, the normalization validator becomes the mandatory correctness gate before any cross-engine timing summary is emitted. + +Publication-grade competitor claims remain blocked until the same normalized contract is executed on the dedicated hardware campaign required by Issue #10. From 4192e382d781baba19346851a936de1b5aa7d7d8 Mon Sep 17 00:00:00 2001 From: SAURAV SINGLA Date: Fri, 28 Aug 2026 22:00:46 +0530 Subject: [PATCH 03/21] bench: add LAGraph normalized BFS runner --- .../lagraph_bfs_json_runner.c | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 tools/native_competitors/lagraph_bfs_json_runner.c diff --git a/tools/native_competitors/lagraph_bfs_json_runner.c b/tools/native_competitors/lagraph_bfs_json_runner.c new file mode 100644 index 0000000..935e70e --- /dev/null +++ b/tools/native_competitors/lagraph_bfs_json_runner.c @@ -0,0 +1,66 @@ +#include +#include +#include +#include +#include +#include +#include + +static void die(const char *msg) { fprintf(stderr, "%s\n", msg); exit(2); } + +int main(int argc, char **argv) { + const char *dataset = NULL; + int64_t source = -1, vertices = -1; + bool directed = false; + for (int i = 1; i < argc; i++) { + if (!strcmp(argv[i], "--dataset") && i + 1 < argc) dataset = argv[++i]; + else if (!strcmp(argv[i], "--source") && i + 1 < argc) source = atoll(argv[++i]); + else if (!strcmp(argv[i], "--vertices") && i + 1 < argc) vertices = atoll(argv[++i]); + else if (!strcmp(argv[i], "--directed")) directed = true; + else die("invalid argument"); + } + if (!dataset || source < 0 || vertices <= 0 || source >= vertices) die("invalid runner arguments"); + + FILE *f = fopen(dataset, "r"); + if (!f) die("cannot open dataset"); + + char msg[LAGRAPH_MSG_LEN]; + if (LAGraph_Init(msg) < 0) die(msg); + GrB_Matrix A = NULL; + if (GrB_Matrix_new(&A, GrB_BOOL, (GrB_Index) vertices, (GrB_Index) vertices) < 0) die("GrB_Matrix_new failed"); + + long long u, v; + while (fscanf(f, "%lld %lld", &u, &v) == 2) { + if (u < 0 || v < 0 || u >= vertices || v >= vertices) die("edge vertex out of range"); + if (GrB_Matrix_setElement_BOOL(A, true, (GrB_Index) u, (GrB_Index) v) < 0) die("set edge failed"); + if (!directed && u != v) { + if (GrB_Matrix_setElement_BOOL(A, true, (GrB_Index) v, (GrB_Index) u) < 0) die("set reverse edge failed"); + } + } + fclose(f); + + LAGraph_Graph G = NULL; + LAGraph_Kind kind = directed ? LAGraph_ADJACENCY_DIRECTED : LAGraph_ADJACENCY_UNDIRECTED; + if (LAGraph_New(&G, &A, kind, msg) < 0) die(msg); + int cache_status = LAGraph_Cached_OutDegree(G, msg); + if (cache_status < 0) die(msg); + + GrB_Vector level = NULL; + if (LAGr_BreadthFirstSearch(&level, NULL, G, source, msg) < 0) die(msg); + + printf("{\"framework_version\":\"LAGraph-1.2.2\",\"distances\":["); + for (int64_t i = 0; i < vertices; i++) { + int64_t d = -1; + GrB_Info info = GrB_Vector_extractElement_INT64(&d, level, (GrB_Index) i); + if (info == GrB_NO_VALUE) d = -1; + else if (info < 0) die("extract level failed"); + if (i) putchar(','); + printf("%lld", (long long) d); + } + printf("]}\n"); + + GrB_free(&level); + LAGraph_Delete(&G, msg); + LAGraph_Finalize(msg); + return 0; +} From d94a935899a5ccebc79b8ff33548697ee6483347 Mon Sep 17 00:00:00 2001 From: SAURAV SINGLA Date: Fri, 28 Aug 2026 22:00:58 +0530 Subject: [PATCH 04/21] bench: add GAP normalized BFS runner --- .../native_competitors/gap_bfs_json_runner.cc | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 tools/native_competitors/gap_bfs_json_runner.cc diff --git a/tools/native_competitors/gap_bfs_json_runner.cc b/tools/native_competitors/gap_bfs_json_runner.cc new file mode 100644 index 0000000..7a461c7 --- /dev/null +++ b/tools/native_competitors/gap_bfs_json_runner.cc @@ -0,0 +1,67 @@ +#include +#include +#include +#include + +#define main gapbs_original_main +#include "bfs.cc" +#undef main + +static void die(const std::string &msg) { std::cerr << msg << "\n"; std::exit(2); } + +int main(int argc, char **argv) { + std::string dataset; + int source = -1; + int vertices = -1; + bool directed = false; + for (int i = 1; i < argc; ++i) { + std::string a = argv[i]; + if (a == "--dataset" && i + 1 < argc) dataset = argv[++i]; + else if (a == "--source" && i + 1 < argc) source = std::stoi(argv[++i]); + else if (a == "--vertices" && i + 1 < argc) vertices = std::stoi(argv[++i]); + else if (a == "--directed") directed = true; + else die("invalid argument"); + } + if (dataset.empty() || source < 0 || vertices <= 0 || source >= vertices) die("invalid runner arguments"); + + std::vector args = {"gap-json-runner"}; + if (!directed) args.push_back("-s"); + args.push_back("-f"); + args.push_back(dataset); + args.push_back("-r"); + args.push_back(std::to_string(source)); + std::vector cargs; + for (auto &s : args) cargs.push_back(s.data()); + + CLApp cli(static_cast(cargs.size()), cargs.data(), "normalized breadth-first search"); + if (!cli.ParseArgs()) die("GAP argument parsing failed"); + Builder b(cli); + Graph g = b.MakeGraph(); + if (g.num_nodes() != vertices) die("GAP graph vertex count mismatch"); + + pvector parent = DOBFS(g, source, false); + std::vector dist(vertices, -1); + dist[source] = 0; + for (int v = 0; v < vertices; ++v) { + if (v == source || parent[v] < 0) continue; + int cur = v; + int depth = 0; + std::vector seen; + while (cur != source) { + if (cur < 0 || cur >= vertices || parent[cur] < 0) { depth = -1; break; } + seen.push_back(cur); + cur = parent[cur]; + ++depth; + if (depth > vertices) die("cycle detected in GAP BFS parent tree"); + } + if (depth >= 0) dist[v] = depth; + } + + std::cout << "{\"framework_version\":\"GAPBS-1.5\",\"distances\":["; + for (int i = 0; i < vertices; ++i) { + if (i) std::cout << ','; + std::cout << dist[i]; + } + std::cout << "]}\n"; + return 0; +} From 0ea75918772697ea619321bf8cca29cb80caf81e Mon Sep 17 00:00:00 2001 From: SAURAV SINGLA Date: Fri, 28 Aug 2026 22:01:24 +0530 Subject: [PATCH 05/21] ci: run normalized native BFS correctness gate --- .github/workflows/normalized-native-bfs.yml | 121 ++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 .github/workflows/normalized-native-bfs.yml diff --git a/.github/workflows/normalized-native-bfs.yml b/.github/workflows/normalized-native-bfs.yml new file mode 100644 index 0000000..5b94430 --- /dev/null +++ b/.github/workflows/normalized-native-bfs.yml @@ -0,0 +1,121 @@ +name: Normalized Native BFS + +on: + pull_request: + paths: + - '.github/workflows/normalized-native-bfs.yml' + - 'tools/validate_normalized_native_bfs.py' + - 'tools/competitor_benchmark.py' + - 'tools/native_competitors/**' + - 'docs/normalized-native-bfs.md' + workflow_dispatch: + +jobs: + normalized-bfs: + runs-on: ubuntu-latest + timeout-minutes: 60 + env: + GRAPHBLAS_TAG: v10.3.2 + LAGRAPH_TAG: v1.2.2 + GAPBS_TAG: v1.5 + steps: + - uses: actions/checkout@v4 + + - name: Install build dependencies + run: | + sudo apt-get update + sudo apt-get install -y cmake ninja-build libomp-dev + + - name: Clone immutable native competitors + run: | + git clone --depth 1 --branch "$GRAPHBLAS_TAG" https://github.com/DrTimothyAldenDavis/GraphBLAS.git external/GraphBLAS + git clone --depth 1 --branch "$LAGRAPH_TAG" https://github.com/GraphBLAS/LAGraph.git external/LAGraph + git clone --depth 1 --branch "$GAPBS_TAG" https://github.com/sbeamer/gapbs.git external/gapbs + + - name: Build SuiteSparse GraphBLAS + run: | + cmake -S external/GraphBLAS -B external/GraphBLAS/build \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX="$PWD/external/install" + cmake --build external/GraphBLAS/build --parallel 2 + cmake --install external/GraphBLAS/build + + - name: Build LAGraph + run: | + cmake -S external/LAGraph -B external/LAGraph/build \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_PREFIX_PATH="$PWD/external/install" \ + -DCMAKE_INSTALL_PREFIX="$PWD/external/install" + cmake --build external/LAGraph/build --parallel 2 + cmake --install external/LAGraph/build + + - name: Build normalized native runner binaries + run: | + mkdir -p build/native artifacts/normalized + cc -O3 -fopenmp \ + -Iexternal/install/include \ + tools/native_competitors/lagraph_bfs_json_runner.c \ + -Lexternal/install/lib -Lexternal/install/lib64 \ + -llagraph -lgraphblas \ + -o build/native/lagraph_bfs_json_runner + c++ -O3 -std=c++17 -fopenmp \ + -Iexternal/gapbs/src \ + tools/native_competitors/gap_bfs_json_runner.cc \ + -o build/native/gap_bfs_json_runner + test -x build/native/lagraph_bfs_json_runner + test -x build/native/gap_bfs_json_runner + + - name: Generate deterministic normalization graph + run: | + python - <<'PY' + from pathlib import Path + n = 4096 + edges = set() + for u in range(n): + for delta in (1, 7, 31, 127): + v = (u + delta) % n + a, b = sorted((u, v)) + if a != b: + edges.add((a, b)) + Path('artifacts/normalized/hosted-native.el').write_text( + ''.join(f'{u} {v}\n' for u, v in sorted(edges)) + ) + PY + + - name: Validate full-distance normalization + env: + LD_LIBRARY_PATH: ${{ github.workspace }}/external/install/lib:${{ github.workspace }}/external/install/lib64 + VELOGRAPHX_LAGRAPH_BFS_BIN: ${{ github.workspace }}/build/native/lagraph_bfs_json_runner + VELOGRAPHX_GAP_BFS_BIN: ${{ github.workspace }}/build/native/gap_bfs_json_runner + run: | + python tools/validate_normalized_native_bfs.py \ + --dataset artifacts/normalized/hosted-native.el \ + --source 0 \ + --repeat 3 \ + --lagraph-command "python tools/native_competitors/lagraph_wrapper.py" \ + --gap-command "python tools/native_competitors/gap_wrapper.py" \ + --output artifacts/normalized/normalized-native-bfs.json + + - name: Validate claim gate + run: | + python - <<'PY' + import json + from pathlib import Path + r = json.loads(Path('artifacts/normalized/normalized-native-bfs.json').read_text()) + assert r['normalized_cross_engine_claim'] is True + assert r['correctness_gate']['passed'] is True + assert r['correctness_gate']['same_dataset'] is True + assert r['correctness_gate']['same_source'] is True + assert r['correctness_gate']['same_directedness'] is True + assert r['correctness_gate']['same_full_distance_digest'] is True + assert r['research_claim'] is False + assert r['publication_grade'] is False + assert r['claim_gate']['publication_ready'] is False + PY + + - name: Upload normalized native BFS evidence + uses: actions/upload-artifact@v4 + with: + name: velographx-normalized-native-bfs + path: artifacts/normalized + retention-days: 30 From 033e64847d53718db05e0a56689da6a67f51fc19 Mon Sep 17 00:00:00 2001 From: SAURAV SINGLA Date: Fri, 28 Aug 2026 23:24:33 +0530 Subject: [PATCH 06/21] bench: include LAGraph experimental BFS API explicitly --- tools/native_competitors/lagraph_bfs_json_runner.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/native_competitors/lagraph_bfs_json_runner.c b/tools/native_competitors/lagraph_bfs_json_runner.c index 935e70e..2db18df 100644 --- a/tools/native_competitors/lagraph_bfs_json_runner.c +++ b/tools/native_competitors/lagraph_bfs_json_runner.c @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -42,18 +43,17 @@ int main(int argc, char **argv) { LAGraph_Graph G = NULL; LAGraph_Kind kind = directed ? LAGraph_ADJACENCY_DIRECTED : LAGraph_ADJACENCY_UNDIRECTED; if (LAGraph_New(&G, &A, kind, msg) < 0) die(msg); - int cache_status = LAGraph_Cached_OutDegree(G, msg); - if (cache_status < 0) die(msg); + if (LAGraph_Cached_OutDegree(G, msg) < 0) die(msg); GrB_Vector level = NULL; - if (LAGr_BreadthFirstSearch(&level, NULL, G, source, msg) < 0) die(msg); + if (LAGr_BreadthFirstSearch(&level, NULL, G, (GrB_Index) source, msg) < 0) die(msg); printf("{\"framework_version\":\"LAGraph-1.2.2\",\"distances\":["); for (int64_t i = 0; i < vertices; i++) { int64_t d = -1; GrB_Info info = GrB_Vector_extractElement_INT64(&d, level, (GrB_Index) i); if (info == GrB_NO_VALUE) d = -1; - else if (info < 0) die("extract level failed"); + else if (info != GrB_SUCCESS) die("extract level failed"); if (i) putchar(','); printf("%lld", (long long) d); } From ec0798a8297d7559f14f58d21ec3bd1e5193a1a1 Mon Sep 17 00:00:00 2001 From: SAURAV SINGLA Date: Fri, 28 Aug 2026 23:24:51 +0530 Subject: [PATCH 07/21] ci: fix normalized native runner build wiring --- .github/workflows/normalized-native-bfs.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/normalized-native-bfs.yml b/.github/workflows/normalized-native-bfs.yml index 5b94430..174dcd4 100644 --- a/.github/workflows/normalized-native-bfs.yml +++ b/.github/workflows/normalized-native-bfs.yml @@ -51,12 +51,16 @@ jobs: - name: Build normalized native runner binaries run: | + set -euxo pipefail mkdir -p build/native artifacts/normalized - cc -O3 -fopenmp \ + cc -O3 -std=c11 -fopenmp \ -Iexternal/install/include \ + -Iexternal/LAGraph/include \ tools/native_competitors/lagraph_bfs_json_runner.c \ -Lexternal/install/lib -Lexternal/install/lib64 \ - -llagraph -lgraphblas \ + -Wl,-rpath,"$PWD/external/install/lib" \ + -Wl,-rpath,"$PWD/external/install/lib64" \ + -llagraph -lgraphblas -lm \ -o build/native/lagraph_bfs_json_runner c++ -O3 -std=c++17 -fopenmp \ -Iexternal/gapbs/src \ From 4fb12a53aa457b58f8eb3851afe0731b965e2a51 Mon Sep 17 00:00:00 2001 From: SAURAV SINGLA Date: Sat, 29 Aug 2026 00:05:09 +0530 Subject: [PATCH 08/21] bench: link GAP BFS runner against native kernel object --- .../native_competitors/gap_bfs_json_runner.cc | 42 +++++++++++++------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/tools/native_competitors/gap_bfs_json_runner.cc b/tools/native_competitors/gap_bfs_json_runner.cc index 7a461c7..28548ab 100644 --- a/tools/native_competitors/gap_bfs_json_runner.cc +++ b/tools/native_competitors/gap_bfs_json_runner.cc @@ -3,11 +3,20 @@ #include #include -#define main gapbs_original_main -#include "bfs.cc" -#undef main +#include "builder.h" +#include "command_line.h" +#include "graph.h" +#include "pvector.h" -static void die(const std::string &msg) { std::cerr << msg << "\n"; std::exit(2); } +// Implemented by the pinned GAP Benchmark Suite src/bfs.cc object that the +// workflow compiles separately. Keep this declaration in sync with GAP v1.5. +pvector DOBFS(const Graph &g, NodeID source, bool logging_enabled, + int alpha, int beta); + +static void die(const std::string &msg) { + std::cerr << msg << "\n"; + std::exit(2); +} int main(int argc, char **argv) { std::string dataset; @@ -22,7 +31,9 @@ int main(int argc, char **argv) { else if (a == "--directed") directed = true; else die("invalid argument"); } - if (dataset.empty() || source < 0 || vertices <= 0 || source >= vertices) die("invalid runner arguments"); + if (dataset.empty() || source < 0 || vertices <= 0 || source >= vertices) { + die("invalid runner arguments"); + } std::vector args = {"gap-json-runner"}; if (!directed) args.push_back("-s"); @@ -30,26 +41,33 @@ int main(int argc, char **argv) { args.push_back(dataset); args.push_back("-r"); args.push_back(std::to_string(source)); - std::vector cargs; + std::vector cargs; + cargs.reserve(args.size()); for (auto &s : args) cargs.push_back(s.data()); - CLApp cli(static_cast(cargs.size()), cargs.data(), "normalized breadth-first search"); + CLApp cli(static_cast(cargs.size()), cargs.data(), + "normalized breadth-first search"); if (!cli.ParseArgs()) die("GAP argument parsing failed"); Builder b(cli); Graph g = b.MakeGraph(); if (g.num_nodes() != vertices) die("GAP graph vertex count mismatch"); - pvector parent = DOBFS(g, source, false); + pvector parent = DOBFS(g, static_cast(source), false, 15, 18); + if (!BFSVerifier(g, static_cast(source), parent)) { + die("GAP BFS verification failed"); + } + std::vector dist(vertices, -1); dist[source] = 0; for (int v = 0; v < vertices; ++v) { if (v == source || parent[v] < 0) continue; - int cur = v; + NodeID cur = static_cast(v); int depth = 0; - std::vector seen; while (cur != source) { - if (cur < 0 || cur >= vertices || parent[cur] < 0) { depth = -1; break; } - seen.push_back(cur); + if (cur < 0 || cur >= vertices || parent[cur] < 0) { + depth = -1; + break; + } cur = parent[cur]; ++depth; if (depth > vertices) die("cycle detected in GAP BFS parent tree"); From 778bac44920aaa6759530b65c06e3053efc91072 Mon Sep 17 00:00:00 2001 From: SAURAV SINGLA Date: Sat, 29 Aug 2026 00:05:25 +0530 Subject: [PATCH 09/21] bench: declare GAP verification entry point --- tools/native_competitors/gap_bfs_json_runner.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/native_competitors/gap_bfs_json_runner.cc b/tools/native_competitors/gap_bfs_json_runner.cc index 28548ab..2214108 100644 --- a/tools/native_competitors/gap_bfs_json_runner.cc +++ b/tools/native_competitors/gap_bfs_json_runner.cc @@ -9,9 +9,11 @@ #include "pvector.h" // Implemented by the pinned GAP Benchmark Suite src/bfs.cc object that the -// workflow compiles separately. Keep this declaration in sync with GAP v1.5. +// workflow compiles separately. Keep these declarations in sync with GAP v1.5. pvector DOBFS(const Graph &g, NodeID source, bool logging_enabled, int alpha, int beta); +bool BFSVerifier(const Graph &g, NodeID source, + const pvector &parent); static void die(const std::string &msg) { std::cerr << msg << "\n"; From b7419394dde9f29da86606090c0025326e3cd0a5 Mon Sep 17 00:00:00 2001 From: SAURAV SINGLA Date: Sat, 29 Aug 2026 00:05:42 +0530 Subject: [PATCH 10/21] ci: compile GAP BFS kernel separately for normalized runner --- .github/workflows/normalized-native-bfs.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/normalized-native-bfs.yml b/.github/workflows/normalized-native-bfs.yml index 174dcd4..c9b8745 100644 --- a/.github/workflows/normalized-native-bfs.yml +++ b/.github/workflows/normalized-native-bfs.yml @@ -62,9 +62,19 @@ jobs: -Wl,-rpath,"$PWD/external/install/lib64" \ -llagraph -lgraphblas -lm \ -o build/native/lagraph_bfs_json_runner + + # Compile the pinned GAP BFS implementation as its own translation + # unit. Rename only its CLI main so DOBFS/BFSVerifier remain native + # symbols that our thin JSON runner can call directly. + c++ -O3 -std=c++17 -fopenmp \ + -Iexternal/gapbs/src \ + -Dmain=gapbs_original_main \ + -c external/gapbs/src/bfs.cc \ + -o build/native/gap_bfs_kernel.o c++ -O3 -std=c++17 -fopenmp \ -Iexternal/gapbs/src \ tools/native_competitors/gap_bfs_json_runner.cc \ + build/native/gap_bfs_kernel.o \ -o build/native/gap_bfs_json_runner test -x build/native/lagraph_bfs_json_runner test -x build/native/gap_bfs_json_runner From 1ebaa2c65fb8620085f2cde0632702a5d5d27cec Mon Sep 17 00:00:00 2001 From: SAURAV SINGLA Date: Sat, 29 Aug 2026 09:27:59 +0530 Subject: [PATCH 11/21] fix: use pinned LAGraph v1.2.2 BFS API --- tools/native_competitors/lagraph_bfs_json_runner.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tools/native_competitors/lagraph_bfs_json_runner.c b/tools/native_competitors/lagraph_bfs_json_runner.c index 2db18df..df0a459 100644 --- a/tools/native_competitors/lagraph_bfs_json_runner.c +++ b/tools/native_competitors/lagraph_bfs_json_runner.c @@ -46,7 +46,17 @@ int main(int argc, char **argv) { if (LAGraph_Cached_OutDegree(G, msg) < 0) die(msg); GrB_Vector level = NULL; - if (LAGr_BreadthFirstSearch(&level, NULL, G, (GrB_Index) source, msg) < 0) die(msg); + if (LAGr_BreadthFirstSearch_Extended( + &level, + NULL, + G, + (GrB_Index) source, + -1, + -1, + true, + msg) < 0) { + die(msg); + } printf("{\"framework_version\":\"LAGraph-1.2.2\",\"distances\":["); for (int64_t i = 0; i < vertices; i++) { From 37d6b53b42636b13da3cb8d844334972348184c9 Mon Sep 17 00:00:00 2001 From: SAURAV SINGLA Date: Sat, 29 Aug 2026 09:43:48 +0530 Subject: [PATCH 12/21] test: make work-stealing assertion deterministic --- tests/test_work_stealing.cpp | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/test_work_stealing.cpp b/tests/test_work_stealing.cpp index 6940d52..048b97e 100644 --- a/tests/test_work_stealing.cpp +++ b/tests/test_work_stealing.cpp @@ -2,7 +2,9 @@ #include #include +#include #include +#include #include int main() { @@ -17,6 +19,26 @@ int main() { assert(pool.queue_group(2) == 0); assert(pool.queue_group(3) == 1); + // Force a real stealing opportunity instead of relying on scheduler timing. + // All tasks start on queue 0 and remain blocked until at least one worker has + // attempted to steal from another queue. + std::atomic release_probe{false}; + for (std::size_t i = 0; i < 32; ++i) { + pool.submit([&release_probe] { + while (!release_probe.load(std::memory_order_acquire)) { + std::this_thread::yield(); + } + }, 0); + } + + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(2); + while (pool.stats().steal_attempts == 0 && std::chrono::steady_clock::now() < deadline) { + std::this_thread::yield(); + } + assert(pool.stats().steal_attempts > 0); + release_probe.store(true, std::memory_order_release); + pool.wait_idle(); + std::atomic sum{0}; for (std::size_t i = 0; i < 1000; ++i) { pool.submit([&sum, i] { sum.fetch_add(i, std::memory_order_relaxed); }, 0); From 44223d7c445ca7fb7206e170c188575022ae285f Mon Sep 17 00:00:00 2001 From: SAURAV SINGLA Date: Sat, 29 Aug 2026 10:23:42 +0530 Subject: [PATCH 13/21] fix: use pinned LAGraph BFS API --- tools/native_competitors/lagraph_bfs_json_runner.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tools/native_competitors/lagraph_bfs_json_runner.c b/tools/native_competitors/lagraph_bfs_json_runner.c index df0a459..be4e0b9 100644 --- a/tools/native_competitors/lagraph_bfs_json_runner.c +++ b/tools/native_competitors/lagraph_bfs_json_runner.c @@ -46,14 +46,11 @@ int main(int argc, char **argv) { if (LAGraph_Cached_OutDegree(G, msg) < 0) die(msg); GrB_Vector level = NULL; - if (LAGr_BreadthFirstSearch_Extended( + if (LAGr_BreadthFirstSearch( &level, NULL, G, (GrB_Index) source, - -1, - -1, - true, msg) < 0) { die(msg); } From 2b8decd8eeaaf1e3ebf25476a019ab2f37e15335 Mon Sep 17 00:00:00 2001 From: SAURAV SINGLA Date: Sat, 29 Aug 2026 10:55:18 +0530 Subject: [PATCH 14/21] fix: define pinned GAPBS v1.5 runner aliases --- tools/native_competitors/gap_bfs_json_runner.cc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tools/native_competitors/gap_bfs_json_runner.cc b/tools/native_competitors/gap_bfs_json_runner.cc index 2214108..5f9c5bc 100644 --- a/tools/native_competitors/gap_bfs_json_runner.cc +++ b/tools/native_competitors/gap_bfs_json_runner.cc @@ -1,3 +1,4 @@ +#include #include #include #include @@ -8,6 +9,14 @@ #include "graph.h" #include "pvector.h" +// Match GAP Benchmark Suite v1.5's benchmark.h aliases locally without +// including benchmark.h itself. That header defines non-inline helpers and is +// already pulled into the separately compiled bfs.cc translation unit. +typedef int32_t NodeID; +typedef int32_t WeightT; +typedef CSRGraph Graph; +typedef BuilderBase Builder; + // Implemented by the pinned GAP Benchmark Suite src/bfs.cc object that the // workflow compiles separately. Keep these declarations in sync with GAP v1.5. pvector DOBFS(const Graph &g, NodeID source, bool logging_enabled, From f96147017557bc5682dd26b9470423ea6a0b9cce Mon Sep 17 00:00:00 2001 From: SAURAV SINGLA Date: Sat, 29 Aug 2026 11:30:06 +0530 Subject: [PATCH 15/21] ci: isolate native BFS runner builds --- .github/workflows/normalized-native-bfs.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/normalized-native-bfs.yml b/.github/workflows/normalized-native-bfs.yml index c9b8745..3b9bcb3 100644 --- a/.github/workflows/normalized-native-bfs.yml +++ b/.github/workflows/normalized-native-bfs.yml @@ -49,10 +49,14 @@ jobs: cmake --build external/LAGraph/build --parallel 2 cmake --install external/LAGraph/build - - name: Build normalized native runner binaries + - name: Prepare normalized native runner build run: | set -euxo pipefail mkdir -p build/native artifacts/normalized + + - name: Build LAGraph native BFS runner + run: | + set -euxo pipefail cc -O3 -std=c11 -fopenmp \ -Iexternal/install/include \ -Iexternal/LAGraph/include \ @@ -62,7 +66,11 @@ jobs: -Wl,-rpath,"$PWD/external/install/lib64" \ -llagraph -lgraphblas -lm \ -o build/native/lagraph_bfs_json_runner + test -x build/native/lagraph_bfs_json_runner + - name: Build GAP native BFS runner + run: | + set -euxo pipefail # Compile the pinned GAP BFS implementation as its own translation # unit. Rename only its CLI main so DOBFS/BFSVerifier remain native # symbols that our thin JSON runner can call directly. @@ -76,7 +84,6 @@ jobs: tools/native_competitors/gap_bfs_json_runner.cc \ build/native/gap_bfs_kernel.o \ -o build/native/gap_bfs_json_runner - test -x build/native/lagraph_bfs_json_runner test -x build/native/gap_bfs_json_runner - name: Generate deterministic normalization graph From 9103dd430083d4bb2ab91801f56be6662067b46d Mon Sep 17 00:00:00 2001 From: SAURAV SINGLA Date: Sat, 29 Aug 2026 11:50:32 +0530 Subject: [PATCH 16/21] fix: use stable LAGraph runner surface --- tools/native_competitors/lagraph_bfs_json_runner.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tools/native_competitors/lagraph_bfs_json_runner.c b/tools/native_competitors/lagraph_bfs_json_runner.c index be4e0b9..b3a6ac5 100644 --- a/tools/native_competitors/lagraph_bfs_json_runner.c +++ b/tools/native_competitors/lagraph_bfs_json_runner.c @@ -1,6 +1,4 @@ #include -#include -#include #include #include #include @@ -58,7 +56,7 @@ int main(int argc, char **argv) { printf("{\"framework_version\":\"LAGraph-1.2.2\",\"distances\":["); for (int64_t i = 0; i < vertices; i++) { int64_t d = -1; - GrB_Info info = GrB_Vector_extractElement_INT64(&d, level, (GrB_Index) i); + GrB_Info info = GrB_Vector_extractElement(&d, level, (GrB_Index) i); if (info == GrB_NO_VALUE) d = -1; else if (info != GrB_SUCCESS) die("extract level failed"); if (i) putchar(','); From c17db8bc278f3ae067edf81c51c6b72e65e90fae Mon Sep 17 00:00:00 2001 From: SAURAV SINGLA Date: Sat, 29 Aug 2026 11:50:49 +0530 Subject: [PATCH 17/21] fix: link LAGraph runner via exported CMake target --- .github/workflows/normalized-native-bfs.yml | 25 +++++++++++++-------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/.github/workflows/normalized-native-bfs.yml b/.github/workflows/normalized-native-bfs.yml index 3b9bcb3..f3a7afa 100644 --- a/.github/workflows/normalized-native-bfs.yml +++ b/.github/workflows/normalized-native-bfs.yml @@ -57,15 +57,22 @@ jobs: - name: Build LAGraph native BFS runner run: | set -euxo pipefail - cc -O3 -std=c11 -fopenmp \ - -Iexternal/install/include \ - -Iexternal/LAGraph/include \ - tools/native_competitors/lagraph_bfs_json_runner.c \ - -Lexternal/install/lib -Lexternal/install/lib64 \ - -Wl,-rpath,"$PWD/external/install/lib" \ - -Wl,-rpath,"$PWD/external/install/lib64" \ - -llagraph -lgraphblas -lm \ - -o build/native/lagraph_bfs_json_runner + cat > build/native/CMakeLists.txt <<'CMAKE' + cmake_minimum_required(VERSION 3.23) + project(velographx_lagraph_runner C) + find_package(LAGraph CONFIG REQUIRED) + add_executable(lagraph_bfs_json_runner + ${CMAKE_CURRENT_LIST_DIR}/../../tools/native_competitors/lagraph_bfs_json_runner.c) + target_compile_features(lagraph_bfs_json_runner PRIVATE c_std_11) + target_compile_options(lagraph_bfs_json_runner PRIVATE -O3) + target_link_libraries(lagraph_bfs_json_runner PRIVATE SuiteSparse::LAGraph) + set_target_properties(lagraph_bfs_json_runner PROPERTIES + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) + CMAKE + cmake -S build/native -B build/native/lagraph-build \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_PREFIX_PATH="$PWD/external/install" + cmake --build build/native/lagraph-build --parallel 2 test -x build/native/lagraph_bfs_json_runner - name: Build GAP native BFS runner From 9dec245e7975774bf7056e9a24c7d9da26ef953c Mon Sep 17 00:00:00 2001 From: SAURAV SINGLA Date: Sat, 29 Aug 2026 12:43:23 +0530 Subject: [PATCH 18/21] fix: build LAGraph runner in pinned build tree --- .github/workflows/normalized-native-bfs.yml | 44 ++++++++++----------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/.github/workflows/normalized-native-bfs.yml b/.github/workflows/normalized-native-bfs.yml index f3a7afa..24f62b6 100644 --- a/.github/workflows/normalized-native-bfs.yml +++ b/.github/workflows/normalized-native-bfs.yml @@ -40,39 +40,35 @@ jobs: cmake --build external/GraphBLAS/build --parallel 2 cmake --install external/GraphBLAS/build - - name: Build LAGraph - run: | - cmake -S external/LAGraph -B external/LAGraph/build \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_PREFIX_PATH="$PWD/external/install" \ - -DCMAKE_INSTALL_PREFIX="$PWD/external/install" - cmake --build external/LAGraph/build --parallel 2 - cmake --install external/LAGraph/build - - name: Prepare normalized native runner build run: | set -euxo pipefail mkdir -p build/native artifacts/normalized - - name: Build LAGraph native BFS runner + - name: Build LAGraph and native BFS runner run: | set -euxo pipefail - cat > build/native/CMakeLists.txt <<'CMAKE' - cmake_minimum_required(VERSION 3.23) - project(velographx_lagraph_runner C) - find_package(LAGraph CONFIG REQUIRED) - add_executable(lagraph_bfs_json_runner - ${CMAKE_CURRENT_LIST_DIR}/../../tools/native_competitors/lagraph_bfs_json_runner.c) - target_compile_features(lagraph_bfs_json_runner PRIVATE c_std_11) - target_compile_options(lagraph_bfs_json_runner PRIVATE -O3) - target_link_libraries(lagraph_bfs_json_runner PRIVATE SuiteSparse::LAGraph) - set_target_properties(lagraph_bfs_json_runner PROPERTIES - RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) + # Build the thin runner in the exact pinned LAGraph build graph. This + # avoids a second find_package consumer project and links against the + # same LAGraph/GraphBLAS targets that CMake has already resolved. + cat >> external/LAGraph/CMakeLists.txt <<'CMAKE' + + add_executable(velographx_lagraph_bfs_runner + ${CMAKE_CURRENT_SOURCE_DIR}/../../tools/native_competitors/lagraph_bfs_json_runner.c) + target_compile_features(velographx_lagraph_bfs_runner PRIVATE c_std_11) + target_compile_options(velographx_lagraph_bfs_runner PRIVATE -O3) + target_link_libraries(velographx_lagraph_bfs_runner PRIVATE + LAGraph GraphBLAS::GraphBLAS) + set_target_properties(velographx_lagraph_bfs_runner PROPERTIES + OUTPUT_NAME lagraph_bfs_json_runner + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/../../build/native") CMAKE - cmake -S build/native -B build/native/lagraph-build \ + cmake -S external/LAGraph -B external/LAGraph/build \ -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_PREFIX_PATH="$PWD/external/install" - cmake --build build/native/lagraph-build --parallel 2 + -DCMAKE_PREFIX_PATH="$PWD/external/install" \ + -DCMAKE_INSTALL_PREFIX="$PWD/external/install" + cmake --build external/LAGraph/build --parallel 2 + cmake --install external/LAGraph/build test -x build/native/lagraph_bfs_json_runner - name: Build GAP native BFS runner From c400c7da3c1bb0d13e1d89893a50053c2656f4d9 Mon Sep 17 00:00:00 2001 From: SAURAV SINGLA Date: Sat, 29 Aug 2026 13:06:32 +0530 Subject: [PATCH 19/21] fix: compile GAP BFS kernel in runner translation unit --- .../native_competitors/gap_bfs_json_runner.cc | 28 ++++++------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/tools/native_competitors/gap_bfs_json_runner.cc b/tools/native_competitors/gap_bfs_json_runner.cc index 5f9c5bc..e3e3c71 100644 --- a/tools/native_competitors/gap_bfs_json_runner.cc +++ b/tools/native_competitors/gap_bfs_json_runner.cc @@ -4,25 +4,13 @@ #include #include -#include "builder.h" -#include "command_line.h" -#include "graph.h" -#include "pvector.h" - -// Match GAP Benchmark Suite v1.5's benchmark.h aliases locally without -// including benchmark.h itself. That header defines non-inline helpers and is -// already pulled into the separately compiled bfs.cc translation unit. -typedef int32_t NodeID; -typedef int32_t WeightT; -typedef CSRGraph Graph; -typedef BuilderBase Builder; - -// Implemented by the pinned GAP Benchmark Suite src/bfs.cc object that the -// workflow compiles separately. Keep these declarations in sync with GAP v1.5. -pvector DOBFS(const Graph &g, NodeID source, bool logging_enabled, - int alpha, int beta); -bool BFSVerifier(const Graph &g, NodeID source, - const pvector &parent); +// Compile the pinned GAP Benchmark Suite BFS implementation in this same +// translation unit. This keeps the runner on GAPBS v1.5's native C++11 build +// model and avoids a fragile cross-translation-unit re-declaration boundary. +// Rename only GAP's CLI entry point; DOBFS and BFSVerifier remain unchanged. +#define main gapbs_original_main +#include "bfs.cc" +#undef main static void die(const std::string &msg) { std::cerr << msg << "\n"; @@ -54,7 +42,7 @@ int main(int argc, char **argv) { args.push_back(std::to_string(source)); std::vector cargs; cargs.reserve(args.size()); - for (auto &s : args) cargs.push_back(s.data()); + for (auto &s : args) cargs.push_back(&s[0]); CLApp cli(static_cast(cargs.size()), cargs.data(), "normalized breadth-first search"); From 406444fe5f18896cb6ed4d79228478f22d8ee0cd Mon Sep 17 00:00:00 2001 From: SAURAV SINGLA Date: Sat, 29 Aug 2026 13:06:53 +0530 Subject: [PATCH 20/21] ci: build GAP runner with pinned C++11 model --- .github/workflows/normalized-native-bfs.yml | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/.github/workflows/normalized-native-bfs.yml b/.github/workflows/normalized-native-bfs.yml index 24f62b6..1af38ad 100644 --- a/.github/workflows/normalized-native-bfs.yml +++ b/.github/workflows/normalized-native-bfs.yml @@ -74,18 +74,12 @@ jobs: - name: Build GAP native BFS runner run: | set -euxo pipefail - # Compile the pinned GAP BFS implementation as its own translation - # unit. Rename only its CLI main so DOBFS/BFSVerifier remain native - # symbols that our thin JSON runner can call directly. - c++ -O3 -std=c++17 -fopenmp \ - -Iexternal/gapbs/src \ - -Dmain=gapbs_original_main \ - -c external/gapbs/src/bfs.cc \ - -o build/native/gap_bfs_kernel.o - c++ -O3 -std=c++17 -fopenmp \ + # The thin runner includes the pinned GAPBS v1.5 bfs.cc directly, + # renaming only its CLI main. GAPBS v1.5's own Makefile uses C++11, + # so keep the hosted correctness runner on that exact language mode. + c++ -O3 -std=c++11 -fopenmp \ -Iexternal/gapbs/src \ tools/native_competitors/gap_bfs_json_runner.cc \ - build/native/gap_bfs_kernel.o \ -o build/native/gap_bfs_json_runner test -x build/native/gap_bfs_json_runner From bd696da0d3c76e85135ed09806dc286ded5238b4 Mon Sep 17 00:00:00 2001 From: SAURAV SINGLA Date: Sat, 29 Aug 2026 13:33:33 +0530 Subject: [PATCH 21/21] fix: parse GAP JSON payload from native stdout --- tools/native_competitors/gap_wrapper.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/tools/native_competitors/gap_wrapper.py b/tools/native_competitors/gap_wrapper.py index ad0412b..2a8d2c5 100644 --- a/tools/native_competitors/gap_wrapper.py +++ b/tools/native_competitors/gap_wrapper.py @@ -25,6 +25,21 @@ def require_env(name: str) -> str: return value +def parse_native_payload(stdout: str) -> dict: + """Extract the runner JSON while tolerating GAPBS timing output.""" + for raw_line in reversed(stdout.splitlines()): + line = raw_line.strip() + if not line.startswith("{"): + continue + try: + payload = json.loads(line) + except json.JSONDecodeError: + continue + if isinstance(payload, dict) and "distances" in payload: + return payload + raise RuntimeError("GAP BFS runner did not emit a valid JSON distances payload") + + def main() -> int: if os.environ.get("VELOGRAPHX_ALGORITHM") != "bfs": raise RuntimeError("GAP wrapper currently supports only bfs") @@ -47,12 +62,7 @@ def main() -> int: if proc.returncode != 0: detail = proc.stderr.strip() or proc.stdout.strip() or f"exit code {proc.returncode}" raise RuntimeError(f"GAP BFS runner failed: {detail}") - try: - payload = json.loads(proc.stdout) - except json.JSONDecodeError as exc: - raise RuntimeError("GAP BFS runner must emit one JSON object") from exc - if not isinstance(payload, dict) or "distances" not in payload: - raise RuntimeError("GAP BFS runner JSON must contain distances") + payload = parse_native_payload(proc.stdout) if len(payload["distances"]) != vertices: raise RuntimeError("GAP BFS runner returned the wrong number of distances") payload.setdefault("framework_version", "GAP-local")