@graphty/webgpu-graph-algorithms 0.6.14 → 0.6.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (197) hide show
  1. package/README.md +52 -52
  2. package/dist/browser.js +1 -1
  3. package/dist/chunks/{context-Bi6AhScG.js → context-VIvatQOo.js} +69 -34
  4. package/dist/chunks/context-VIvatQOo.js.map +1 -0
  5. package/dist/node.js +1 -1
  6. package/dist/src/accelerator.d.ts +5 -3
  7. package/dist/src/accelerator.d.ts.map +1 -1
  8. package/dist/src/accelerator.js +101 -5
  9. package/dist/src/accelerator.js.map +1 -1
  10. package/dist/src/algorithms/all-pairs.d.ts +41 -0
  11. package/dist/src/algorithms/all-pairs.d.ts.map +1 -0
  12. package/dist/src/algorithms/all-pairs.js +181 -0
  13. package/dist/src/algorithms/all-pairs.js.map +1 -0
  14. package/dist/src/algorithms/betweenness.d.ts +70 -0
  15. package/dist/src/algorithms/betweenness.d.ts.map +1 -0
  16. package/dist/src/algorithms/betweenness.js +538 -0
  17. package/dist/src/algorithms/betweenness.js.map +1 -0
  18. package/dist/src/algorithms/closeness.d.ts +15 -5
  19. package/dist/src/algorithms/closeness.d.ts.map +1 -1
  20. package/dist/src/algorithms/closeness.js +112 -26
  21. package/dist/src/algorithms/closeness.js.map +1 -1
  22. package/dist/src/algorithms/components.d.ts +9 -1
  23. package/dist/src/algorithms/components.d.ts.map +1 -1
  24. package/dist/src/algorithms/components.js +2 -2
  25. package/dist/src/algorithms/components.js.map +1 -1
  26. package/dist/src/algorithms/label-propagation.d.ts +31 -0
  27. package/dist/src/algorithms/label-propagation.d.ts.map +1 -0
  28. package/dist/src/algorithms/label-propagation.js +254 -0
  29. package/dist/src/algorithms/label-propagation.js.map +1 -0
  30. package/dist/src/algorithms/simple-symmetric.d.ts +88 -0
  31. package/dist/src/algorithms/simple-symmetric.d.ts.map +1 -0
  32. package/dist/src/algorithms/simple-symmetric.js +347 -0
  33. package/dist/src/algorithms/simple-symmetric.js.map +1 -0
  34. package/dist/src/algorithms/triangles.d.ts +34 -0
  35. package/dist/src/algorithms/triangles.d.ts.map +1 -0
  36. package/dist/src/algorithms/triangles.js +203 -0
  37. package/dist/src/algorithms/triangles.js.map +1 -0
  38. package/dist/src/constants.d.ts +53 -0
  39. package/dist/src/constants.d.ts.map +1 -1
  40. package/dist/src/constants.js +53 -0
  41. package/dist/src/constants.js.map +1 -1
  42. package/dist/src/index.d.ts +12 -3
  43. package/dist/src/index.d.ts.map +1 -1
  44. package/dist/src/index.js +8 -1
  45. package/dist/src/index.js.map +1 -1
  46. package/dist/src/kernel/prelude.d.ts.map +1 -1
  47. package/dist/src/kernel/prelude.js +4 -1
  48. package/dist/src/kernel/prelude.js.map +1 -1
  49. package/dist/src/kernels.d.ts +24 -6
  50. package/dist/src/kernels.d.ts.map +1 -1
  51. package/dist/src/kernels.js +373 -7
  52. package/dist/src/kernels.js.map +1 -1
  53. package/dist/src/memory/residency.js +15 -4
  54. package/dist/src/memory/residency.js.map +1 -1
  55. package/dist/src/primitives/coo-to-csr.d.ts +73 -0
  56. package/dist/src/primitives/coo-to-csr.d.ts.map +1 -0
  57. package/dist/src/primitives/coo-to-csr.js +183 -0
  58. package/dist/src/primitives/coo-to-csr.js.map +1 -0
  59. package/dist/src/primitives/frontier.d.ts +2 -0
  60. package/dist/src/primitives/frontier.d.ts.map +1 -1
  61. package/dist/src/primitives/frontier.js +2 -0
  62. package/dist/src/primitives/frontier.js.map +1 -1
  63. package/dist/src/primitives/group-by-key.d.ts +82 -0
  64. package/dist/src/primitives/group-by-key.d.ts.map +1 -0
  65. package/dist/src/primitives/group-by-key.js +147 -0
  66. package/dist/src/primitives/group-by-key.js.map +1 -0
  67. package/dist/src/types/accelerator.d.ts +19 -7
  68. package/dist/src/types/accelerator.d.ts.map +1 -1
  69. package/dist/src/types/algorithms.d.ts +4 -0
  70. package/dist/src/types/algorithms.d.ts.map +1 -1
  71. package/dist/src/types/all-pairs.d.ts +35 -0
  72. package/dist/src/types/all-pairs.d.ts.map +1 -0
  73. package/dist/src/types/all-pairs.js +8 -0
  74. package/dist/src/types/all-pairs.js.map +1 -0
  75. package/dist/src/types/betweenness.d.ts +35 -0
  76. package/dist/src/types/betweenness.d.ts.map +1 -0
  77. package/dist/src/types/betweenness.js +7 -0
  78. package/dist/src/types/betweenness.js.map +1 -0
  79. package/dist/src/types/community.d.ts +18 -0
  80. package/dist/src/types/community.d.ts.map +1 -0
  81. package/dist/src/types/community.js +5 -0
  82. package/dist/src/types/community.js.map +1 -0
  83. package/dist/src/types/structure.d.ts +27 -0
  84. package/dist/src/types/structure.d.ts.map +1 -0
  85. package/dist/src/types/structure.js +8 -0
  86. package/dist/src/types/structure.js.map +1 -0
  87. package/dist/src/wgsl/apsp-fw.wgsl.d.ts +25 -0
  88. package/dist/src/wgsl/apsp-fw.wgsl.d.ts.map +1 -0
  89. package/dist/src/wgsl/apsp-fw.wgsl.js +113 -0
  90. package/dist/src/wgsl/apsp-fw.wgsl.js.map +1 -0
  91. package/dist/src/wgsl/apsp-init.wgsl.d.ts +12 -0
  92. package/dist/src/wgsl/apsp-init.wgsl.d.ts.map +1 -0
  93. package/dist/src/wgsl/apsp-init.wgsl.js +26 -0
  94. package/dist/src/wgsl/apsp-init.wgsl.js.map +1 -0
  95. package/dist/src/wgsl/bc-backward.wgsl.d.ts +15 -0
  96. package/dist/src/wgsl/bc-backward.wgsl.d.ts.map +1 -0
  97. package/dist/src/wgsl/bc-backward.wgsl.js +34 -0
  98. package/dist/src/wgsl/bc-backward.wgsl.js.map +1 -0
  99. package/dist/src/wgsl/bc-edge-gather.wgsl.d.ts +12 -0
  100. package/dist/src/wgsl/bc-edge-gather.wgsl.d.ts.map +1 -0
  101. package/dist/src/wgsl/bc-edge-gather.wgsl.js +36 -0
  102. package/dist/src/wgsl/bc-edge-gather.wgsl.js.map +1 -0
  103. package/dist/src/wgsl/bc-finalize.wgsl.d.ts +21 -0
  104. package/dist/src/wgsl/bc-finalize.wgsl.d.ts.map +1 -0
  105. package/dist/src/wgsl/bc-finalize.wgsl.js +47 -0
  106. package/dist/src/wgsl/bc-finalize.wgsl.js.map +1 -0
  107. package/dist/src/wgsl/bc-forward-edge.wgsl.d.ts +15 -0
  108. package/dist/src/wgsl/bc-forward-edge.wgsl.d.ts.map +1 -0
  109. package/dist/src/wgsl/bc-forward-edge.wgsl.js +76 -0
  110. package/dist/src/wgsl/bc-forward-edge.wgsl.js.map +1 -0
  111. package/dist/src/wgsl/bc-forward.wgsl.d.ts +23 -0
  112. package/dist/src/wgsl/bc-forward.wgsl.d.ts.map +1 -0
  113. package/dist/src/wgsl/bc-forward.wgsl.js +106 -0
  114. package/dist/src/wgsl/bc-forward.wgsl.js.map +1 -0
  115. package/dist/src/wgsl/bc-gather.wgsl.d.ts +9 -0
  116. package/dist/src/wgsl/bc-gather.wgsl.d.ts.map +1 -0
  117. package/dist/src/wgsl/bc-gather.wgsl.js +20 -0
  118. package/dist/src/wgsl/bc-gather.wgsl.js.map +1 -0
  119. package/dist/src/wgsl/closeness-reduce.wgsl.d.ts +4 -1
  120. package/dist/src/wgsl/closeness-reduce.wgsl.d.ts.map +1 -1
  121. package/dist/src/wgsl/closeness-reduce.wgsl.js +8 -4
  122. package/dist/src/wgsl/closeness-reduce.wgsl.js.map +1 -1
  123. package/dist/src/wgsl/closeness-sweep.wgsl.d.ts +4 -2
  124. package/dist/src/wgsl/closeness-sweep.wgsl.d.ts.map +1 -1
  125. package/dist/src/wgsl/closeness-sweep.wgsl.js +12 -2
  126. package/dist/src/wgsl/closeness-sweep.wgsl.js.map +1 -1
  127. package/dist/src/wgsl/coo-emit.wgsl.d.ts +10 -0
  128. package/dist/src/wgsl/coo-emit.wgsl.d.ts.map +1 -0
  129. package/dist/src/wgsl/coo-emit.wgsl.js +33 -0
  130. package/dist/src/wgsl/coo-emit.wgsl.js.map +1 -0
  131. package/dist/src/wgsl/coo-scatter.wgsl.d.ts +15 -0
  132. package/dist/src/wgsl/coo-scatter.wgsl.d.ts.map +1 -0
  133. package/dist/src/wgsl/coo-scatter.wgsl.js +32 -0
  134. package/dist/src/wgsl/coo-scatter.wgsl.js.map +1 -0
  135. package/dist/src/wgsl/group-by-key-row.wgsl.d.ts +26 -0
  136. package/dist/src/wgsl/group-by-key-row.wgsl.d.ts.map +1 -0
  137. package/dist/src/wgsl/group-by-key-row.wgsl.js +146 -0
  138. package/dist/src/wgsl/group-by-key-row.wgsl.js.map +1 -0
  139. package/dist/src/wgsl/lpa-step.wgsl.d.ts +10 -0
  140. package/dist/src/wgsl/lpa-step.wgsl.d.ts.map +1 -0
  141. package/dist/src/wgsl/lpa-step.wgsl.js +35 -0
  142. package/dist/src/wgsl/lpa-step.wgsl.js.map +1 -0
  143. package/dist/src/wgsl/orient-flags.wgsl.d.ts +9 -0
  144. package/dist/src/wgsl/orient-flags.wgsl.d.ts.map +1 -0
  145. package/dist/src/wgsl/orient-flags.wgsl.js +21 -0
  146. package/dist/src/wgsl/orient-flags.wgsl.js.map +1 -0
  147. package/dist/src/wgsl/run-flags.wgsl.d.ts +8 -0
  148. package/dist/src/wgsl/run-flags.wgsl.d.ts.map +1 -0
  149. package/dist/src/wgsl/run-flags.wgsl.js +18 -0
  150. package/dist/src/wgsl/run-flags.wgsl.js.map +1 -0
  151. package/dist/src/wgsl/tri-intersect.wgsl.d.ts +11 -0
  152. package/dist/src/wgsl/tri-intersect.wgsl.d.ts.map +1 -0
  153. package/dist/src/wgsl/tri-intersect.wgsl.js +64 -0
  154. package/dist/src/wgsl/tri-intersect.wgsl.js.map +1 -0
  155. package/dist/webgpu-graph-algorithms.js +2828 -321
  156. package/dist/webgpu-graph-algorithms.js.map +1 -1
  157. package/package.json +5 -5
  158. package/src/accelerator.ts +130 -7
  159. package/src/algorithms/all-pairs.ts +228 -0
  160. package/src/algorithms/betweenness.ts +739 -0
  161. package/src/algorithms/closeness.ts +124 -32
  162. package/src/algorithms/components.ts +2 -2
  163. package/src/algorithms/label-propagation.ts +280 -0
  164. package/src/algorithms/simple-symmetric.ts +409 -0
  165. package/src/algorithms/triangles.ts +240 -0
  166. package/src/constants.ts +53 -0
  167. package/src/index.ts +20 -1
  168. package/src/kernel/prelude.ts +6 -0
  169. package/src/kernels.ts +411 -10
  170. package/src/memory/residency.ts +15 -4
  171. package/src/primitives/coo-to-csr.ts +251 -0
  172. package/src/primitives/frontier.ts +4 -0
  173. package/src/primitives/group-by-key.ts +209 -0
  174. package/src/types/accelerator.ts +26 -6
  175. package/src/types/algorithms.ts +5 -0
  176. package/src/types/all-pairs.ts +37 -0
  177. package/src/types/betweenness.ts +38 -0
  178. package/src/types/community.ts +18 -0
  179. package/src/types/structure.ts +28 -0
  180. package/src/wgsl/apsp-fw.wgsl.ts +112 -0
  181. package/src/wgsl/apsp-init.wgsl.ts +25 -0
  182. package/src/wgsl/bc-backward.wgsl.ts +33 -0
  183. package/src/wgsl/bc-edge-gather.wgsl.ts +35 -0
  184. package/src/wgsl/bc-finalize.wgsl.ts +46 -0
  185. package/src/wgsl/bc-forward-edge.wgsl.ts +75 -0
  186. package/src/wgsl/bc-forward.wgsl.ts +105 -0
  187. package/src/wgsl/bc-gather.wgsl.ts +19 -0
  188. package/src/wgsl/closeness-reduce.wgsl.ts +8 -4
  189. package/src/wgsl/closeness-sweep.wgsl.ts +12 -2
  190. package/src/wgsl/coo-emit.wgsl.ts +32 -0
  191. package/src/wgsl/coo-scatter.wgsl.ts +31 -0
  192. package/src/wgsl/group-by-key-row.wgsl.ts +145 -0
  193. package/src/wgsl/lpa-step.wgsl.ts +34 -0
  194. package/src/wgsl/orient-flags.wgsl.ts +20 -0
  195. package/src/wgsl/run-flags.wgsl.ts +17 -0
  196. package/src/wgsl/tri-intersect.wgsl.ts +63 -0
  197. package/dist/chunks/context-Bi6AhScG.js.map +0 -1
@@ -1,6 +1,6 @@
1
- import { W as WebGpuGraphError, U as UNIFORM_SLOT_BYTES, B as BufferUsage, M as MAX_WORKGROUPS_PER_DIM, a as WGSL_RESERVED_WORDS, S as STATE_HEADER_BYTES, d as deviceLostError, i as isWebGpuGraphError, b as U32_MAX$2, c as MAX_LEVELS_PER_SUBMIT, R as RADIX_BINS, F as FUSED_FRONTIER_MAX, e as BEAMER_BETA, f as SSSP_DELTA_FACTOR, g as F32_INF_BITS, h as GRID_COARSEST_SIDE, j as GRID_MIN_SIDE, k as GRID_SORT_BITS, l as FA2_DEFAULTS, m as MAX_ITERATIONS_PER_STEP, n as MAX_1D_ITEMS, o as hasErrorCode, p as FA2_FLAG_FIRST, P as PARTIAL_BYTES, E as EXACT_TILES_PER_PASS, I as INDIRECT_ARGS_STRIDE, q as GRID_HUB_CELL, L as LAYOUT_TUNING_DEFAULTS, r as EXACT_MAX_NODES, s as SETTLE_FLOOR_UNBOUNDED, T as TRACE_RECORD_BYTES, t as GRID_BBOX_MARGIN, u as GRID_EXTENT_FLOOR, v as FR_ADAPTIVE_MAX_ITERATIONS, w as FR_START_TEMPERATURE, x as FA2_FLAG_ADAPTIVE, y as SETTLE_FLOOR_FRACTION, z as FR_REHEAT_FRACTION, A as FR_DEFAULTS, C as SE_DEFAULTS, D as SETTLE_FLOOR_REFERENCE_NODES, H as SE_SCALE_REFERENCE_NODES } from "./chunks/context-Bi6AhScG.js";
2
- import { J, G, K, N, O, Q } from "./chunks/context-Bi6AhScG.js";
3
- import { renumberPartition, INVALID_INDEX, makeMask, maskTest, expandEdges, fromEdgeArrays } from "@graphty/graph-format";
1
+ import { W as WebGpuGraphError, U as UNIFORM_SLOT_BYTES, B as BufferUsage, M as MAX_WORKGROUPS_PER_DIM, a as WGSL_RESERVED_WORDS, S as STATE_HEADER_BYTES, d as deviceLostError, i as isWebGpuGraphError, b as U32_MAX$2, c as MAX_LEVELS_PER_SUBMIT, R as RADIX_BINS, F as FUSED_FRONTIER_MAX, e as BEAMER_BETA, f as SSSP_DELTA_FACTOR, g as F32_INF_BITS, h as BC_EDGE_PARALLEL_GAMMA, j as BC_BATCH_BUDGET_FRACTION, k as BC_MAX_BATCH, l as BC_BACKWARD_LEVELS_PER_SUBMIT, A as APSP_MAX_DISPATCHES_PER_SUBMIT, m as APSP_TILE, n as GROUP_ROW_THREAD_MAX, o as GROUP_ROW_THREAD_LIMIT, p as GROUP_HASH_LOAD_FACTOR, P as PARALLEL_MERGE_LIMIT, L as LABEL_PROP_PASSES_PER_SUBMIT, q as GRID_COARSEST_SIDE, r as GRID_MIN_SIDE, s as GRID_SORT_BITS, t as FA2_DEFAULTS, u as MAX_ITERATIONS_PER_STEP, v as MAX_1D_ITEMS, w as hasErrorCode, x as FA2_FLAG_FIRST, y as PARTIAL_BYTES, E as EXACT_TILES_PER_PASS, I as INDIRECT_ARGS_STRIDE, z as GRID_HUB_CELL, C as LAYOUT_TUNING_DEFAULTS, D as EXACT_MAX_NODES, H as SETTLE_FLOOR_UNBOUNDED, T as TRACE_RECORD_BYTES, J as GRID_BBOX_MARGIN, K as GRID_EXTENT_FLOOR, N as FR_ADAPTIVE_MAX_ITERATIONS, O as FR_START_TEMPERATURE, Q as FA2_FLAG_ADAPTIVE, V as SETTLE_FLOOR_FRACTION, X as FR_REHEAT_FRACTION, Y as FR_DEFAULTS, Z as SE_DEFAULTS, _ as SETTLE_FLOOR_REFERENCE_NODES, $ as SE_SCALE_REFERENCE_NODES } from "./chunks/context-VIvatQOo.js";
2
+ import { a0, G, a1, a2, a3, a4 } from "./chunks/context-VIvatQOo.js";
3
+ import { renumberPartition, INVALID_INDEX, foldArcs, makeMask, maskTest, expandEdges, fromEdgeArrays } from "@graphty/graph-format";
4
4
  class UniformRing {
5
5
  /**
6
6
  * Creates the ring buffer (`slots x UNIFORM_SLOT_BYTES` bytes, UNIFORM | COPY_DST) through the allocator.
@@ -211,6 +211,10 @@ function plan1d(items, wg, caps) {
211
211
  assertWorkgroupSize(wg);
212
212
  return grid(Math.ceil(items / wg), items, caps);
213
213
  }
214
+ function plan2d(groups, caps) {
215
+ assertCount$1("groups", groups);
216
+ return grid(groups, groups, caps);
217
+ }
214
218
  function planGridStride(items, wg, caps, maxGroups) {
215
219
  assertCount$1("items", items);
216
220
  assertWorkgroupSize(wg);
@@ -605,6 +609,364 @@ fn advance_expand(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocati
605
609
  }
606
610
  `
607
611
  );
612
+ const apspFwWgsl = (
613
+ /* wgsl */
614
+ `
615
+ var<workgroup> tileA: array<f32, APSP_TILE * APSP_TILE>;
616
+ var<workgroup> tileB: array<f32, APSP_TILE * APSP_TILE>;
617
+
618
+ fn tile_row(block: vec2<u32>, c: u32) -> u32 { return block.x * APSP_TILE + c / APSP_TILE; }
619
+ fn tile_col(block: vec2<u32>, c: u32) -> u32 { return block.y * APSP_TILE + c % APSP_TILE; }
620
+
621
+ fn load_cell(block: vec2<u32>, c: u32) -> f32 {
622
+ let i = tile_row(block, c);
623
+ let j = tile_col(block, c);
624
+ if (i < P.n && j < P.n) { return dist[i * P.n + j]; }
625
+ return bitcast<f32>(P.infBits); // outside the matrix: no path through it
626
+ }
627
+
628
+ fn store_cell(block: vec2<u32>, c: u32, v: f32) {
629
+ let i = tile_row(block, c);
630
+ let j = tile_col(block, c);
631
+ if (i >= P.n || j >= P.n) { return; } // an edge tile stores nothing outside n x n
632
+ dist[i * P.n + j] = v;
633
+ }
634
+
635
+ @compute @workgroup_size(WG)
636
+ fn apsp_fw(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
637
+ let g = group_id(wid);
638
+ let r = P.round;
639
+ let others = max(P.blocks, 2u) - 1u; // the blocks of a strip, the pivot excluded
640
+ let cells = APSP_TILE * APSP_TILE;
641
+ var count = 1u;
642
+ var own = vec2<u32>(r, r); // the block this workgroup updates
643
+ var a = vec2<u32>(r, r); // staged in tileA
644
+ var b = vec2<u32>(r, r); // staged in tileB
645
+ if (PHASE == 1u) {
646
+ count = 2u * others;
647
+ let s = g % others;
648
+ let o = select(s, s + 1u, s >= r); // a strip skips the pivot block
649
+ own = select(vec2<u32>(o, r), vec2<u32>(r, o), g < others); // block row r first, then block column r
650
+ b = own;
651
+ }
652
+ if (PHASE == 2u) {
653
+ count = others * others;
654
+ let i = g / others;
655
+ let j = g % others;
656
+ own = vec2<u32>(select(i, i + 1u, i >= r), select(j, j + 1u, j >= r));
657
+ a = vec2<u32>(own.x, r); // the pivot-column block (i, r)
658
+ b = vec2<u32>(r, own.y); // the pivot-row block (r, j)
659
+ }
660
+ if (g >= count) { return; } // uniform: the workgroup id and uniforms only
661
+
662
+ for (var c = lid.x; c < cells; c = c + WG) {
663
+ tileA[c] = load_cell(a, c);
664
+ if (PHASE != 0u) { tileB[c] = load_cell(b, c); }
665
+ }
666
+ workgroupBarrier(); // every staged cell is visible
667
+
668
+ if (PHASE == 2u) {
669
+ for (var c = lid.x; c < cells; c = c + WG) {
670
+ let x = c / APSP_TILE;
671
+ let y = c % APSP_TILE;
672
+ var v = load_cell(own, c);
673
+ for (var kr = 0u; kr < APSP_TILE; kr = kr + 1u) {
674
+ v = min(v, tileA[x * APSP_TILE + kr] + tileB[kr * APSP_TILE + y]);
675
+ }
676
+ store_cell(own, c, v);
677
+ }
678
+ return;
679
+ }
680
+
681
+ let pivotRow = PHASE == 1u && own.x == r; // block row r reads d[x][k] from the pivot
682
+ for (var k = 0u; k < APSP_TILE; k = k + 1u) {
683
+ for (var c = lid.x; c < cells; c = c + WG) {
684
+ let x = c / APSP_TILE;
685
+ let y = c % APSP_TILE;
686
+ if (PHASE == 0u) {
687
+ let via = tileA[x * APSP_TILE + k] + tileA[k * APSP_TILE + y];
688
+ if (via < tileA[c]) { tileA[c] = via; }
689
+ } else {
690
+ let left = select(tileB[x * APSP_TILE + k], tileA[x * APSP_TILE + k], pivotRow);
691
+ let right = select(tileA[k * APSP_TILE + y], tileB[k * APSP_TILE + y], pivotRow);
692
+ let via = left + right;
693
+ if (via < tileB[c]) { tileB[c] = via; }
694
+ }
695
+ }
696
+ workgroupBarrier(); // step k is complete before step k + 1 reads
697
+ }
698
+ for (var c = lid.x; c < cells; c = c + WG) {
699
+ store_cell(own, c, select(tileB[c], tileA[c], PHASE == 0u));
700
+ }
701
+ }
702
+ `
703
+ );
704
+ const apspInitWgsl = (
705
+ /* wgsl */
706
+ `
707
+ @compute @workgroup_size(WG)
708
+ fn apsp_init(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
709
+ let u = linear_id(wid, lid.x);
710
+ if (u >= P.n) { return; }
711
+ let rowBase = u * P.n;
712
+ let end = rowPtr[u + 1u];
713
+ for (var a = rowPtr[u]; a < end; a = a + 1u) {
714
+ let v = colIdx[a];
715
+ let w = select(1.0, weights[a], HAS_WEIGHTS);
716
+ dist[rowBase + v] = min(dist[rowBase + v], w); // parallel arcs collapse to the cheapest
717
+ }
718
+ dist[rowBase + u] = 0.0; // last: a self-loop never displaces the zero
719
+ }
720
+ `
721
+ );
722
+ const bcBackwardWgsl = (
723
+ /* wgsl */
724
+ `
725
+ @compute @workgroup_size(WG)
726
+ fn bc_backward(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
727
+ for (var i = linear_id(wid, lid.x); i < P.count; i = i + P.stride) {
728
+ let t = S[P.start + i]; // s * n + w
729
+ let w = t % P.n;
730
+ let base = t - w; // s * n
731
+ let succ = depthK[t] + 1u;
732
+ let sw = f32(sigmaK[t]);
733
+ var acc = 0.0;
734
+ for (var a = rowPtr[w]; a < rowPtr[w + 1u]; a = a + 1u) {
735
+ let v = base + colIdx[a];
736
+ if (depthK[v] == succ) { // v is a successor of w for source s
737
+ acc = acc + (sw / f32(sigmaK[v])) * (1.0 + deltaK[v]);
738
+ }
739
+ }
740
+ deltaK[t] = acc; // written once per (w, s)
741
+ }
742
+ }
743
+ `
744
+ );
745
+ const bcEdgeGatherWgsl = (
746
+ /* wgsl */
747
+ `
748
+ @compute @workgroup_size(WG)
749
+ fn bc_edge_gather(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
750
+ for (var a = linear_id(wid, lid.x); a < P.count; a = a + P.stride) {
751
+ var lo = 0u; // the row w with rowPtr[w] <= a < rowPtr[w + 1]
752
+ var hi = P.n;
753
+ loop {
754
+ if (lo >= hi) { break; }
755
+ let mid = (lo + hi) / 2u;
756
+ if (rowPtr[mid + 1u] <= a) { lo = mid + 1u; } else { hi = mid; }
757
+ }
758
+ let w = lo;
759
+ let nbr = colIdx[a];
760
+ var acc = arcScores[a];
761
+ for (var s = 0u; s < P.k; s = s + 1u) {
762
+ let base = s * P.n;
763
+ let dw = depthK[base + w];
764
+ if (dw != INVALID_INDEX && depthK[base + nbr] == dw + 1u) { // (w, nbr) is on a shortest path from s
765
+ acc = acc + (f32(sigmaK[base + w]) / f32(sigmaK[base + nbr])) * (1.0 + deltaK[base + nbr]);
766
+ }
767
+ }
768
+ arcScores[a] = acc;
769
+ }
770
+ }
771
+ `
772
+ );
773
+ const bcFinalizeWgsl = (
774
+ /* wgsl */
775
+ `
776
+ @compute @workgroup_size(WG)
777
+ fn bc_finalize(@builtin(local_invocation_id) lid: vec3<u32>) {
778
+ if (lid.x != 0u) { return; } // one lane; no barrier follows (3.5 rule 1)
779
+ if (P.role == 1u) { // the seed of a batch
780
+ for (var i = 0u; i < P.k; i = i + 1u) {
781
+ let t = S[i];
782
+ depthK[t] = 0u; // the source is at depth 0
783
+ sigmaK[t] = 1u; // with one shortest path, itself
784
+ }
785
+ ends[0] = 0u;
786
+ atomicStore(&counters[26], P.k); // stackTop: the seeds are the log's first k entries
787
+ atomicStore(&counters[27], 0u); // sigmaOverflow
788
+ atomicStore(&counters[11], U32_MAX); // level: the first boundary brings it to 0
789
+ atomicStore(&counters[15], 0u); // done
790
+ return;
791
+ }
792
+ if (atomicLoad(&counters[15]) != 0u) { return; } // done: a no-op level the host recorded past the end
793
+ let level = atomicLoad(&counters[11]) + 1u;
794
+ let top = atomicLoad(&counters[26]);
795
+ ends[level + 1u] = top; // the level's entries end where the log ends now
796
+ let count = top - ends[level];
797
+ atomicStore(&counters[0], count); // frontierCount (the inspect seam reads it)
798
+ atomicStore(&counters[11], level);
799
+ atomicStore(&counters[15], select(0u, 1u, count == 0u)); // an empty level ends the batch
800
+ }
801
+ `
802
+ );
803
+ const bcForwardWgsl = (
804
+ /* wgsl */
805
+ `
806
+ var<workgroup> sh: array<u32, WG>; // the block's degrees, then their inclusive scan
807
+ var<workgroup> rowStart: array<u32, WG>; // the first arc of each entry's row
808
+ var<workgroup> entryOf: array<u32, WG>; // each entry, s * n + u
809
+ var<workgroup> wstart: u32; // the level's first log index
810
+ var<workgroup> wcount: u32; // the level's entry count
811
+ var<workgroup> wwon: atomic<u32>; // the strip's winners
812
+ var<workgroup> wbase: u32; // where the strip's winners go in the log
813
+
814
+ @compute @workgroup_size(WG)
815
+ fn bc_forward(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
816
+ let level = atomicLoad(&counters[11]);
817
+ if (lid.x == 0u) {
818
+ let lo = ends[level];
819
+ wstart = lo;
820
+ wcount = ends[level + 1u] - lo;
821
+ }
822
+ let start = workgroupUniformLoad(&wstart);
823
+ let count = workgroupUniformLoad(&wcount); // uniform: the block loop below holds barriers
824
+ let next = level + 1u;
825
+ for (var b0 = group_id(wid) * WG; b0 < count; b0 = b0 + P.stride) { // grid-stride over blocks of WG entries
826
+ let i = b0 + lid.x;
827
+ var deg = 0u;
828
+ var first = 0u;
829
+ var entry = 0u;
830
+ if (i < count) { // guarded loads into locals (3.5 rule 1)
831
+ entry = S[start + i];
832
+ let u = entry % P.n;
833
+ first = rowPtr[u];
834
+ deg = rowPtr[u + 1u] - first;
835
+ }
836
+ sh[lid.x] = deg;
837
+ rowStart[lid.x] = first;
838
+ entryOf[lid.x] = entry;
839
+ workgroupBarrier();
840
+ for (var s = 1u; s < WG; s = s * 2u) { // Hillis-Steele inclusive scan of the degrees
841
+ var t = 0u;
842
+ if (lid.x >= s) { t = sh[lid.x - s]; }
843
+ workgroupBarrier();
844
+ sh[lid.x] = sh[lid.x] + t;
845
+ workgroupBarrier();
846
+ }
847
+ let aggregate = workgroupUniformLoad(&sh[WG - 1u]); // uniform; includes a barrier
848
+ for (var p0 = 0u; p0 < aggregate; p0 = p0 + WG) { // strip [0, aggregate) WG arcs at a time
849
+ let p = p0 + lid.x;
850
+ var won = false;
851
+ var claimed = 0u;
852
+ if (p < aggregate) {
853
+ var lo = 0u; // upper_bound: the first k with sh[k] > p owns arc p
854
+ var hi = WG;
855
+ loop {
856
+ if (lo >= hi) { break; }
857
+ let mid = (lo + hi) / 2u;
858
+ if (sh[mid] > p) { hi = mid; } else { lo = mid + 1u; }
859
+ }
860
+ let k = lo;
861
+ var exclusive = 0u;
862
+ if (k > 0u) { exclusive = sh[k - 1u]; }
863
+ let origin = entryOf[k]; // s * n + u
864
+ let x = (origin - (origin % P.n)) + colIdx[rowStart[k] + (p - exclusive)]; // s * n + x
865
+ if (atomicLoad(&depthK[x]) == INVALID_INDEX) { // the pre-check of design 16.1
866
+ won = atomicMin(&depthK[x], next) == INVALID_INDEX; // the claim: the one winner appends
867
+ }
868
+ if (atomicLoad(&depthK[x]) == next) { // the count: EVERY arc on a shortest path adds
869
+ let add = atomicLoad(&sigmaK[origin]);
870
+ let old = atomicAdd(&sigmaK[x], add);
871
+ if (old + add < old) { atomicOr(&counters[27], 1u); } // the u32 wrap, reported
872
+ }
873
+ claimed = x;
874
+ }
875
+ var slot = 0u;
876
+ if (won) { slot = atomicAdd(&wwon, 1u); }
877
+ workgroupBarrier();
878
+ if (lid.x == 0u) {
879
+ wbase = atomicAdd(&counters[26], atomicLoad(&wwon)); // stackTop: one global atomic per strip
880
+ atomicStore(&wwon, 0u);
881
+ }
882
+ workgroupBarrier();
883
+ if (won) { S[wbase + slot] = claimed; }
884
+ }
885
+ workgroupBarrier(); // sh, rowStart and entryOf are reused by the next block
886
+ }
887
+ }
888
+ `
889
+ );
890
+ const bcForwardEdgeWgsl = (
891
+ /* wgsl */
892
+ `
893
+ var<workgroup> wlive: u32; // 1 when the level has entries
894
+ var<workgroup> wwon: atomic<u32>; // the strip's winners
895
+ var<workgroup> wbase: u32; // where the strip's winners go in the log
896
+
897
+ fn claim(x: u32, next: u32) -> bool {
898
+ if (atomicLoad(&depthK[x]) != INVALID_INDEX) { return false; } // the pre-check of design 16.1
899
+ return atomicMin(&depthK[x], next) == INVALID_INDEX;
900
+ }
901
+
902
+ fn count_paths(origin: u32, x: u32, next: u32) {
903
+ if (atomicLoad(&depthK[x]) == next) { // every arc on a shortest path adds
904
+ let add = atomicLoad(&sigmaK[origin]);
905
+ let old = atomicAdd(&sigmaK[x], add);
906
+ if (old + add < old) { atomicOr(&counters[27], 1u); } // the u32 wrap, reported
907
+ }
908
+ }
909
+
910
+ @compute @workgroup_size(WG)
911
+ fn bc_forward_edge(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
912
+ let level = atomicLoad(&counters[11]);
913
+ if (lid.x == 0u) { wlive = select(0u, 1u, ends[level + 1u] > ends[level]); }
914
+ if (workgroupUniformLoad(&wlive) == 0u) { return; } // uniform: nothing below runs on an empty level
915
+ let next = level + 1u;
916
+ for (var s = 0u; s < P.k; s = s + 1u) {
917
+ let base = s * P.n;
918
+ for (var e0 = group_id(wid) * WG; e0 < P.count; e0 = e0 + P.stride) { // grid-stride over the edges
919
+ let e = e0 + lid.x;
920
+ var a = INVALID_INDEX; // the claims this lane won
921
+ var b = INVALID_INDEX;
922
+ if (e < P.count) {
923
+ let u = base + edgeSrc[e];
924
+ let x = base + edgeDst[e];
925
+ if (atomicLoad(&depthK[u]) == level) {
926
+ if (claim(x, next)) { a = x; }
927
+ count_paths(u, x, next);
928
+ }
929
+ if (UNDIRECTED) { // the other direction of an undirected edge
930
+ if (atomicLoad(&depthK[x]) == level) {
931
+ if (claim(u, next)) { b = u; }
932
+ count_paths(x, u, next);
933
+ }
934
+ }
935
+ }
936
+ let mine = select(0u, 1u, a != INVALID_INDEX) + select(0u, 1u, b != INVALID_INDEX);
937
+ var slot = 0u;
938
+ if (mine != 0u) { slot = atomicAdd(&wwon, mine); }
939
+ workgroupBarrier();
940
+ if (lid.x == 0u) {
941
+ wbase = atomicAdd(&counters[26], atomicLoad(&wwon)); // stackTop: one global atomic per strip
942
+ atomicStore(&wwon, 0u);
943
+ }
944
+ workgroupBarrier();
945
+ if (a != INVALID_INDEX) {
946
+ S[wbase + slot] = a;
947
+ slot = slot + 1u;
948
+ }
949
+ if (b != INVALID_INDEX) { S[wbase + slot] = b; }
950
+ }
951
+ }
952
+ }
953
+ `
954
+ );
955
+ const bcGatherWgsl = (
956
+ /* wgsl */
957
+ `
958
+ @compute @workgroup_size(WG)
959
+ fn bc_gather(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
960
+ for (var w = linear_id(wid, lid.x); w < P.n; w = w + P.stride) {
961
+ var acc = bc[w];
962
+ for (var s = 0u; s < P.k; s = s + 1u) {
963
+ acc = acc + deltaK[s * P.n + w];
964
+ }
965
+ bc[w] = acc;
966
+ }
967
+ }
968
+ `
969
+ );
608
970
  const bfRelaxWgsl = (
609
971
  /* wgsl */
610
972
  `
@@ -854,13 +1216,14 @@ const closenessReduceWgsl = (
854
1216
  @compute @workgroup_size(WG)
855
1217
  fn closeness_reduce(@builtin(local_invocation_id) lid: vec3<u32>) {
856
1218
  if (lid.x != 0u) { return; } // one lane; no barrier follows (3.5 rule 1)
857
- if (P.role == 1u) { // the seed of a batch: P.source is its first source
1219
+ if (P.role != 0u) { // the seed of a batch: P.source is its first source
858
1220
  let k = min(32u, P.n - P.source);
859
1221
  for (var s = 0u; s < k; s = s + 1u) {
860
- let v = P.source + s;
1222
+ var v = P.source + s;
1223
+ if (P.role == 2u) { v = atomicLoad(&perSource[128u + P.bitsBase + P.source + s]); } // a sampled run's list
861
1224
  let bit = 1u << s;
862
- bits[v] = bit; // visited
863
- bits[P.bitsBase + v] = bit; // the frontier level 0 reads (region 1: level 0's parity is 0)
1225
+ bits[v] = bits[v] | bit; // visited
1226
+ bits[P.bitsBase + v] = bits[P.bitsBase + v] | bit; // the frontier level 0 reads (region 1: level 0's parity is 0)
864
1227
  bits[3u * P.bitsBase + v] = 1u; // flags: level 0's compact turns them into the list
865
1228
  }
866
1229
  atomicStore(&counters[0], k); // not done
@@ -908,11 +1271,16 @@ var<workgroup> rowStart: array<u32, WG>; // the first bound arc of each
908
1271
  var<workgroup> rowOf: array<u32, WG>; // the frontier vertex of each entry (the source end of its arcs)
909
1272
  var<workgroup> local: array<atomic<u32>, 32>; // this workgroup's fresh claims per source
910
1273
  var<workgroup> wcount: u32; // the frontier list's length
1274
+ var<workgroup> wdist: u32; // the distance of this level's claims
911
1275
 
912
1276
  @compute @workgroup_size(WG)
913
1277
  fn closeness_sweep(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
914
- if (lid.x == 0u) { wcount = atomicLoad(&counters[0]); } // the frontier list's length (compact's total)
1278
+ if (lid.x == 0u) {
1279
+ wcount = atomicLoad(&counters[0]); // the frontier list's length (compact's total)
1280
+ wdist = atomicLoad(&counters[11]) + 1u; // the level word: this level claims at level + 1
1281
+ }
915
1282
  let count = workgroupUniformLoad(&wcount); // uniform: the block loop below holds barriers
1283
+ let dist = workgroupUniformLoad(&wdist);
916
1284
  let nextBase = select(2u * P.bitsBase, P.bitsBase, P.mode == 1u); // the region that is next this level
917
1285
  let frontierBase = 3u * P.bitsBase - nextBase; // the other one: the region that is the frontier
918
1286
  for (var b0 = group_id(wid) * WG; b0 < count; b0 = b0 + P.stride) { // grid-stride over blocks of WG entries
@@ -960,6 +1328,9 @@ fn closeness_sweep(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocat
960
1328
  if (fresh != 0u) {
961
1329
  atomicOr(&bits[nextBase + x], fresh);
962
1330
  atomicStore(&bits[3u * P.bitsBase + x], 1u); // flags: x is in the next frontier list (compact reads it)
1331
+ if (P.perNode == 1u) { // a sampled run: x's distance to each source won
1332
+ atomicAdd(&perSource[128u + x], countOneBits(fresh) * dist);
1333
+ }
963
1334
  var b = fresh;
964
1335
  loop { // one tally per set bit of fresh
965
1336
  if (b == 0u) { break; }
@@ -992,6 +1363,54 @@ fn compact_scatter(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocat
992
1363
  }
993
1364
  `
994
1365
  );
1366
+ const cooEmitWgsl = (
1367
+ /* wgsl */
1368
+ `
1369
+ @compute @workgroup_size(WG)
1370
+ fn coo_emit(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
1371
+ let i = linear_id(wid, lid.x);
1372
+ if (i >= P.count) { return; } // no barrier follows
1373
+ var a = i;
1374
+ if (INDEXED) { a = order[i]; } // the arc this position takes
1375
+ let e = a / 2u; // arc 2e is edge e as declared, arc 2e + 1 its reverse
1376
+ let forward = (a % 2u) == 0u;
1377
+ let u = edgeSrc[e];
1378
+ let v = edgeDst[e];
1379
+ var w = 1.0;
1380
+ if (WEIGHTED) { w = edgeWeight[e]; }
1381
+ if (u == v) { // a self-loop is dropped: both arcs sort last and never open a run
1382
+ outSrc[i] = INVALID_INDEX;
1383
+ outDst[i] = INVALID_INDEX;
1384
+ outWeight[i] = 0.0;
1385
+ return;
1386
+ }
1387
+ outSrc[i] = select(v, u, forward);
1388
+ outDst[i] = select(u, v, forward);
1389
+ outWeight[i] = w;
1390
+ }
1391
+ `
1392
+ );
1393
+ const cooScatterWgsl = (
1394
+ /* wgsl */
1395
+ `
1396
+ @compute @workgroup_size(WG)
1397
+ fn coo_scatter(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
1398
+ let i = linear_id(wid, lid.x);
1399
+ if (i >= P.count) { return; } // no barrier follows
1400
+ let s = src[i];
1401
+ var slot = 0u;
1402
+ if (SORTED_INPUT) {
1403
+ if (i > 0u && s < src[i - 1u]) { atomicStore(&cursors[0], 1u); } // unsorted input: the flag the driver refuses
1404
+ let within = i - rowPtr[s]; // the arc's place in its row
1405
+ slot = rowPtr[s] + within;
1406
+ } else {
1407
+ slot = rowPtr[s] + atomicAdd(&cursors[s], 1u); // race order: the row is not sorted in this mode
1408
+ }
1409
+ colIdx[slot] = dst[i];
1410
+ if (WEIGHTED) { outWeight[slot] = weight[i]; }
1411
+ }
1412
+ `
1413
+ );
995
1414
  const countingScatterWgsl = (
996
1415
  /* wgsl */
997
1416
  `
@@ -1931,6 +2350,130 @@ fn grid_near_field(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocat
1931
2350
  }
1932
2351
  `
1933
2352
  );
2353
+ const groupByKeyRowWgsl = (
2354
+ /* wgsl */
2355
+ `
2356
+ var<workgroup> shMax: array<f32, WG>;
2357
+ var<workgroup> shKey: array<u32, WG>;
2358
+ var<workgroup> shSum: array<u32, WG>;
2359
+
2360
+ fn weight_of(a: u32) -> f32 {
2361
+ if (WEIGHTED) { return weights[a]; }
2362
+ return 1.0;
2363
+ }
2364
+ fn scale_of(maxW: f32, d: u32) -> f32 { // 2^s, maxW x d x 2^s in [2^28, 2^30), from exponents alone
2365
+ let em = i32((bitcast<u32>(maxW) >> 23u) & 255u) - 127;
2366
+ let ed = i32(firstLeadingBit(max(d, 1u)));
2367
+ let s = clamp(28 - em - ed, -126, 126);
2368
+ return bitcast<f32>(u32(s + 127) << 23u);
2369
+ }
2370
+ fn inverse_of(scale: f32) -> f32 { // 2^-s, exact
2371
+ let s = i32((bitcast<u32>(scale) >> 23u) & 255u) - 127;
2372
+ return bitcast<f32>(u32(127 - s) << 23u);
2373
+ }
2374
+ fn quantize(w: f32, scale: f32) -> u32 { // nearest integer, halves up; every step exact
2375
+ let x = max(w * scale, 0.0);
2376
+ let i = u32(x);
2377
+ return select(i, i + 1u, x - f32(i) >= 0.5);
2378
+ }
2379
+ fn better(sum: u32, key: u32, bestSum: u32, bestKey0: u32) -> bool { return sum > bestSum || (sum == bestSum && key < bestKey0); }
2380
+
2381
+ fn row_thread(v: u32) {
2382
+ let lo = rowPtr[v];
2383
+ let hi = rowPtr[v + 1u];
2384
+ var maxW = 0.0;
2385
+ for (var a = lo; a < hi; a = a + 1u) { maxW = max(maxW, weight_of(a)); }
2386
+ let scale = scale_of(maxW, hi - lo);
2387
+ var bk = INVALID_INDEX;
2388
+ var bs = 0u;
2389
+ for (var a = lo; a < hi; a = a + 1u) {
2390
+ let k = keyIn[colIdx[a]];
2391
+ var seen = false;
2392
+ for (var b = lo; b < a; b = b + 1u) { if (keyIn[colIdx[b]] == k) { seen = true; break; } }
2393
+ if (seen) { continue; } // this key was summed at its first arc
2394
+ var sum = 0u;
2395
+ for (var b = a; b < hi; b = b + 1u) { if (keyIn[colIdx[b]] == k) { sum = sum + quantize(weight_of(b), scale); } }
2396
+ if (better(sum, k, bs, bk)) { bk = k; bs = sum; }
2397
+ }
2398
+ bestKey[v] = bk;
2399
+ bestScore[v] = f32(bs) * inverse_of(scale);
2400
+ }
2401
+
2402
+ fn row_hash(g: u32, lid: u32) {
2403
+ let v = rows[P.rowsBase + g];
2404
+ let base = rows[P.basesBase + g];
2405
+ let lo = rowPtr[v];
2406
+ let hi = rowPtr[v + 1u];
2407
+ let cap = GROUP_HASH_LOAD_FACTOR * (hi - lo);
2408
+ var m = 0.0;
2409
+ for (var a = lo + lid; a < hi; a = a + WG) { m = max(m, weight_of(a)); }
2410
+ shMax[lid] = m;
2411
+ workgroupBarrier();
2412
+ for (var s = WG / 2u; s > 0u; s = s / 2u) {
2413
+ if (lid < s) { shMax[lid] = max(shMax[lid], shMax[lid + s]); }
2414
+ workgroupBarrier();
2415
+ }
2416
+ let scale = scale_of(shMax[0], hi - lo);
2417
+ for (var j = lid; j < cap; j = j + WG) { // the region is this workgroup's alone: clear it
2418
+ atomicStore(&hashRegion[base + 2u * j], INVALID_INDEX);
2419
+ atomicStore(&hashRegion[base + 2u * j + 1u], 0u);
2420
+ }
2421
+ storageBarrier();
2422
+ var exhausted = false;
2423
+ for (var a = lo + lid; a < hi; a = a + WG) {
2424
+ let k = keyIn[colIdx[a]];
2425
+ let q = quantize(weight_of(a), scale);
2426
+ var slot = lowbias32(k) % cap;
2427
+ var steps = 0u;
2428
+ loop {
2429
+ if (steps >= cap + 64u) { exhausted = true; break; } // bounded: a spurious compare-exchange failure is legal (WGSL 17.8.5)
2430
+ steps = steps + 1u;
2431
+ let r = atomicCompareExchangeWeak(&hashRegion[base + 2u * slot], INVALID_INDEX, k);
2432
+ if (r.exchanged || r.old_value == k) { atomicAdd(&hashRegion[base + 2u * slot + 1u], q); break; }
2433
+ if (r.old_value == INVALID_INDEX) { continue; } // a spurious failure: the same slot again
2434
+ slot = (slot + 1u) % cap; // linear probing
2435
+ }
2436
+ }
2437
+ if (exhausted) { atomicStore(&hashRegion[0], 1u); }
2438
+ storageBarrier();
2439
+ var bk = INVALID_INDEX;
2440
+ var bs = 0u;
2441
+ for (var j = lid; j < cap; j = j + WG) {
2442
+ let k = atomicLoad(&hashRegion[base + 2u * j]);
2443
+ if (k != INVALID_INDEX) {
2444
+ let sum = atomicLoad(&hashRegion[base + 2u * j + 1u]);
2445
+ if (better(sum, k, bs, bk)) { bk = k; bs = sum; }
2446
+ }
2447
+ }
2448
+ shKey[lid] = bk;
2449
+ shSum[lid] = bs;
2450
+ workgroupBarrier();
2451
+ for (var s = WG / 2u; s > 0u; s = s / 2u) {
2452
+ if (lid < s && better(shSum[lid + s], shKey[lid + s], shSum[lid], shKey[lid])) {
2453
+ shKey[lid] = shKey[lid + s];
2454
+ shSum[lid] = shSum[lid + s];
2455
+ }
2456
+ workgroupBarrier();
2457
+ }
2458
+ if (lid == 0u) {
2459
+ bestKey[v] = shKey[0];
2460
+ bestScore[v] = f32(shSum[0]) * inverse_of(scale);
2461
+ }
2462
+ }
2463
+
2464
+ @compute @workgroup_size(WG)
2465
+ fn group_by_key_row(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
2466
+ if (TIER == 0u) {
2467
+ let i = linear_id(wid, lid.x);
2468
+ if (i < P.count) { row_thread(rows[P.rowsBase + i]); }
2469
+ return; // TIER is an override: uniform
2470
+ }
2471
+ let g = group_id(wid);
2472
+ if (g >= P.count) { return; } // uniform: the workgroup id and a uniform
2473
+ row_hash(g, lid.x);
2474
+ }
2475
+ `
2476
+ );
1934
2477
  const histogramWgsl = (
1935
2478
  /* wgsl */
1936
2479
  `
@@ -1965,6 +2508,51 @@ fn indirect_finalize(@builtin(local_invocation_id) lid: vec3<u32>) {
1965
2508
  }
1966
2509
  `
1967
2510
  );
2511
+ const lpaStepWgsl = (
2512
+ /* wgsl */
2513
+ `
2514
+ var<workgroup> moved: atomic<u32>;
2515
+
2516
+ @compute @workgroup_size(WG)
2517
+ fn lpa_step(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
2518
+ if (lid.x == 0u) { atomicStore(&moved, 0u); }
2519
+ workgroupBarrier();
2520
+ let v = linear_id(wid, lid.x);
2521
+ if (v < P.n) {
2522
+ let cur = labelsIn[v];
2523
+ let best = bestKey[v];
2524
+ var next = cur;
2525
+ if (best != INVALID_INDEX && best != cur) {
2526
+ let down = best < cur;
2527
+ if (down == (P.direction == 0u)) { next = best; } // the alternating direction rule
2528
+ }
2529
+ labelsOut[v] = next;
2530
+ if (next != cur) { atomicAdd(&moved, 1u); }
2531
+ }
2532
+ workgroupBarrier(); // every lane, unconditionally
2533
+ if (lid.x == 0u) {
2534
+ let m = atomicLoad(&moved);
2535
+ if (m > 0u) { atomicAdd(&counters[P.counterIndex], m); } // one atomic per workgroup
2536
+ }
2537
+ }
2538
+ `
2539
+ );
2540
+ const orientFlagsWgsl = (
2541
+ /* wgsl */
2542
+ `
2543
+ @compute @workgroup_size(WG)
2544
+ fn orient_flags(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
2545
+ let a = linear_id(wid, lid.x);
2546
+ if (a >= P.count) { return; } // no barrier follows
2547
+ let u = src[a];
2548
+ let v = colIdx[a];
2549
+ let du = rowPtr[u + 1u] - rowPtr[u];
2550
+ let dv = rowPtr[v + 1u] - rowPtr[v];
2551
+ let keep = dv > du || (dv == du && v > u); // (degree, id) of the target above the source's
2552
+ flags[a] = select(0u, 1u, keep);
2553
+ }
2554
+ `
2555
+ );
1968
2556
  const prFinalizeWgsl = (
1969
2557
  /* wgsl */
1970
2558
  `
@@ -2150,6 +2738,20 @@ fn reduce(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) l
2150
2738
  }
2151
2739
  `
2152
2740
  );
2741
+ const runFlagsWgsl = (
2742
+ /* wgsl */
2743
+ `
2744
+ @compute @workgroup_size(WG)
2745
+ fn run_flags(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
2746
+ let i = linear_id(wid, lid.x);
2747
+ if (i >= P.count) { return; } // no barrier follows
2748
+ let a = keysA[i];
2749
+ var first = a != INVALID_INDEX; // a dropped arc never opens a run
2750
+ if (i > 0u) { first = first && (a != keysA[i - 1u] || keysB[i] != keysB[i - 1u]); }
2751
+ flags[i] = select(0u, 1u, first);
2752
+ }
2753
+ `
2754
+ );
2153
2755
  const scanAddWgsl = (
2154
2756
  /* wgsl */
2155
2757
  `
@@ -2470,6 +3072,63 @@ fn sssp_relax(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_i
2470
3072
  }
2471
3073
  `
2472
3074
  );
3075
+ const triIntersectWgsl = (
3076
+ /* wgsl */
3077
+ `
3078
+ fn contains(lo0: u32, hi0: u32, x: u32) -> bool { // binary search of x in colIdx[lo0, hi0), sorted ascending
3079
+ var lo = lo0;
3080
+ var hi = hi0;
3081
+ loop {
3082
+ if (lo >= hi) { break; }
3083
+ let mid = lo + (hi - lo) / 2u;
3084
+ let y = colIdx[mid];
3085
+ if (y == x) { return true; }
3086
+ if (y < x) { lo = mid + 1u; } else { hi = mid; }
3087
+ }
3088
+ return false;
3089
+ }
3090
+
3091
+ @compute @workgroup_size(WG)
3092
+ fn tri_intersect(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
3093
+ let a = linear_id(wid, lid.x);
3094
+ if (a >= P.count) { return; } // no barrier follows
3095
+ let u = src[a];
3096
+ let v = colIdx[a];
3097
+ let uLo = rowPtr[u];
3098
+ let uHi = rowPtr[u + 1u];
3099
+ let vLo = rowPtr[v];
3100
+ let vHi = rowPtr[v + 1u];
3101
+ let du = uHi - uLo;
3102
+ let dv = vHi - vLo;
3103
+ var search = SEARCH == 2u;
3104
+ if (SEARCH == 0u) { search = du / TRIANGLE_BINARY_SEARCH_RATIO > dv || dv / TRIANGLE_BINARY_SEARCH_RATIO > du; }
3105
+ var found = 0u;
3106
+ if (search) {
3107
+ var sLo = uLo;
3108
+ var sHi = uHi;
3109
+ var lLo = vLo;
3110
+ var lHi = vHi;
3111
+ if (du > dv) { sLo = vLo; sHi = vHi; lLo = uLo; lHi = uHi; }
3112
+ for (var k = sLo; k < sHi; k = k + 1u) {
3113
+ let w = colIdx[k];
3114
+ if (contains(lLo, lHi, w)) { atomicAdd(&counts[w], 1u); found = found + 1u; }
3115
+ }
3116
+ } else {
3117
+ var i = uLo;
3118
+ var j = vLo;
3119
+ loop {
3120
+ if (i >= uHi || j >= vHi) { break; }
3121
+ let x = colIdx[i];
3122
+ let y = colIdx[j];
3123
+ if (x == y) { atomicAdd(&counts[x], 1u); found = found + 1u; i = i + 1u; j = j + 1u; }
3124
+ else if (x < y) { i = i + 1u; }
3125
+ else { j = j + 1u; }
3126
+ }
3127
+ }
3128
+ if (found > 0u) { atomicAdd(&counts[u], found); atomicAdd(&counts[v], found); }
3129
+ }
3130
+ `
3131
+ );
2473
3132
  const wccCompressWgsl = (
2474
3133
  /* wgsl */
2475
3134
  `
@@ -2805,7 +3464,9 @@ const FRONTIER_COUNTERS = UniformBlock.define(
2805
3464
  ["thresholdBits", "u32"],
2806
3465
  ["deltaBits", "u32"],
2807
3466
  ["path", "u32"],
2808
- ["nextDegreeSum", "u32"]
3467
+ ["nextDegreeSum", "u32"],
3468
+ ["stackTop", "u32"],
3469
+ ["sigmaOverflow", "u32"]
2809
3470
  ],
2810
3471
  { layout: "storage" }
2811
3472
  );
@@ -2828,9 +3489,19 @@ const FRONTIER_PARAMS = UniformBlock.define("FrontierParams", [
2828
3489
  ["stride", "u32"],
2829
3490
  ["firstOfSubmit", "u32"],
2830
3491
  ["iteration", "u32"],
2831
- ["pad1", "u32"],
3492
+ ["perNode", "u32"],
2832
3493
  ["pad2", "u32"]
2833
3494
  ]);
3495
+ const BC_PARAMS = UniformBlock.define("BcParams", [
3496
+ ["n", "u32"],
3497
+ ["k", "u32"],
3498
+ ["start", "u32"],
3499
+ ["count", "u32"],
3500
+ ["stride", "u32"],
3501
+ ["role", "u32"],
3502
+ ["pad0", "u32"],
3503
+ ["pad1", "u32"]
3504
+ ]);
2834
3505
  const BF_PARAMS = UniformBlock.define("BfParams", [
2835
3506
  ["edgeCount", "u32"],
2836
3507
  ["stride", "u32"],
@@ -2847,6 +3518,30 @@ const BF_FLAGS = UniformBlock.define(
2847
3518
  ],
2848
3519
  { layout: "storage" }
2849
3520
  );
3521
+ const APSP_PARAMS = UniformBlock.define("ApspParams", [
3522
+ ["n", "u32"],
3523
+ ["round", "u32"],
3524
+ ["blocks", "u32"],
3525
+ ["infBits", "u32"]
3526
+ ]);
3527
+ const COO_PARAMS = UniformBlock.define("CooParams", [
3528
+ ["count", "u32"],
3529
+ ["pad0", "u32"],
3530
+ ["pad1", "u32"],
3531
+ ["pad2", "u32"]
3532
+ ]);
3533
+ const GROUP_PARAMS = UniformBlock.define("GroupParams", [
3534
+ ["rowsBase", "u32"],
3535
+ ["basesBase", "u32"],
3536
+ ["count", "u32"],
3537
+ ["pad0", "u32"]
3538
+ ]);
3539
+ const LPA_PARAMS = UniformBlock.define("LpaParams", [
3540
+ ["n", "u32"],
3541
+ ["direction", "u32"],
3542
+ ["counterIndex", "u32"],
3543
+ ["pad0", "u32"]
3544
+ ]);
2850
3545
  function decl(group, binding, name, kind, wgslType) {
2851
3546
  return { group, binding, name, kind, wgslType };
2852
3547
  }
@@ -3616,13 +4311,283 @@ const CLOSENESS_REDUCE = {
3616
4311
  snippetSlots: [],
3617
4312
  phase: "P8"
3618
4313
  };
3619
- const REGISTRY = Object.freeze({
3620
- degree: DEGREE,
3621
- reduce: REDUCE,
3622
- fill: FILL,
3623
- "segmented-reduce": SEGMENTED_REDUCE,
3624
- "fa2-stats-finalize": FA2_STATS_FINALIZE,
3625
- "fa2-attraction": FA2_ATTRACTION,
4314
+ const BC_FINALIZE = {
4315
+ id: "bc-finalize",
4316
+ body: bcFinalizeWgsl,
4317
+ entryPoint: "bc_finalize",
4318
+ bindings: [
4319
+ decl(1, 0, "counters", "storage", "array<atomic<u32>>"),
4320
+ decl(1, 1, "ends", "storage", "array<u32>"),
4321
+ decl(1, 2, "S", "storage-ro", "array<u32>"),
4322
+ decl(1, 3, "depthK", "storage", "array<u32>"),
4323
+ decl(1, 4, "sigmaK", "storage", "array<u32>"),
4324
+ decl(2, 0, "P", "uniform", "BcParams")
4325
+ ],
4326
+ overrideDecls: [],
4327
+ uniforms: [BC_PARAMS],
4328
+ needs: [],
4329
+ snippetSlots: [],
4330
+ phase: "P9"
4331
+ };
4332
+ const BC_FORWARD = {
4333
+ id: "bc-forward",
4334
+ body: bcForwardWgsl,
4335
+ entryPoint: "bc_forward",
4336
+ bindings: [
4337
+ decl(1, 0, "rowPtr", "storage-ro", "array<u32>"),
4338
+ decl(1, 1, "colIdx", "storage-ro", "array<u32>"),
4339
+ decl(1, 2, "S", "storage", "array<u32>"),
4340
+ decl(1, 3, "ends", "storage-ro", "array<u32>"),
4341
+ decl(1, 4, "counters", "storage", "array<atomic<u32>>"),
4342
+ decl(1, 5, "depthK", "storage", "array<atomic<u32>>"),
4343
+ decl(1, 6, "sigmaK", "storage", "array<atomic<u32>>"),
4344
+ decl(2, 0, "P", "uniform", "BcParams")
4345
+ ],
4346
+ overrideDecls: [],
4347
+ uniforms: [BC_PARAMS],
4348
+ needs: [],
4349
+ snippetSlots: [],
4350
+ phase: "P9"
4351
+ };
4352
+ const BC_BACKWARD = {
4353
+ id: "bc-backward",
4354
+ body: bcBackwardWgsl,
4355
+ entryPoint: "bc_backward",
4356
+ bindings: [
4357
+ decl(1, 0, "rowPtr", "storage-ro", "array<u32>"),
4358
+ decl(1, 1, "colIdx", "storage-ro", "array<u32>"),
4359
+ decl(1, 2, "S", "storage-ro", "array<u32>"),
4360
+ decl(1, 3, "depthK", "storage-ro", "array<u32>"),
4361
+ decl(1, 4, "sigmaK", "storage-ro", "array<u32>"),
4362
+ decl(1, 5, "deltaK", "storage", "array<f32>"),
4363
+ decl(2, 0, "P", "uniform", "BcParams")
4364
+ ],
4365
+ overrideDecls: [],
4366
+ uniforms: [BC_PARAMS],
4367
+ needs: [],
4368
+ snippetSlots: [],
4369
+ phase: "P9"
4370
+ };
4371
+ const BC_GATHER = {
4372
+ id: "bc-gather",
4373
+ body: bcGatherWgsl,
4374
+ entryPoint: "bc_gather",
4375
+ bindings: [
4376
+ decl(1, 0, "deltaK", "storage-ro", "array<f32>"),
4377
+ decl(1, 1, "bc", "storage", "array<f32>"),
4378
+ decl(2, 0, "P", "uniform", "BcParams")
4379
+ ],
4380
+ overrideDecls: [],
4381
+ uniforms: [BC_PARAMS],
4382
+ needs: [],
4383
+ snippetSlots: [],
4384
+ phase: "P9"
4385
+ };
4386
+ const BC_EDGE_GATHER = {
4387
+ id: "bc-edge-gather",
4388
+ body: bcEdgeGatherWgsl,
4389
+ entryPoint: "bc_edge_gather",
4390
+ bindings: [
4391
+ decl(1, 0, "rowPtr", "storage-ro", "array<u32>"),
4392
+ decl(1, 1, "colIdx", "storage-ro", "array<u32>"),
4393
+ decl(1, 2, "depthK", "storage-ro", "array<u32>"),
4394
+ decl(1, 3, "sigmaK", "storage-ro", "array<u32>"),
4395
+ decl(1, 4, "deltaK", "storage-ro", "array<f32>"),
4396
+ decl(1, 5, "arcScores", "storage", "array<f32>"),
4397
+ decl(2, 0, "P", "uniform", "BcParams")
4398
+ ],
4399
+ overrideDecls: [],
4400
+ uniforms: [BC_PARAMS],
4401
+ needs: [],
4402
+ snippetSlots: [],
4403
+ phase: "P9"
4404
+ };
4405
+ const BC_FORWARD_EDGE = {
4406
+ id: "bc-forward-edge",
4407
+ body: bcForwardEdgeWgsl,
4408
+ entryPoint: "bc_forward_edge",
4409
+ bindings: [
4410
+ decl(1, 0, "edgeSrc", "storage-ro", "array<u32>"),
4411
+ decl(1, 1, "edgeDst", "storage-ro", "array<u32>"),
4412
+ decl(1, 2, "S", "storage", "array<u32>"),
4413
+ decl(1, 3, "ends", "storage-ro", "array<u32>"),
4414
+ decl(1, 4, "counters", "storage", "array<atomic<u32>>"),
4415
+ decl(1, 5, "depthK", "storage", "array<atomic<u32>>"),
4416
+ decl(1, 6, "sigmaK", "storage", "array<atomic<u32>>"),
4417
+ decl(2, 0, "P", "uniform", "BcParams")
4418
+ ],
4419
+ overrideDecls: [{ name: "UNDIRECTED", type: "bool", default: false }],
4420
+ uniforms: [BC_PARAMS],
4421
+ needs: [],
4422
+ snippetSlots: [],
4423
+ phase: "P9"
4424
+ };
4425
+ const APSP_INIT = {
4426
+ id: "apsp-init",
4427
+ body: apspInitWgsl,
4428
+ entryPoint: "apsp_init",
4429
+ bindings: GRAPH_SLOTS.concat(decl(1, 0, "dist", "storage", "array<f32>"), decl(2, 0, "P", "uniform", "ApspParams")),
4430
+ overrideDecls: [],
4431
+ uniforms: [APSP_PARAMS],
4432
+ needs: [],
4433
+ snippetSlots: [],
4434
+ phase: "P9"
4435
+ };
4436
+ const APSP_FW = {
4437
+ id: "apsp-fw",
4438
+ body: apspFwWgsl,
4439
+ entryPoint: "apsp_fw",
4440
+ bindings: [decl(1, 0, "dist", "storage", "array<f32>"), decl(2, 0, "P", "uniform", "ApspParams")],
4441
+ overrideDecls: [{ name: "PHASE", type: "u32", default: 0 }],
4442
+ uniforms: [APSP_PARAMS],
4443
+ needs: [],
4444
+ snippetSlots: [],
4445
+ phase: "P9"
4446
+ };
4447
+ const COO_EMIT = {
4448
+ id: "coo-emit",
4449
+ body: cooEmitWgsl,
4450
+ entryPoint: "coo_emit",
4451
+ bindings: [
4452
+ decl(1, 0, "edgeSrc", "storage-ro", "array<u32>"),
4453
+ decl(1, 1, "edgeDst", "storage-ro", "array<u32>"),
4454
+ decl(1, 2, "edgeWeight", "storage-ro", "array<f32>"),
4455
+ decl(1, 3, "order", "storage-ro", "array<u32>"),
4456
+ decl(1, 4, "outSrc", "storage", "array<u32>"),
4457
+ decl(1, 5, "outDst", "storage", "array<u32>"),
4458
+ decl(1, 6, "outWeight", "storage", "array<f32>"),
4459
+ decl(2, 0, "P", "uniform", "CooParams")
4460
+ ],
4461
+ overrideDecls: [
4462
+ { name: "INDEXED", type: "bool", default: false },
4463
+ { name: "WEIGHTED", type: "bool", default: false }
4464
+ ],
4465
+ uniforms: [COO_PARAMS],
4466
+ needs: [],
4467
+ snippetSlots: [],
4468
+ phase: "P11"
4469
+ };
4470
+ const RUN_FLAGS = {
4471
+ id: "run-flags",
4472
+ body: runFlagsWgsl,
4473
+ entryPoint: "run_flags",
4474
+ bindings: [
4475
+ decl(1, 0, "keysA", "storage-ro", "array<u32>"),
4476
+ decl(1, 1, "keysB", "storage-ro", "array<u32>"),
4477
+ decl(1, 2, "flags", "storage", "array<u32>"),
4478
+ decl(2, 0, "P", "uniform", "CooParams")
4479
+ ],
4480
+ overrideDecls: [],
4481
+ uniforms: [COO_PARAMS],
4482
+ needs: [],
4483
+ snippetSlots: [],
4484
+ phase: "P11"
4485
+ };
4486
+ const COO_SCATTER = {
4487
+ id: "coo-scatter",
4488
+ body: cooScatterWgsl,
4489
+ entryPoint: "coo_scatter",
4490
+ bindings: [
4491
+ decl(1, 0, "src", "storage-ro", "array<u32>"),
4492
+ decl(1, 1, "dst", "storage-ro", "array<u32>"),
4493
+ decl(1, 2, "weight", "storage-ro", "array<f32>"),
4494
+ decl(1, 3, "rowPtr", "storage-ro", "array<u32>"),
4495
+ decl(1, 4, "cursors", "storage", "array<atomic<u32>>"),
4496
+ decl(1, 5, "colIdx", "storage", "array<u32>"),
4497
+ decl(1, 6, "outWeight", "storage", "array<f32>"),
4498
+ decl(2, 0, "P", "uniform", "CooParams")
4499
+ ],
4500
+ overrideDecls: [
4501
+ { name: "SORTED_INPUT", type: "bool", default: false },
4502
+ { name: "WEIGHTED", type: "bool", default: false }
4503
+ ],
4504
+ uniforms: [COO_PARAMS],
4505
+ needs: [],
4506
+ snippetSlots: [],
4507
+ phase: "P11"
4508
+ };
4509
+ const ORIENT_FLAGS = {
4510
+ id: "orient-flags",
4511
+ body: orientFlagsWgsl,
4512
+ entryPoint: "orient_flags",
4513
+ bindings: [
4514
+ decl(1, 0, "rowPtr", "storage-ro", "array<u32>"),
4515
+ decl(1, 1, "colIdx", "storage-ro", "array<u32>"),
4516
+ decl(1, 2, "src", "storage-ro", "array<u32>"),
4517
+ decl(1, 3, "flags", "storage", "array<u32>"),
4518
+ decl(2, 0, "P", "uniform", "CooParams")
4519
+ ],
4520
+ overrideDecls: [],
4521
+ uniforms: [COO_PARAMS],
4522
+ needs: [],
4523
+ snippetSlots: [],
4524
+ phase: "P11"
4525
+ };
4526
+ const TRI_INTERSECT = {
4527
+ id: "tri-intersect",
4528
+ body: triIntersectWgsl,
4529
+ entryPoint: "tri_intersect",
4530
+ bindings: [
4531
+ decl(1, 0, "rowPtr", "storage-ro", "array<u32>"),
4532
+ decl(1, 1, "colIdx", "storage-ro", "array<u32>"),
4533
+ decl(1, 2, "src", "storage-ro", "array<u32>"),
4534
+ decl(1, 3, "counts", "storage", "array<atomic<u32>>"),
4535
+ decl(2, 0, "P", "uniform", "CooParams")
4536
+ ],
4537
+ overrideDecls: [{ name: "SEARCH", type: "u32", default: 0 }],
4538
+ uniforms: [COO_PARAMS],
4539
+ needs: [],
4540
+ snippetSlots: [],
4541
+ phase: "P11"
4542
+ };
4543
+ const GROUP_BY_KEY_ROW = {
4544
+ id: "group-by-key-row",
4545
+ body: groupByKeyRowWgsl,
4546
+ entryPoint: "group_by_key_row",
4547
+ bindings: [
4548
+ decl(1, 0, "rowPtr", "storage-ro", "array<u32>"),
4549
+ decl(1, 1, "colIdx", "storage-ro", "array<u32>"),
4550
+ decl(1, 2, "weights", "storage-ro", "array<f32>"),
4551
+ decl(1, 3, "keyIn", "storage-ro", "array<u32>"),
4552
+ decl(1, 4, "rows", "storage-ro", "array<u32>"),
4553
+ decl(1, 5, "hashRegion", "storage", "array<atomic<u32>>"),
4554
+ decl(1, 6, "bestKey", "storage", "array<u32>"),
4555
+ decl(1, 7, "bestScore", "storage", "array<f32>"),
4556
+ decl(2, 0, "P", "uniform", "GroupParams")
4557
+ ],
4558
+ overrideDecls: [
4559
+ { name: "TIER", type: "u32", default: 0 },
4560
+ { name: "WEIGHTED", type: "bool", default: false }
4561
+ ],
4562
+ uniforms: [GROUP_PARAMS],
4563
+ needs: [],
4564
+ snippetSlots: [],
4565
+ phase: "P11"
4566
+ };
4567
+ const LPA_STEP = {
4568
+ id: "lpa-step",
4569
+ body: lpaStepWgsl,
4570
+ entryPoint: "lpa_step",
4571
+ bindings: [
4572
+ decl(1, 0, "labelsIn", "storage-ro", "array<u32>"),
4573
+ decl(1, 1, "bestKey", "storage-ro", "array<u32>"),
4574
+ decl(1, 2, "labelsOut", "storage", "array<u32>"),
4575
+ decl(1, 3, "counters", "storage", "array<atomic<u32>>"),
4576
+ decl(2, 0, "P", "uniform", "LpaParams")
4577
+ ],
4578
+ overrideDecls: [],
4579
+ uniforms: [LPA_PARAMS],
4580
+ needs: [],
4581
+ snippetSlots: [],
4582
+ phase: "P11"
4583
+ };
4584
+ const REGISTRY = Object.freeze({
4585
+ degree: DEGREE,
4586
+ reduce: REDUCE,
4587
+ fill: FILL,
4588
+ "segmented-reduce": SEGMENTED_REDUCE,
4589
+ "fa2-stats-finalize": FA2_STATS_FINALIZE,
4590
+ "fa2-attraction": FA2_ATTRACTION,
3626
4591
  "fa2-repulsion-exact": FA2_REPULSION_EXACT,
3627
4592
  "fa2-speed-finalize": FA2_SPEED_FINALIZE,
3628
4593
  "fa2-integrate": FA2_INTEGRATE,
@@ -3662,7 +4627,22 @@ const REGISTRY = Object.freeze({
3662
4627
  "sssp-relax": SSSP_RELAX,
3663
4628
  "bf-relax": BF_RELAX,
3664
4629
  "closeness-sweep": CLOSENESS_SWEEP,
3665
- "closeness-reduce": CLOSENESS_REDUCE
4630
+ "closeness-reduce": CLOSENESS_REDUCE,
4631
+ "bc-finalize": BC_FINALIZE,
4632
+ "bc-forward": BC_FORWARD,
4633
+ "bc-backward": BC_BACKWARD,
4634
+ "bc-gather": BC_GATHER,
4635
+ "bc-edge-gather": BC_EDGE_GATHER,
4636
+ "bc-forward-edge": BC_FORWARD_EDGE,
4637
+ "apsp-init": APSP_INIT,
4638
+ "apsp-fw": APSP_FW,
4639
+ "coo-emit": COO_EMIT,
4640
+ "run-flags": RUN_FLAGS,
4641
+ "coo-scatter": COO_SCATTER,
4642
+ "orient-flags": ORIENT_FLAGS,
4643
+ "tri-intersect": TRI_INTERSECT,
4644
+ "group-by-key-row": GROUP_BY_KEY_ROW,
4645
+ "lpa-step": LPA_STEP
3666
4646
  });
3667
4647
  const bodyOverrides = /* @__PURE__ */ new Map();
3668
4648
  function entryOf(id) {
@@ -3821,7 +4801,7 @@ class ScanPlannerImpl {
3821
4801
  }
3822
4802
  const POISON = 3735928559;
3823
4803
  const CHECK_BLOCKS = 32;
3824
- const RING_SLOTS$6 = 8;
4804
+ const RING_SLOTS$9 = 8;
3825
4805
  const checked = /* @__PURE__ */ new WeakMap();
3826
4806
  function inputAt(i) {
3827
4807
  return i + 1;
@@ -3849,7 +4829,7 @@ async function runCheck(ctx) {
3849
4829
  const count = CHECK_BLOCKS * wg + 1;
3850
4830
  const bytes = 4 * count;
3851
4831
  const lease = ctx.pool.lease();
3852
- const ring = new UniformRing(ctx.device, ctx.allocator, RING_SLOTS$6, "device-check/ring");
4832
+ const ring = new UniformRing(ctx.device, ctx.allocator, RING_SLOTS$9, "device-check/ring");
3853
4833
  try {
3854
4834
  const scope = {
3855
4835
  device: ctx.device,
@@ -4066,7 +5046,7 @@ function coreOfView(v, arcCount) {
4066
5046
  hasWeights: weights !== null
4067
5047
  });
4068
5048
  }
4069
- function checkDest$5(dest, n) {
5049
+ function checkDest$7(dest, n) {
4070
5050
  if (dest === void 0) {
4071
5051
  return null;
4072
5052
  }
@@ -4095,7 +5075,7 @@ async function degree(ctx, s, options) {
4095
5075
  ctx.assertReady();
4096
5076
  await assertDeviceComputes(ctx);
4097
5077
  const n = s.nodeCount;
4098
- const dest = checkDest$5(options?.dest, n);
5078
+ const dest = checkDest$7(options?.dest, n);
4099
5079
  if (options?.signal?.aborted) {
4100
5080
  throw new WebGpuGraphError("E_ABORTED", "degree: the signal was aborted before any work started", {});
4101
5081
  }
@@ -4516,13 +5496,13 @@ function algorithmScope(ctx, label, slots) {
4516
5496
  ringOverruns: () => ring.overruns
4517
5497
  };
4518
5498
  }
4519
- const ALGORITHM$4 = "connectedComponents";
5499
+ const ALGORITHM$8 = "connectedComponents";
4520
5500
  const ROUNDS_PER_BATCH$1 = 4;
4521
5501
  const MAX_WCC_ROUNDS = 64;
4522
5502
  const SAMPLE_SIZE = 1024;
4523
5503
  const MAX_STEPS = 1024;
4524
- const RING_SLOTS$5 = 2 * ROUNDS_PER_BATCH$1;
4525
- function checkDest$4(dest, n) {
5504
+ const RING_SLOTS$8 = 2 * ROUNDS_PER_BATCH$1;
5505
+ function checkDest$6(dest, n) {
4526
5506
  if (dest === void 0) {
4527
5507
  return null;
4528
5508
  }
@@ -4531,7 +5511,7 @@ function checkDest$4(dest, n) {
4531
5511
  }
4532
5512
  throw new WebGpuGraphError(
4533
5513
  "E_INVALID_ARGUMENT",
4534
- `${ALGORITHM$4}: dest must be a Uint32Array of length ${n} over an ArrayBuffer`,
5514
+ `${ALGORITHM$8}: dest must be a Uint32Array of length ${n} over an ArrayBuffer`,
4535
5515
  {
4536
5516
  argument: "dest",
4537
5517
  value: `${dest.constructor.name}(${dest.length})`,
@@ -4541,7 +5521,7 @@ function checkDest$4(dest, n) {
4541
5521
  }
4542
5522
  function coreOf$2(ctx, s) {
4543
5523
  const core = ctx.residency.core(s);
4544
- assertWholeCore(core, s.arcCount, ctx.caps.limits.maxStorageBufferBindingSize, ALGORITHM$4);
5524
+ assertWholeCore(core, s.arcCount, ctx.caps.limits.maxStorageBufferBindingSize, ALGORITHM$8);
4545
5525
  return core;
4546
5526
  }
4547
5527
  function bindingOf$3(buffer, size) {
@@ -4600,8 +5580,8 @@ function checkLabels(raw) {
4600
5580
  for (let v = 0; v < n; v++) {
4601
5581
  const label = raw[v];
4602
5582
  if (label >= n) {
4603
- throw new WebGpuGraphError("E_VALIDATION", `${ALGORITHM$4}: labels[${v}] = ${label} is not a node index`, {
4604
- label: `${ALGORITHM$4}/labels`,
5583
+ throw new WebGpuGraphError("E_VALIDATION", `${ALGORITHM$8}: labels[${v}] = ${label} is not a node index`, {
5584
+ label: `${ALGORITHM$8}/labels`,
4605
5585
  message: `the device produced a label outside [0, ${n})`
4606
5586
  });
4607
5587
  }
@@ -4617,9 +5597,9 @@ async function connectedComponents(ctx, s, options) {
4617
5597
  await assertDeviceComputes(ctx);
4618
5598
  const n = s.nodeCount;
4619
5599
  const renumber = options?.renumber !== false;
4620
- const dest = checkDest$4(options?.dest, n);
5600
+ const dest = checkDest$6(options?.dest, n);
4621
5601
  if (options?.signal?.aborted) {
4622
- throw new WebGpuGraphError("E_ABORTED", `${ALGORITHM$4}: the signal was aborted before any work started`, {});
5602
+ throw new WebGpuGraphError("E_ABORTED", `${ALGORITHM$8}: the signal was aborted before any work started`, {});
4623
5603
  }
4624
5604
  if (n === 0) {
4625
5605
  options?.onProgress?.(1, 1);
@@ -4636,7 +5616,7 @@ async function connectedComponents(ctx, s, options) {
4636
5616
  }
4637
5617
  const edges = ctx.residency.view(s, "edgeList");
4638
5618
  const edgeCount = edges.scalars.edgeCount[0];
4639
- const scope = algorithmScope(ctx, ALGORITHM$4, RING_SLOTS$5);
5619
+ const scope = algorithmScope(ctx, ALGORITHM$8, RING_SLOTS$8);
4640
5620
  try {
4641
5621
  const compBytes = 4 * (n + 1);
4642
5622
  const comp = scope.scratch(compBytes, "comp");
@@ -4660,12 +5640,12 @@ async function connectedComponents(ctx, s, options) {
4660
5640
  const params = wccParams({ items: n, stride: rowPlan.stride ?? n, r: 0, giant: U32_MAX$2 });
4661
5641
  compress.dispatch(pass2, compress.bind({ comp: compBinding, P: params.binding }), rowPlan, [params.offset]);
4662
5642
  };
4663
- const submit = (batch) => {
5643
+ const submit2 = (batch) => {
4664
5644
  scope.flush();
4665
5645
  return batch.submit();
4666
5646
  };
4667
5647
  queue.writeBuffer(comp, 4 * flagIndex, zero);
4668
- const setup = new CommandBatch(ctx, `${ALGORITHM$4}/setup`);
5648
+ const setup = new CommandBatch(ctx, `${ALGORITHM$8}/setup`);
4669
5649
  let pass = setup.pass("sample-rounds");
4670
5650
  const fillParams = scope.params(FILL_PARAMS, { count: n, value: 0, mode: 1, pad0: 0 });
4671
5651
  fill.dispatch(
@@ -4682,9 +5662,9 @@ async function connectedComponents(ctx, s, options) {
4682
5662
  }
4683
5663
  recordCompress(pass);
4684
5664
  setup.endPass();
4685
- await submit(setup).readback;
5665
+ await submit2(setup).readback;
4686
5666
  ctx.assertReady();
4687
- const sampler = new CommandBatch(ctx, `${ALGORITHM$4}/sample`);
5667
+ const sampler = new CommandBatch(ctx, `${ALGORITHM$8}/sample`);
4688
5668
  pass = sampler.pass("sample");
4689
5669
  const sampleParams = wccParams({ items, stride: 0, r: 0, giant: U32_MAX$2 });
4690
5670
  sample.dispatch(
@@ -4695,14 +5675,14 @@ async function connectedComponents(ctx, s, options) {
4695
5675
  );
4696
5676
  sampler.endPass();
4697
5677
  const histRequest = sampler.readback(hist, 0, 4 * items);
4698
- const histBytes = await submit(sampler).readback;
5678
+ const histBytes = await submit2(sampler).readback;
4699
5679
  ctx.assertReady();
4700
5680
  const giant = modeOf(new Uint32Array(histBytes, histRequest.offset, items));
4701
5681
  const edgeBindings = { edgeSrc: edges.bindings.src, edgeDst: edges.bindings.dst, comp: compBinding };
4702
5682
  let rounds = 0;
4703
5683
  for (; ; ) {
4704
5684
  queue.writeBuffer(comp, 4 * flagIndex, zero);
4705
- const batch = new CommandBatch(ctx, `${ALGORITHM$4}/rounds`);
5685
+ const batch = new CommandBatch(ctx, `${ALGORITHM$8}/rounds`);
4706
5686
  pass = batch.pass("edge-rounds");
4707
5687
  for (let i = 0; i < ROUNDS_PER_BATCH$1; i++) {
4708
5688
  const params = wccParams({ items: edgeCount, stride: edgePlan.stride ?? edgeCount, r: 0, giant });
@@ -4712,12 +5692,12 @@ async function connectedComponents(ctx, s, options) {
4712
5692
  }
4713
5693
  batch.endPass();
4714
5694
  const flagRequest = batch.readback(comp, 4 * flagIndex, 4);
4715
- const submitted = submit(batch);
5695
+ const submitted = submit2(batch);
4716
5696
  const back = await submitted.readback;
4717
5697
  rounds += ROUNDS_PER_BATCH$1;
4718
5698
  ctx.assertReady();
4719
5699
  if (options?.signal?.aborted) {
4720
- throw new WebGpuGraphError("E_ABORTED", `${ALGORITHM$4}: the signal was aborted`, {
5700
+ throw new WebGpuGraphError("E_ABORTED", `${ALGORITHM$8}: the signal was aborted`, {
4721
5701
  batchId: submitted.id
4722
5702
  });
4723
5703
  }
@@ -4727,15 +5707,15 @@ async function connectedComponents(ctx, s, options) {
4727
5707
  if (rounds >= MAX_WCC_ROUNDS) {
4728
5708
  throw new WebGpuGraphError(
4729
5709
  "E_VALIDATION",
4730
- `${ALGORITHM$4}: the changed flag never settled in ${MAX_WCC_ROUNDS} rounds`,
4731
- { label: ALGORITHM$4, message: `the changed flag never settled in ${MAX_WCC_ROUNDS} rounds` }
5710
+ `${ALGORITHM$8}: the changed flag never settled in ${MAX_WCC_ROUNDS} rounds`,
5711
+ { label: ALGORITHM$8, message: `the changed flag never settled in ${MAX_WCC_ROUNDS} rounds` }
4732
5712
  );
4733
5713
  }
4734
5714
  }
4735
- const final = new CommandBatch(ctx, `${ALGORITHM$4}/final`);
5715
+ const final = new CommandBatch(ctx, `${ALGORITHM$8}/final`);
4736
5716
  recordCompress(final.pass("compress"));
4737
5717
  final.endPass();
4738
- await submit(final).readback;
5718
+ await submit2(final).readback;
4739
5719
  ctx.assertReady();
4740
5720
  const raw = !renumber && dest !== null ? dest : new Uint32Array(n);
4741
5721
  await ctx.readback.read(comp, 4 * n, raw);
@@ -4974,8 +5954,20 @@ class TieredPlanner {
4974
5954
  async function prepareSegmentedReduce(scope, core, options) {
4975
5955
  const op = opCode(options.op);
4976
5956
  validateValueSnippet(options.valueSnippet);
4977
- const perm = null;
4978
- const ranges = [{ tier: 0, start: 0, end: rowCountOf(core, "segmentedReduce") }];
5957
+ const { tiers } = options;
5958
+ const perm = tiers?.perm ?? null;
5959
+ if (tiers !== null && scope.workgroupSize < MID_TIER_LANES) {
5960
+ throw new WebGpuGraphError(
5961
+ "E_UNSUPPORTED",
5962
+ `segmentedReduce: the mid tier folds ${MID_TIER_LANES} lanes per row, more than the workgroup size ${scope.workgroupSize}`,
5963
+ { feature: "segmentedReduce.tiers" }
5964
+ );
5965
+ }
5966
+ const ranges = tiers === null ? [{ tier: 0, start: 0, end: rowCountOf(core, "segmentedReduce") }] : [
5967
+ { tier: 2, start: 0, end: tiers.segmentOffsets[1] },
5968
+ { tier: 1, start: tiers.segmentOffsets[1], end: tiers.segmentOffsets[2] },
5969
+ { tier: 0, start: tiers.segmentOffsets[2], end: tiers.segmentOffsets[4] }
5970
+ ];
4979
5971
  const compiled = [];
4980
5972
  for (const range of ranges) {
4981
5973
  if (range.tier !== 0 && range.end <= range.start) {
@@ -5098,8 +6090,8 @@ async function prepareSpmvPull(scope, rev, options) {
5098
6090
  return new SpmvPullPlannerImpl(scope, compiled, perm, options.weights);
5099
6091
  }
5100
6092
  const PR_BATCH = 8;
5101
- const RING_SLOTS$4 = 2 * PR_BATCH + 2;
5102
- function checkDest$3(dest, n, algorithm) {
6093
+ const RING_SLOTS$7 = 2 * PR_BATCH + 2;
6094
+ function checkDest$5(dest, n, algorithm) {
5103
6095
  if (dest === void 0) {
5104
6096
  return null;
5105
6097
  }
@@ -5146,7 +6138,7 @@ async function run(ctx, s, personalization, options, algorithm) {
5146
6138
  expected: "a positive integer"
5147
6139
  });
5148
6140
  }
5149
- const dest = checkDest$3(options?.dest, n, algorithm);
6141
+ const dest = checkDest$5(options?.dest, n, algorithm);
5150
6142
  if (options?.signal?.aborted) {
5151
6143
  throw aborted$3(algorithm);
5152
6144
  }
@@ -5176,7 +6168,7 @@ async function run(ctx, s, personalization, options, algorithm) {
5176
6168
  const weights = useWeights ? void 0 : null;
5177
6169
  const weightedCore = useWeights ? core : { ...core, weights: null, hasWeights: false };
5178
6170
  const weightedRev = useWeights ? rev : { ...rev, weights: null, hasWeights: false };
5179
- const scope = algorithmScope(ctx, algorithm, RING_SLOTS$4);
6171
+ const scope = algorithmScope(ctx, algorithm, RING_SLOTS$7);
5180
6172
  let uploaded = null;
5181
6173
  try {
5182
6174
  const bytes = 4 * n;
@@ -5193,7 +6185,7 @@ async function run(ctx, s, personalization, options, algorithm) {
5193
6185
  uploaded = ctx.residency.array(personalization, `${algorithm}/personalization`);
5194
6186
  }
5195
6187
  await ctx.allocator.check();
5196
- const normaliser = await prepareSegmentedReduce(scope, weightedCore, {
6188
+ const normaliser2 = await prepareSegmentedReduce(scope, weightedCore, {
5197
6189
  op: "sum",
5198
6190
  valueSnippet: "v = weight;",
5199
6191
  tiers: null
@@ -5226,7 +6218,7 @@ async function run(ctx, s, personalization, options, algorithm) {
5226
6218
  const batch = new CommandBatch(ctx, algorithm);
5227
6219
  let pass = batch.pass("iterations");
5228
6220
  if (iterationsRun === 0) {
5229
- normaliser.record(pass, weightedCore, outWeightSumBinding);
6221
+ normaliser2.record(pass, weightedCore, outWeightSumBinding);
5230
6222
  }
5231
6223
  const last = iterationsRun + k === maxIterations;
5232
6224
  for (let i = 0; i < k + (last ? 1 : 0); i++) {
@@ -5339,8 +6331,8 @@ async function personalizedPageRank(ctx, s, personalization, options) {
5339
6331
  return run(ctx, s, normalised2, options, "personalizedPageRank");
5340
6332
  }
5341
6333
  const BATCH = 8;
5342
- const RING_SLOTS$3 = 4 * BATCH + 8;
5343
- function checkDest$2(dest, n, algorithm) {
6334
+ const RING_SLOTS$6 = 4 * BATCH + 8;
6335
+ function checkDest$4(dest, n, algorithm) {
5344
6336
  if (dest === void 0) {
5345
6337
  return null;
5346
6338
  }
@@ -5378,7 +6370,7 @@ function whole(buffer, size) {
5378
6370
  }
5379
6371
  async function runPowerIteration(ctx, n, config) {
5380
6372
  await assertDeviceComputes(ctx);
5381
- const scope = algorithmScope(ctx, config.label, RING_SLOTS$3);
6373
+ const scope = algorithmScope(ctx, config.label, RING_SLOTS$6);
5382
6374
  try {
5383
6375
  const bytes = 4 * n;
5384
6376
  const ring = (config.alternate === null ? ["rankA", "rankB"] : ["rankA", "rankB", "rankC"]).map(
@@ -5490,7 +6482,7 @@ function resolve(ctx, s, options, algorithm) {
5490
6482
  expected: "a positive integer"
5491
6483
  });
5492
6484
  }
5493
- const dest = checkDest$2(options?.dest, n, algorithm);
6485
+ const dest = checkDest$4(options?.dest, n, algorithm);
5494
6486
  if (options?.signal?.aborted) {
5495
6487
  throw aborted$2(algorithm);
5496
6488
  }
@@ -5646,7 +6638,7 @@ function checkCount(what, count) {
5646
6638
  });
5647
6639
  }
5648
6640
  }
5649
- function checkWords$1(what, name, binding, words) {
6641
+ function checkWords$3(what, name, binding, words) {
5650
6642
  if (binding.size < 4 * words) {
5651
6643
  throw new WebGpuGraphError("E_INVALID_ARGUMENT", `${what}: ${name} is smaller than 4 x ${words} bytes`, {
5652
6644
  argument: name,
@@ -5666,15 +6658,15 @@ function checkWordIndex(what, name, index, block) {
5666
6658
  }
5667
6659
  function checkCompactArguments(r) {
5668
6660
  checkCount("compact", r.count);
5669
- checkWords$1("compact", "queue", r.queue, r.count);
5670
- checkWords$1("compact", "flags", r.flags, r.count);
5671
- checkWords$1("compact", "out", r.out, r.count);
6661
+ checkWords$3("compact", "queue", r.queue, r.count);
6662
+ checkWords$3("compact", "flags", r.flags, r.count);
6663
+ checkWords$3("compact", "out", r.out, r.count);
5672
6664
  checkWordIndex("compact", "outIndex", r.outIndex, r.outCount);
5673
6665
  }
5674
6666
  function checkDedupeArguments(r) {
5675
6667
  checkCount("dedupe", r.count);
5676
- checkWords$1("dedupe", "queue", r.queue, r.count);
5677
- checkWords$1("dedupe", "out", r.out, Math.min(r.count, Math.floor(r.owner.size / 4)));
6668
+ checkWords$3("dedupe", "queue", r.queue, r.count);
6669
+ checkWords$3("dedupe", "out", r.out, Math.min(r.count, Math.floor(r.owner.size / 4)));
5678
6670
  checkWordIndex("dedupe", "outIndex", r.outIndex, r.outCount);
5679
6671
  if (r.countIndex === U32_MAX$2) {
5680
6672
  return;
@@ -5822,7 +6814,9 @@ const W = Object.freeze({
5822
6814
  thresholdBits: 22,
5823
6815
  deltaBits: 23,
5824
6816
  path: 24,
5825
- nextDegreeSum: 25
6817
+ nextDegreeSum: 25,
6818
+ stackTop: 26,
6819
+ sigmaOverflow: 27
5826
6820
  });
5827
6821
  function definedWords(words) {
5828
6822
  const out = {};
@@ -6156,7 +7150,7 @@ class RadixSortPlannerImpl {
6156
7150
  return src;
6157
7151
  }
6158
7152
  }
6159
- const ALGORITHM$3 = "breadthFirstSearch";
7153
+ const ALGORITHM$7 = "breadthFirstSearch";
6160
7154
  const NEXT_DEGREE_MAX_GROUPS = 128;
6161
7155
  function bfsRingSlots(windows, levelsPerSubmit) {
6162
7156
  return Math.max((5 + 4 * windows) * levelsPerSubmit + 16, RESULT_BATCH_SLOTS + windows);
@@ -6168,7 +7162,7 @@ function normaliseMaxDepth(maxDepth) {
6168
7162
  }
6169
7163
  return Math.max(0, Math.ceil(maxDepth));
6170
7164
  }
6171
- function checkDest$1(dest, n) {
7165
+ function checkDest$3(dest, n) {
6172
7166
  if (dest === void 0) {
6173
7167
  return null;
6174
7168
  }
@@ -6177,7 +7171,7 @@ function checkDest$1(dest, n) {
6177
7171
  }
6178
7172
  throw new WebGpuGraphError(
6179
7173
  "E_INVALID_ARGUMENT",
6180
- `${ALGORITHM$3}: dest must be a Uint32Array of length ${n} over an ArrayBuffer`,
7174
+ `${ALGORITHM$7}: dest must be a Uint32Array of length ${n} over an ArrayBuffer`,
6181
7175
  {
6182
7176
  argument: "dest",
6183
7177
  value: `${dest.constructor.name}(${dest.length})`,
@@ -6192,8 +7186,8 @@ function degreeView(ctx, s, name) {
6192
7186
  const { bindings } = ctx.residency.view(s, name);
6193
7187
  const { [name]: binding } = bindings;
6194
7188
  if (binding === void 0) {
6195
- throw new WebGpuGraphError("E_VALIDATION", `${ALGORITHM$3}: the ${name} view has no ${name} binding`, {
6196
- label: `${ALGORITHM$3}/${name}`,
7189
+ throw new WebGpuGraphError("E_VALIDATION", `${ALGORITHM$7}: the ${name} view has no ${name} binding`, {
7190
+ label: `${ALGORITHM$7}/${name}`,
6197
7191
  message: `the ${name} view has no ${name} binding`
6198
7192
  });
6199
7193
  }
@@ -6202,15 +7196,15 @@ function degreeView(ctx, s, name) {
6202
7196
  function aborted$1(batchId) {
6203
7197
  return new WebGpuGraphError(
6204
7198
  "E_ABORTED",
6205
- `${ALGORITHM$3}: the signal was aborted`,
7199
+ `${ALGORITHM$7}: the signal was aborted`,
6206
7200
  batchId === void 0 ? {} : { batchId }
6207
7201
  );
6208
7202
  }
6209
7203
  function wordOf$1(block, name) {
6210
7204
  const value = block[name];
6211
7205
  if (typeof value !== "number") {
6212
- throw new WebGpuGraphError("E_VALIDATION", `${ALGORITHM$3}: counters.${name} did not decode to a number`, {
6213
- label: `${ALGORITHM$3}/counters`,
7206
+ throw new WebGpuGraphError("E_VALIDATION", `${ALGORITHM$7}: counters.${name} did not decode to a number`, {
7207
+ label: `${ALGORITHM$7}/counters`,
6214
7208
  message: `the field ${name} did not decode to a number`
6215
7209
  });
6216
7210
  }
@@ -6221,7 +7215,7 @@ async function bfsWithTuning(ctx, s, source, options, tuning) {
6221
7215
  await assertDeviceComputes(ctx);
6222
7216
  const n = s.nodeCount;
6223
7217
  if (!Number.isInteger(source) || source < 0 || source >= n) {
6224
- throw new WebGpuGraphError("E_INVALID_ARGUMENT", `${ALGORITHM$3}: source ${source} is outside [0, ${n})`, {
7218
+ throw new WebGpuGraphError("E_INVALID_ARGUMENT", `${ALGORITHM$7}: source ${source} is outside [0, ${n})`, {
6225
7219
  argument: "source",
6226
7220
  value: source,
6227
7221
  expected: `an integer in [0, ${n})`
@@ -6231,7 +7225,7 @@ async function bfsWithTuning(ctx, s, source, options, tuning) {
6231
7225
  if (!Number.isInteger(levelsPerSubmit) || levelsPerSubmit < 1 || levelsPerSubmit > MAX_LEVELS_PER_SUBMIT) {
6232
7226
  throw new WebGpuGraphError(
6233
7227
  "E_INVALID_ARGUMENT",
6234
- `${ALGORITHM$3}: levelsPerSubmit must be an integer in [1, ${MAX_LEVELS_PER_SUBMIT}]`,
7228
+ `${ALGORITHM$7}: levelsPerSubmit must be an integer in [1, ${MAX_LEVELS_PER_SUBMIT}]`,
6235
7229
  {
6236
7230
  argument: "levelsPerSubmit",
6237
7231
  value: levelsPerSubmit,
@@ -6239,7 +7233,7 @@ async function bfsWithTuning(ctx, s, source, options, tuning) {
6239
7233
  }
6240
7234
  );
6241
7235
  }
6242
- const dest = checkDest$1(options?.dest, n);
7236
+ const dest = checkDest$3(options?.dest, n);
6243
7237
  const maxDepth = normaliseMaxDepth(options?.maxDepth);
6244
7238
  const predKind = tuning.predKind ?? 1;
6245
7239
  if (options?.signal?.aborted) {
@@ -6250,12 +7244,12 @@ async function bfsWithTuning(ctx, s, source, options, tuning) {
6250
7244
  if (s.directed && 4 * s.arcCount > ctx.caps.limits.maxStorageBufferBindingSize) {
6251
7245
  throw new WebGpuGraphError(
6252
7246
  "E_TOO_LARGE",
6253
- `${ALGORITHM$3}: the reverse adjacency of a directed snapshot (${4 * s.arcCount} bytes) needs arc windows, which no view executes (spec 4.3); the bottom-up sweep binds it whole`,
7247
+ `${ALGORITHM$7}: the reverse adjacency of a directed snapshot (${4 * s.arcCount} bytes) needs arc windows, which no view executes (spec 4.3); the bottom-up sweep binds it whole`,
6254
7248
  {
6255
7249
  needed: 4 * s.arcCount,
6256
7250
  limit: ctx.caps.limits.maxStorageBufferBindingSize,
6257
7251
  path: "windowed",
6258
- algorithm: ALGORITHM$3
7252
+ algorithm: ALGORITHM$7
6259
7253
  }
6260
7254
  );
6261
7255
  }
@@ -6263,7 +7257,7 @@ async function bfsWithTuning(ctx, s, source, options, tuning) {
6263
7257
  const backward = coreWindows(reverse);
6264
7258
  const outDegree = degreeView(ctx, s, "outDegree");
6265
7259
  const inDegree = degreeView(ctx, s, "inDegree");
6266
- const scope = algorithmScope(ctx, ALGORITHM$3, bfsRingSlots(forward.length, levelsPerSubmit));
7260
+ const scope = algorithmScope(ctx, ALGORITHM$7, bfsRingSlots(forward.length, levelsPerSubmit));
6267
7261
  tuning.onScope?.(scope);
6268
7262
  try {
6269
7263
  const bytes = 4 * n;
@@ -6305,7 +7299,7 @@ async function bfsWithTuning(ctx, s, source, options, tuning) {
6305
7299
  const degreePlan = planGridStride(n, wg, ctx.caps, NEXT_DEGREE_MAX_GROUPS);
6306
7300
  const fusedPlan = planGridStride(n * wg, wg, ctx.caps);
6307
7301
  const bitsPlan = plan1d(bitsWords, wg, ctx.caps);
6308
- const recordFill = (pass2, dst, value, mode) => {
7302
+ const recordFill2 = (pass2, dst, value, mode) => {
6309
7303
  const params = scope.params(FILL_PARAMS, { count: n, value, mode, pad0: 0 });
6310
7304
  fill.dispatch(pass2, fill.bind({ dst, P: params.binding }), fillPlan, [params.offset]);
6311
7305
  };
@@ -6323,16 +7317,16 @@ async function bfsWithTuning(ctx, s, source, options, tuning) {
6323
7317
  outIndex: 0
6324
7318
  });
6325
7319
  };
6326
- const submit = (batch) => {
7320
+ const submit2 = (batch) => {
6327
7321
  scope.flush();
6328
7322
  return batch.submit();
6329
7323
  };
6330
- const setup = new CommandBatch(ctx, `${ALGORITHM$3}/setup`);
7324
+ const setup = new CommandBatch(ctx, `${ALGORITHM$7}/setup`);
6331
7325
  const setupPass = setup.pass("fill");
6332
- recordFill(setupPass, depth, INVALID_INDEX, 0);
6333
- recordFill(setupPass, iota, 0, 1);
7326
+ recordFill2(setupPass, depth, INVALID_INDEX, 0);
7327
+ recordFill2(setupPass, iota, 0, 1);
6334
7328
  setup.endPass();
6335
- await submit(setup).readback;
7329
+ await submit2(setup).readback;
6336
7330
  ctx.assertReady();
6337
7331
  queue.writeBuffer(depth.buffer, depth.offset + 4 * source, Uint32Array.of(0));
6338
7332
  frontier.reset(queue, source, { nextFrontierCount: 1, level: U32_MAX$2 });
@@ -6347,7 +7341,7 @@ async function bfsWithTuning(ctx, s, source, options, tuning) {
6347
7341
  let submits = 0;
6348
7342
  for (; ; ) {
6349
7343
  queue.writeBuffer(counters.buffer, counters.offset + 4 * W.unvisitedCount, new Uint32Array(3));
6350
- const batch = new CommandBatch(ctx, `${ALGORITHM$3}/levels`);
7344
+ const batch = new CommandBatch(ctx, `${ALGORITHM$7}/levels`);
6351
7345
  const pass2 = batch.pass("bfs");
6352
7346
  recordRebuild(pass2);
6353
7347
  const bitsParams = scope.params(FILL_PARAMS, { count: bitsWords, value: 0, mode: 0, pad0: 0 });
@@ -6436,7 +7430,7 @@ async function bfsWithTuning(ctx, s, source, options, tuning) {
6436
7430
  frontier: batch.readback(frontier.input.buffer, frontier.input.offset, frontier.input.size),
6437
7431
  count: batch.readback(compactCount.buffer, compactCount.offset, 4)
6438
7432
  };
6439
- const submitted = submit(batch);
7433
+ const submitted = submit2(batch);
6440
7434
  const back2 = await submitted.readback;
6441
7435
  levelsRecorded += levelsPerSubmit;
6442
7436
  submits += 1;
@@ -6457,8 +7451,8 @@ async function bfsWithTuning(ctx, s, source, options, tuning) {
6457
7451
  if (submits > n + 1) {
6458
7452
  throw new WebGpuGraphError(
6459
7453
  "E_VALIDATION",
6460
- `${ALGORITHM$3}: the done flag never rose in ${submits} submits (a traversal has at most ${n} levels)`,
6461
- { label: ALGORITHM$3, message: `the done flag never rose in ${submits} submits` }
7454
+ `${ALGORITHM$7}: the done flag never rose in ${submits} submits (a traversal has at most ${n} levels)`,
7455
+ { label: ALGORITHM$7, message: `the done flag never rose in ${submits} submits` }
6462
7456
  );
6463
7457
  }
6464
7458
  }
@@ -6472,12 +7466,12 @@ async function bfsWithTuning(ctx, s, source, options, tuning) {
6472
7466
  offsets: bindingOf$1(scope.scratch(histBytes, "order/offsets"), histBytes)
6473
7467
  };
6474
7468
  const parent = bindingOf$1(scope.scratch(bytes, "parent"), bytes);
6475
- const result = new CommandBatch(ctx, `${ALGORITHM$3}/result`);
7469
+ const result = new CommandBatch(ctx, `${ALGORITHM$7}/result`);
6476
7470
  result.copy(depth, keys, bytes);
6477
7471
  const pass = result.pass("result");
6478
- recordFill(pass, vals, 0, 1);
7472
+ recordFill2(pass, vals, 0, 1);
6479
7473
  const sorted = sort.record(pass, keys, vals, n, 32, scratch);
6480
- recordFill(pass, parent, INVALID_INDEX, 0);
7474
+ recordFill2(pass, parent, INVALID_INDEX, 0);
6481
7475
  const predPlan = planGridStride(n, wg, ctx.caps);
6482
7476
  for (const w of forward) {
6483
7477
  const predParams = scope.params(FRONTIER_PARAMS, {
@@ -6502,16 +7496,16 @@ async function bfsWithTuning(ctx, s, source, options, tuning) {
6502
7496
  const parentRequest = result.readback(parent.buffer, parent.offset, bytes);
6503
7497
  const orderRequest = result.readback(sorted.vals.buffer, sorted.vals.offset, bytes);
6504
7498
  const blockRequest = result.readback(counters.buffer, counters.offset, FRONTIER_COUNTERS.byteLength);
6505
- const back = await submit(result).readback;
7499
+ const back = await submit2(result).readback;
6506
7500
  ctx.assertReady();
6507
7501
  const block = FRONTIER_COUNTERS.read(new DataView(back), blockRequest.offset);
6508
7502
  const visitedCount = wordOf$1(block, "visitedCount");
6509
7503
  if (visitedCount > n) {
6510
7504
  throw new WebGpuGraphError(
6511
7505
  "E_VALIDATION",
6512
- `${ALGORITHM$3}: visitedCount ${visitedCount} exceeds the ${n} vertices (a duplicate claim)`,
7506
+ `${ALGORITHM$7}: visitedCount ${visitedCount} exceeds the ${n} vertices (a duplicate claim)`,
6513
7507
  {
6514
- label: `${ALGORITHM$3}/visitedCount`,
7508
+ label: `${ALGORITHM$7}/visitedCount`,
6515
7509
  message: `the device counted ${visitedCount} visits of ${n} vertices`
6516
7510
  }
6517
7511
  );
@@ -6535,9 +7529,9 @@ async function bfsWithTuning(ctx, s, source, options, tuning) {
6535
7529
  function breadthFirstSearch(ctx, s, source, options) {
6536
7530
  return bfsWithTuning(ctx, s, source, options, {});
6537
7531
  }
6538
- const ALGORITHM$2 = "sssp";
7532
+ const ALGORITHM$6 = "sssp";
6539
7533
  const HALF_ALIGN = 64;
6540
- const RING_SLOTS$2 = 4 * MAX_LEVELS_PER_SUBMIT + 40;
7534
+ const RING_SLOTS$5 = 4 * MAX_LEVELS_PER_SUBMIT + 40;
6541
7535
  function bitsOf(value) {
6542
7536
  return new Uint32Array(Float32Array.of(value).buffer)[0];
6543
7537
  }
@@ -6563,14 +7557,14 @@ function assertSource(algorithm, source, n) {
6563
7557
  function wordOf(block, name) {
6564
7558
  const value = block[name];
6565
7559
  if (typeof value !== "number") {
6566
- throw new WebGpuGraphError("E_VALIDATION", `${ALGORITHM$2}: counters.${name} did not decode to a number`, {
6567
- label: `${ALGORITHM$2}/counters`,
7560
+ throw new WebGpuGraphError("E_VALIDATION", `${ALGORITHM$6}: counters.${name} did not decode to a number`, {
7561
+ label: `${ALGORITHM$6}/counters`,
6568
7562
  message: `the field ${name} did not decode to a number`
6569
7563
  });
6570
7564
  }
6571
7565
  return value;
6572
7566
  }
6573
- function checkDest(algorithm, dest, n) {
7567
+ function checkDest$2(algorithm, dest, n) {
6574
7568
  if (dest === void 0) {
6575
7569
  return null;
6576
7570
  }
@@ -6664,7 +7658,7 @@ function predBufferWords(n) {
6664
7658
  return 2 * Math.ceil(n / 64) * 64 + 64;
6665
7659
  }
6666
7660
  async function predecessorPass(input) {
6667
- const { algorithm, ctx, scope, predKernel, recordFill, graph, dist, pred, n, arcCount, source, mode } = input;
7661
+ const { algorithm, ctx, scope, predKernel, recordFill: recordFill2, graph, dist, pred, n, arcCount, source, mode } = input;
6668
7662
  const wg = ctx.workgroupSize;
6669
7663
  const { queue } = ctx.device;
6670
7664
  const bytes = 4 * n;
@@ -6699,7 +7693,7 @@ async function predecessorPass(input) {
6699
7693
  for (let iteration = 0; iteration < MAX_LEVELS_PER_SUBMIT; iteration++) {
6700
7694
  recordRole(1, iteration);
6701
7695
  }
6702
- recordFill(pass, predArcs, n, INVALID_INDEX);
7696
+ recordFill2(pass, predArcs, n, INVALID_INDEX);
6703
7697
  recordRole(2, 0);
6704
7698
  batch.endPass();
6705
7699
  const distRequest = batch.readback(dist.buffer, dist.offset, bytes);
@@ -6731,12 +7725,12 @@ async function ssspWithTuning(ctx, s, source, options, tuning) {
6731
7725
  ctx.assertReady();
6732
7726
  await assertDeviceComputes(ctx);
6733
7727
  const n = s.nodeCount;
6734
- assertSource(ALGORITHM$2, source, n);
7728
+ assertSource(ALGORITHM$6, source, n);
6735
7729
  const roundsPerSubmit = tuning.roundsPerSubmit ?? MAX_LEVELS_PER_SUBMIT;
6736
7730
  if (!Number.isInteger(roundsPerSubmit) || roundsPerSubmit < 1 || roundsPerSubmit > MAX_LEVELS_PER_SUBMIT) {
6737
7731
  throw new WebGpuGraphError(
6738
7732
  "E_INVALID_ARGUMENT",
6739
- `${ALGORITHM$2}: roundsPerSubmit must be an integer in [1, ${MAX_LEVELS_PER_SUBMIT}]`,
7733
+ `${ALGORITHM$6}: roundsPerSubmit must be an integer in [1, ${MAX_LEVELS_PER_SUBMIT}]`,
6740
7734
  {
6741
7735
  argument: "roundsPerSubmit",
6742
7736
  value: roundsPerSubmit,
@@ -6745,42 +7739,42 @@ async function ssspWithTuning(ctx, s, source, options, tuning) {
6745
7739
  );
6746
7740
  }
6747
7741
  if (tuning.delta !== void 0 && !(Number.isFinite(tuning.delta) && tuning.delta > 0)) {
6748
- throw new WebGpuGraphError("E_INVALID_ARGUMENT", `${ALGORITHM$2}: delta must be a finite positive number`, {
7742
+ throw new WebGpuGraphError("E_INVALID_ARGUMENT", `${ALGORITHM$6}: delta must be a finite positive number`, {
6749
7743
  argument: "delta",
6750
7744
  value: tuning.delta,
6751
7745
  expected: "a finite positive number"
6752
7746
  });
6753
7747
  }
6754
- const dest = checkDest(ALGORITHM$2, options?.dest, n);
6755
- const vector2 = resolveWeights$1(ALGORITHM$2, s, options?.weights);
6756
- const cutoff = normaliseCutoff(ALGORITHM$2, options?.cutoff);
7748
+ const dest = checkDest$2(ALGORITHM$6, options?.dest, n);
7749
+ const vector2 = resolveWeights$1(ALGORITHM$6, s, options?.weights);
7750
+ const cutoff = normaliseCutoff(ALGORITHM$6, options?.cutoff);
6757
7751
  if (options?.signal?.aborted) {
6758
- throw aborted(ALGORITHM$2);
7752
+ throw aborted(ALGORITHM$6);
6759
7753
  }
6760
7754
  if (vector2 === null || vector2.allOne) {
6761
7755
  return unitWeightRoute(ctx, s, source, cutoff, dest, options);
6762
7756
  }
6763
7757
  if (!vector2.nonNegative) {
6764
- throw new WebGpuGraphError("E_UNSUPPORTED", `${ALGORITHM$2}: a negative weight has no shortest path here`, {
7758
+ throw new WebGpuGraphError("E_UNSUPPORTED", `${ALGORITHM$6}: a negative weight has no shortest path here`, {
6765
7759
  feature: "sssp.negativeWeights",
6766
7760
  hint: "use bellmanFord"
6767
7761
  });
6768
7762
  }
6769
7763
  if (!vector2.finite) {
6770
- throw new WebGpuGraphError("E_UNSUPPORTED", `${ALGORITHM$2}: a NaN or infinite weight has no bit-pattern order`, {
7764
+ throw new WebGpuGraphError("E_UNSUPPORTED", `${ALGORITHM$6}: a NaN or infinite weight has no bit-pattern order`, {
6771
7765
  feature: "sssp.nonFiniteWeights"
6772
7766
  });
6773
7767
  }
6774
7768
  const { arcCount } = s;
6775
7769
  const core = ctx.residency.core(s);
6776
7770
  const limit = ctx.caps.limits.maxStorageBufferBindingSize;
6777
- assertWholeCore(core, arcCount, limit, ALGORITHM$2);
7771
+ assertWholeCore(core, arcCount, limit, ALGORITHM$6);
6778
7772
  const cap = Math.ceil(Math.max(1, arcCount) / HALF_ALIGN) * HALF_ALIGN;
6779
7773
  if (8 * cap > limit) {
6780
7774
  throw new WebGpuGraphError(
6781
7775
  "E_TOO_LARGE",
6782
- `${ALGORITHM$2}: the near-far queue of ${cap} entries per half needs ${8 * cap} bytes, above the ${limit}-byte binding limit (the relax is never windowed)`,
6783
- { needed: 8 * cap, limit, path: "sssp.queue", algorithm: ALGORITHM$2 }
7776
+ `${ALGORITHM$6}: the near-far queue of ${cap} entries per half needs ${8 * cap} bytes, above the ${limit}-byte binding limit (the relax is never windowed)`,
7777
+ { needed: 8 * cap, limit, path: "sssp.queue", algorithm: ALGORITHM$6 }
6784
7778
  );
6785
7779
  }
6786
7780
  const delta = Math.fround(
@@ -6789,7 +7783,7 @@ async function ssspWithTuning(ctx, s, source, options, tuning) {
6789
7783
  const deltaBits = bitsOf(delta);
6790
7784
  const maxRounds = n + Math.ceil(vector2.sum / delta) + 1;
6791
7785
  const maxSubmits = Math.ceil((maxRounds + 1) / roundsPerSubmit) + 1;
6792
- const scope = algorithmScope(ctx, ALGORITHM$2, RING_SLOTS$2);
7786
+ const scope = algorithmScope(ctx, ALGORITHM$6, RING_SLOTS$5);
6793
7787
  try {
6794
7788
  const wg = ctx.workgroupSize;
6795
7789
  const bytes = 4 * n;
@@ -6819,20 +7813,20 @@ async function ssspWithTuning(ctx, s, source, options, tuning) {
6819
7813
  const { counters } = frontier;
6820
7814
  const nearIn = frontier.vertices[0];
6821
7815
  const farIn = frontier.vertices[1];
6822
- const recordFill = (pass, dst, count, value) => {
7816
+ const recordFill2 = (pass, dst, count, value) => {
6823
7817
  const params = scope.params(FILL_PARAMS, { count, value, mode: 0, pad0: 0 });
6824
7818
  fill.dispatch(pass, fill.bind({ dst, P: params.binding }), plan1d(count, wg, ctx.caps), [params.offset]);
6825
7819
  };
6826
- const submit = (batch) => {
7820
+ const submit2 = (batch) => {
6827
7821
  scope.flush();
6828
7822
  return batch.submit();
6829
7823
  };
6830
- const setup = new CommandBatch(ctx, `${ALGORITHM$2}/setup`);
7824
+ const setup = new CommandBatch(ctx, `${ALGORITHM$6}/setup`);
6831
7825
  const setupPass = setup.pass("fill");
6832
- recordFill(setupPass, dist, n, F32_INF_BITS);
6833
- recordFill(setupPass, pred, predWords, INVALID_INDEX);
7826
+ recordFill2(setupPass, dist, n, F32_INF_BITS);
7827
+ recordFill2(setupPass, pred, predWords, INVALID_INDEX);
6834
7828
  setup.endPass();
6835
- await submit(setup).readback;
7829
+ await submit2(setup).readback;
6836
7830
  ctx.assertReady();
6837
7831
  queue.writeBuffer(dist.buffer, dist.offset + 4 * source, Uint32Array.of(0));
6838
7832
  frontier.reset(queue, source, { nextFrontierCount: 1, thresholdBits: deltaBits, deltaBits });
@@ -6873,7 +7867,7 @@ async function ssspWithTuning(ctx, s, source, options, tuning) {
6873
7867
  let roundsRecorded = 0;
6874
7868
  let submits = 0;
6875
7869
  for (; ; ) {
6876
- const batch = new CommandBatch(ctx, `${ALGORITHM$2}/rounds`);
7870
+ const batch = new CommandBatch(ctx, `${ALGORITHM$6}/rounds`);
6877
7871
  const pass = batch.pass("sssp");
6878
7872
  const near = scope.params(FRONTIER_PARAMS, { ...relaxFields, role: 0 });
6879
7873
  const far = scope.params(FRONTIER_PARAMS, { ...relaxFields, role: 1 });
@@ -6890,13 +7884,13 @@ async function ssspWithTuning(ctx, s, source, options, tuning) {
6890
7884
  batch.endPass();
6891
7885
  const doneRequest = batch.readback(counters.buffer, counters.offset + 4 * W.done, 4);
6892
7886
  const inspect = tuning.onRound === void 0 ? null : batch.readback(counters.buffer, counters.offset, FRONTIER_COUNTERS.byteLength);
6893
- const submitted = submit(batch);
7887
+ const submitted = submit2(batch);
6894
7888
  const back = await submitted.readback;
6895
7889
  roundsRecorded += roundsPerSubmit;
6896
7890
  submits += 1;
6897
7891
  ctx.assertReady();
6898
7892
  if (options?.signal?.aborted) {
6899
- throw aborted(ALGORITHM$2, submitted.id);
7893
+ throw aborted(ALGORITHM$6, submitted.id);
6900
7894
  }
6901
7895
  options?.onProgress?.(Math.min(roundsRecorded, maxRounds), maxRounds);
6902
7896
  if (inspect !== null && tuning.onRound !== void 0) {
@@ -6921,30 +7915,30 @@ async function ssspWithTuning(ctx, s, source, options, tuning) {
6921
7915
  const needed = Math.max(wordOf(block, "nextFrontierCount"), wordOf(block, "nextFarCount"));
6922
7916
  throw new WebGpuGraphError(
6923
7917
  "E_TOO_LARGE",
6924
- `${ALGORITHM$2}: a raw pile of ${needed} entries overflowed its ${cap}-entry half`,
6925
- { needed, limit: cap, path: "sssp.pile", algorithm: ALGORITHM$2 }
7918
+ `${ALGORITHM$6}: a raw pile of ${needed} entries overflowed its ${cap}-entry half`,
7919
+ { needed, limit: cap, path: "sssp.pile", algorithm: ALGORITHM$6 }
6926
7920
  );
6927
7921
  }
6928
7922
  throw new WebGpuGraphError(
6929
7923
  "E_UNSUPPORTED",
6930
- `${ALGORITHM$2}: the f32 threshold ${wordOf(block, "thresholdBits")} absorbed the delta ${deltaBits} (as bit patterns); the far pile can no longer be bucketed`,
7924
+ `${ALGORITHM$6}: the f32 threshold ${wordOf(block, "thresholdBits")} absorbed the delta ${deltaBits} (as bit patterns); the far pile can no longer be bucketed`,
6931
7925
  { feature: "sssp.thresholdAbsorbed", hint: "the distances outgrew the delta's f32 precision" }
6932
7926
  );
6933
7927
  }
6934
7928
  if (submits > maxSubmits) {
6935
7929
  throw new WebGpuGraphError(
6936
7930
  "E_VALIDATION",
6937
- `${ALGORITHM$2}: the done flag never rose in ${submits} submits (at most ${maxRounds} rounds)`,
6938
- { label: `${ALGORITHM$2}/rounds`, message: `the done flag never rose in ${submits} submits` }
7931
+ `${ALGORITHM$6}: the done flag never rose in ${submits} submits (at most ${maxRounds} rounds)`,
7932
+ { label: `${ALGORITHM$6}/rounds`, message: `the done flag never rose in ${submits} submits` }
6939
7933
  );
6940
7934
  }
6941
7935
  }
6942
7936
  const passed = await predecessorPass({
6943
- algorithm: ALGORITHM$2,
7937
+ algorithm: ALGORITHM$6,
6944
7938
  ctx,
6945
7939
  scope,
6946
7940
  predKernel,
6947
- recordFill,
7941
+ recordFill: recordFill2,
6948
7942
  graph,
6949
7943
  dist,
6950
7944
  pred,
@@ -6956,8 +7950,8 @@ async function ssspWithTuning(ctx, s, source, options, tuning) {
6956
7950
  if (passed.orphans !== 0) {
6957
7951
  throw new WebGpuGraphError(
6958
7952
  "E_VALIDATION",
6959
- `${ALGORITHM$2}: ${passed.orphans} reached node(s) the predecessor key never reached (a kernel bug)`,
6960
- { label: `${ALGORITHM$2}/pred`, message: `${passed.orphans} orphan(s) in the predecessor pass` }
7953
+ `${ALGORITHM$6}: ${passed.orphans} reached node(s) the predecessor key never reached (a kernel bug)`,
7954
+ { label: `${ALGORITHM$6}/pred`, message: `${passed.orphans} orphan(s) in the predecessor pass` }
6961
7955
  );
6962
7956
  }
6963
7957
  const distOut = dest ?? new Float32Array(n);
@@ -6976,10 +7970,10 @@ async function ssspWithTuning(ctx, s, source, options, tuning) {
6976
7970
  function sssp(ctx, s, source, options) {
6977
7971
  return ssspWithTuning(ctx, s, source, options, {});
6978
7972
  }
6979
- const ALGORITHM$1 = "bellmanFord";
7973
+ const ALGORITHM$5 = "bellmanFord";
6980
7974
  const ROUNDS_PER_BATCH = 8;
6981
7975
  const MAX_RETRIES = 16;
6982
- const RING_SLOTS$1 = MAX_LEVELS_PER_SUBMIT + 16;
7976
+ const RING_SLOTS$4 = MAX_LEVELS_PER_SUBMIT + 16;
6983
7977
  function assertSymmetric(s, vector2) {
6984
7978
  const { arcToEdge, edgeToArc } = s;
6985
7979
  for (let a = 0; a < s.arcCount; a++) {
@@ -6987,7 +7981,7 @@ function assertSymmetric(s, vector2) {
6987
7981
  if (vector2[a] !== vector2[forward]) {
6988
7982
  throw new WebGpuGraphError(
6989
7983
  "E_UNSUPPORTED",
6990
- `${ALGORITHM$1}: weights[${a}] = ${vector2[a]} differs from weights[${forward}] = ${vector2[forward]}, the forward arc of the same undirected edge; the kernel reads one weight per edge`,
7984
+ `${ALGORITHM$5}: weights[${a}] = ${vector2[a]} differs from weights[${forward}] = ${vector2[forward]}, the forward arc of the same undirected edge; the kernel reads one weight per edge`,
6991
7985
  { feature: "bellmanFord.asymmetricUndirectedWeights", hint: "use a directed snapshot" }
6992
7986
  );
6993
7987
  }
@@ -6997,10 +7991,10 @@ async function bellmanFordWithTuning(ctx, s, source, options, tuning) {
6997
7991
  ctx.assertReady();
6998
7992
  await assertDeviceComputes(ctx);
6999
7993
  const n = s.nodeCount;
7000
- assertSource(ALGORITHM$1, source, n);
7994
+ assertSource(ALGORITHM$5, source, n);
7001
7995
  const maxRetries = tuning.maxRetries ?? MAX_RETRIES;
7002
7996
  if (!Number.isInteger(maxRetries) || maxRetries < 1) {
7003
- throw new WebGpuGraphError("E_INVALID_ARGUMENT", `${ALGORITHM$1}: maxRetries must be an integer >= 1`, {
7997
+ throw new WebGpuGraphError("E_INVALID_ARGUMENT", `${ALGORITHM$5}: maxRetries must be an integer >= 1`, {
7004
7998
  argument: "maxRetries",
7005
7999
  value: maxRetries,
7006
8000
  expected: "an integer >= 1"
@@ -7010,7 +8004,7 @@ async function bellmanFordWithTuning(ctx, s, source, options, tuning) {
7010
8004
  if (!Number.isInteger(roundsPerBatch) || roundsPerBatch < 1 || roundsPerBatch > MAX_LEVELS_PER_SUBMIT) {
7011
8005
  throw new WebGpuGraphError(
7012
8006
  "E_INVALID_ARGUMENT",
7013
- `${ALGORITHM$1}: roundsPerBatch must be an integer in [1, ${MAX_LEVELS_PER_SUBMIT}]`,
8007
+ `${ALGORITHM$5}: roundsPerBatch must be an integer in [1, ${MAX_LEVELS_PER_SUBMIT}]`,
7014
8008
  {
7015
8009
  argument: "roundsPerBatch",
7016
8010
  value: roundsPerBatch,
@@ -7018,18 +8012,18 @@ async function bellmanFordWithTuning(ctx, s, source, options, tuning) {
7018
8012
  }
7019
8013
  );
7020
8014
  }
7021
- const dest = checkDest(ALGORITHM$1, options?.dest, n);
7022
- const vector2 = resolveWeights$1(ALGORITHM$1, s, options?.weights);
7023
- const cutoff = normaliseCutoff(ALGORITHM$1, options?.cutoff);
8015
+ const dest = checkDest$2(ALGORITHM$5, options?.dest, n);
8016
+ const vector2 = resolveWeights$1(ALGORITHM$5, s, options?.weights);
8017
+ const cutoff = normaliseCutoff(ALGORITHM$5, options?.cutoff);
7024
8018
  if (options?.signal?.aborted) {
7025
- throw aborted(ALGORITHM$1);
8019
+ throw aborted(ALGORITHM$5);
7026
8020
  }
7027
8021
  if (vector2 === null || vector2.allOne) {
7028
8022
  const unit = await unitWeightRoute(ctx, s, source, cutoff, dest, options);
7029
8023
  return { result: { ...unit, hasNegativeCycle: false }, rounds: 0, retryExhaustedRounds: 0 };
7030
8024
  }
7031
8025
  if (!vector2.finite) {
7032
- throw new WebGpuGraphError("E_UNSUPPORTED", `${ALGORITHM$1}: a NaN or infinite weight has no shortest path`, {
8026
+ throw new WebGpuGraphError("E_UNSUPPORTED", `${ALGORITHM$5}: a NaN or infinite weight has no shortest path`, {
7033
8027
  feature: "bellmanFord.nonFiniteWeights"
7034
8028
  });
7035
8029
  }
@@ -7038,10 +8032,10 @@ async function bellmanFordWithTuning(ctx, s, source, options, tuning) {
7038
8032
  }
7039
8033
  const { arcCount } = s;
7040
8034
  const core = ctx.residency.core(s, ["rowPtr", "colIdx", "weights", "edgeToArc"]);
7041
- assertWholeCore(core, arcCount, ctx.caps.limits.maxStorageBufferBindingSize, ALGORITHM$1);
8035
+ assertWholeCore(core, arcCount, ctx.caps.limits.maxStorageBufferBindingSize, ALGORITHM$5);
7042
8036
  const edges = ctx.residency.view(s, "edgeList");
7043
8037
  const edgeCount = edges.scalars.edgeCount[0];
7044
- const scope = algorithmScope(ctx, ALGORITHM$1, RING_SLOTS$1);
8038
+ const scope = algorithmScope(ctx, ALGORITHM$5, RING_SLOTS$4);
7045
8039
  try {
7046
8040
  const wg = ctx.workgroupSize;
7047
8041
  const bytes = 4 * n;
@@ -7068,23 +8062,23 @@ async function bellmanFordWithTuning(ctx, s, source, options, tuning) {
7068
8062
  const predKernel = await ctx.pipelines.kernel(kernelSpec("sssp-pred", { ...overrides, MODE: 0 }));
7069
8063
  const fill = await ctx.pipelines.kernel(kernelSpec("fill"));
7070
8064
  const graph = graphBindings(core, null, weightsBinding);
7071
- const recordFill = (pass, dst, count, value, mode = 0) => {
8065
+ const recordFill2 = (pass, dst, count, value, mode = 0) => {
7072
8066
  const params = scope.params(FILL_PARAMS, { count, value, mode, pad0: 0 });
7073
8067
  fill.dispatch(pass, fill.bind({ dst, P: params.binding }), plan1d(count, wg, ctx.caps), [params.offset]);
7074
8068
  };
7075
- const submit = (batch) => {
8069
+ const submit2 = (batch) => {
7076
8070
  scope.flush();
7077
8071
  return batch.submit();
7078
8072
  };
7079
- const setup = new CommandBatch(ctx, `${ALGORITHM$1}/setup`);
8073
+ const setup = new CommandBatch(ctx, `${ALGORITHM$5}/setup`);
7080
8074
  const setupPass = setup.pass("fill");
7081
- recordFill(setupPass, dist, n, F32_INF_BITS);
7082
- recordFill(setupPass, pred, predWords, INVALID_INDEX);
8075
+ recordFill2(setupPass, dist, n, F32_INF_BITS);
8076
+ recordFill2(setupPass, pred, predWords, INVALID_INDEX);
7083
8077
  if (iota !== null) {
7084
- recordFill(setupPass, iota, edgeCount, 0, 1);
8078
+ recordFill2(setupPass, iota, edgeCount, 0, 1);
7085
8079
  }
7086
8080
  setup.endPass();
7087
- await submit(setup).readback;
8081
+ await submit2(setup).readback;
7088
8082
  ctx.assertReady();
7089
8083
  queue.writeBuffer(dist.buffer, dist.offset + 4 * source, Uint32Array.of(0));
7090
8084
  const edgePlan = planGridStride(edgeCount, wg, ctx.caps);
@@ -7105,7 +8099,7 @@ async function bellmanFordWithTuning(ctx, s, source, options, tuning) {
7105
8099
  const zero = new Uint32Array(BF_FLAGS.byteLength / 4);
7106
8100
  const runRounds = async (count, label) => {
7107
8101
  queue.writeBuffer(flags.buffer, flags.offset, zero);
7108
- const batch = new CommandBatch(ctx, `${ALGORITHM$1}/${label}`);
8102
+ const batch = new CommandBatch(ctx, `${ALGORITHM$5}/${label}`);
7109
8103
  const pass = batch.pass("relax");
7110
8104
  const params = scope.params(BF_PARAMS, relaxFields);
7111
8105
  const bound = relax.bind({ ...relaxBindings, P: params.binding });
@@ -7114,7 +8108,7 @@ async function bellmanFordWithTuning(ctx, s, source, options, tuning) {
7114
8108
  }
7115
8109
  batch.endPass();
7116
8110
  const request = batch.readback(flags.buffer, flags.offset, BF_FLAGS.byteLength);
7117
- const submitted = submit(batch);
8111
+ const submitted = submit2(batch);
7118
8112
  const back = await submitted.readback;
7119
8113
  ctx.assertReady();
7120
8114
  const block = BF_FLAGS.read(new DataView(back), request.offset);
@@ -7130,9 +8124,9 @@ async function bellmanFordWithTuning(ctx, s, source, options, tuning) {
7130
8124
  if (decision.retryExhausted !== 0) {
7131
8125
  throw new WebGpuGraphError(
7132
8126
  "E_VALIDATION",
7133
- `${ALGORITHM$1}: a lane exhausted the ${maxRetries}-retry compare-exchange bound in the decision round, so its change is not a verdict`,
8127
+ `${ALGORITHM$5}: a lane exhausted the ${maxRetries}-retry compare-exchange bound in the decision round, so its change is not a verdict`,
7134
8128
  {
7135
- label: `${ALGORITHM$1}/retry`,
8129
+ label: `${ALGORITHM$5}/retry`,
7136
8130
  message: "retryExhausted in the decision round",
7137
8131
  batchId: decision.id
7138
8132
  }
@@ -7148,7 +8142,7 @@ async function bellmanFordWithTuning(ctx, s, source, options, tuning) {
7148
8142
  retryExhaustedRounds += 1;
7149
8143
  }
7150
8144
  if (options?.signal?.aborted) {
7151
- throw aborted(ALGORITHM$1, batch.id);
8145
+ throw aborted(ALGORITHM$5, batch.id);
7152
8146
  }
7153
8147
  options?.onProgress?.(rounds, n);
7154
8148
  if (batch.changed === 0 && batch.retryExhausted === 0) {
@@ -7156,11 +8150,11 @@ async function bellmanFordWithTuning(ctx, s, source, options, tuning) {
7156
8150
  }
7157
8151
  }
7158
8152
  const passed = await predecessorPass({
7159
- algorithm: ALGORITHM$1,
8153
+ algorithm: ALGORITHM$5,
7160
8154
  ctx,
7161
8155
  scope,
7162
8156
  predKernel,
7163
- recordFill,
8157
+ recordFill: recordFill2,
7164
8158
  graph,
7165
8159
  dist,
7166
8160
  pred,
@@ -7172,7 +8166,7 @@ async function bellmanFordWithTuning(ctx, s, source, options, tuning) {
7172
8166
  if (passed.orphans !== 0 && !hasNegativeCycle) {
7173
8167
  throw new WebGpuGraphError(
7174
8168
  "E_UNSUPPORTED",
7175
- `${ALGORITHM$1}: ${passed.orphans} reached node(s) the tight subgraph never reaches (a cycle of weights below one f32 ulp relaxed once)`,
8169
+ `${ALGORITHM$5}: ${passed.orphans} reached node(s) the tight subgraph never reaches (a cycle of weights below one f32 ulp relaxed once)`,
7176
8170
  {
7177
8171
  feature: "bellmanFord.roundedCycle",
7178
8172
  hint: "a cycle of weights below one f32 ulp relaxed once at a distance above 2^24; scale the weights or shorten the distances"
@@ -7199,10 +8193,385 @@ async function bellmanFordWithTuning(ctx, s, source, options, tuning) {
7199
8193
  async function bellmanFord(ctx, s, source, options) {
7200
8194
  return (await bellmanFordWithTuning(ctx, s, source, options, {})).result;
7201
8195
  }
7202
- const ALGORITHM = "closenessCentrality";
8196
+ const ALGORITHM$4 = "betweennessCentrality";
8197
+ const BYTES_PER_NODE_SOURCE = 16;
8198
+ const SAMPLE_SEED = 2654435769;
8199
+ const RING_SLOTS$3 = BC_BACKWARD_LEVELS_PER_SUBMIT + 16;
8200
+ function planBatchSize(n, remaining, limits) {
8201
+ const needed = 4 * (n + 2);
8202
+ if (needed > limits.maxStorageBufferBindingSize) {
8203
+ throw new WebGpuGraphError(
8204
+ "E_TOO_LARGE",
8205
+ `${ALGORITHM$4}: one source needs ${needed} bytes in its largest binding at n = ${n}, above maxStorageBufferBindingSize = ${limits.maxStorageBufferBindingSize}; a limit of at least ${needed} admits one source per batch`,
8206
+ { needed, limit: limits.maxStorageBufferBindingSize, path: "binding", algorithm: ALGORITHM$4 }
8207
+ );
8208
+ }
8209
+ const kByBinding = Math.floor(limits.maxStorageBufferBindingSize / (4 * n));
8210
+ const kByBudget = Math.floor(BC_BATCH_BUDGET_FRACTION * limits.maxBufferSize / (BYTES_PER_NODE_SOURCE * n));
8211
+ return Math.max(1, Math.min(kByBinding, kByBudget, BC_MAX_BATCH, remaining));
8212
+ }
8213
+ function drawSources(n, k) {
8214
+ const pool = Array.from({ length: n }, (_, i) => i);
8215
+ let state = SAMPLE_SEED;
8216
+ for (let i = 0; i < k; i++) {
8217
+ state = state + 1831565813 >>> 0;
8218
+ let t = Math.imul(state ^ state >>> 15, state | 1);
8219
+ t = t + Math.imul(t ^ t >>> 7, t | 61) ^ t;
8220
+ const unit = ((t ^ t >>> 14) >>> 0) / 2 ** 32;
8221
+ const j = i + Math.floor(unit * (n - i));
8222
+ [pool[i], pool[j]] = [pool[j], pool[i]];
8223
+ }
8224
+ return pool.slice(0, k);
8225
+ }
8226
+ function badArgument(argument, value, expected) {
8227
+ return new WebGpuGraphError("E_INVALID_ARGUMENT", `${ALGORITHM$4}: ${argument} must be ${expected}`, {
8228
+ argument,
8229
+ value,
8230
+ expected
8231
+ });
8232
+ }
8233
+ function resolveSources(options, n) {
8234
+ const k = options?.k;
8235
+ if (k !== void 0 && (!Number.isInteger(k) || k < 0 || k > n)) {
8236
+ throw badArgument("k", k, `an integer in [0, ${n}]`);
8237
+ }
8238
+ const given = options?.sources;
8239
+ if (given !== void 0) {
8240
+ for (const v of given) {
8241
+ if (!Number.isInteger(v) || v < 0 || v >= n) {
8242
+ throw badArgument("sources", v, `node indices in [0, ${n})`);
8243
+ }
8244
+ }
8245
+ if (k !== void 0 && k !== given.length) {
8246
+ throw badArgument("k", k, `absent or equal to sources.length (${given.length})`);
8247
+ }
8248
+ return [...given];
8249
+ }
8250
+ if (k !== void 0) {
8251
+ return drawSources(n, k);
8252
+ }
8253
+ return Array.from({ length: n }, (_, i) => i);
8254
+ }
8255
+ function recordFill(state, pass, dst, count, value) {
8256
+ const { scope, fill, ctx } = state;
8257
+ const params = scope.params(FILL_PARAMS, { count, value, mode: 0, pad0: 0 });
8258
+ fill.dispatch(pass, fill.bind({ dst, P: params.binding }), plan1d(count, ctx.workgroupSize, ctx.caps), [
8259
+ params.offset
8260
+ ]);
8261
+ }
8262
+ function recordBc(state, pass, kernel, resources, fields, items) {
8263
+ const { scope, ctx } = state;
8264
+ const plan = planGridStride(items, ctx.workgroupSize, ctx.caps);
8265
+ const params = scope.params(BC_PARAMS, { ...fields, stride: plan.stride ?? 0 });
8266
+ let bound = state.bound.get(kernel);
8267
+ if (bound === void 0) {
8268
+ bound = kernel.bind({ ...resources, P: params.binding });
8269
+ state.bound.set(kernel, bound);
8270
+ }
8271
+ kernel.dispatch(pass, bound, items === 0 ? plan1d(1, ctx.workgroupSize, ctx.caps) : plan, [params.offset]);
8272
+ }
8273
+ async function submit(state, batch, signal) {
8274
+ state.scope.flush();
8275
+ const submitted = batch.submit();
8276
+ const back = await submitted.readback;
8277
+ state.ctx.assertReady();
8278
+ if (signal?.aborted) {
8279
+ throw aborted(ALGORITHM$4, submitted.id);
8280
+ }
8281
+ return back;
8282
+ }
8283
+ async function runBatch(state, sources, form, levelsPerSubmit, tuning, signal) {
8284
+ const { ctx, n, S, ends, depthK, sigmaK, deltaK, counters } = state;
8285
+ const k = sources.length;
8286
+ const words = n * k;
8287
+ const seeds = Uint32Array.from(sources, (v, s) => s * n + v);
8288
+ ctx.device.queue.writeBuffer(S.buffer, S.offset, seeds);
8289
+ const forwardFields = { n, k, count: form === "edge" ? state.edgeCount : 0 };
8290
+ const forwardItems = form === "edge" ? state.edgeCount : words;
8291
+ let recorded = 0;
8292
+ let levels = 0;
8293
+ let overflow = false;
8294
+ let endsWords = null;
8295
+ for (let first = true; endsWords === null; first = false) {
8296
+ const batch = new CommandBatch(ctx, `${ALGORITHM$4}/forward`);
8297
+ const pass = batch.pass("forward");
8298
+ if (first) {
8299
+ recordFill(state, pass, depthK, words, 4294967295);
8300
+ recordFill(state, pass, sigmaK, words, 0);
8301
+ recordFill(state, pass, deltaK, words, 0);
8302
+ recordBc(state, pass, state.finalize, { counters, ends, S, depthK, sigmaK }, { n, k, role: 1 }, 1);
8303
+ }
8304
+ for (let level = 0; level < levelsPerSubmit; level++) {
8305
+ recordBc(state, pass, state.finalize, { counters, ends, S, depthK, sigmaK }, { n, k, role: 0 }, 1);
8306
+ if (form === "edge" && state.forwardEdge !== null && state.edgeSrc !== null && state.edgeDst !== null) {
8307
+ recordBc(
8308
+ state,
8309
+ pass,
8310
+ state.forwardEdge,
8311
+ { edgeSrc: state.edgeSrc, edgeDst: state.edgeDst, S, ends, counters, depthK, sigmaK },
8312
+ forwardFields,
8313
+ forwardItems
8314
+ );
8315
+ } else {
8316
+ recordBc(
8317
+ state,
8318
+ pass,
8319
+ state.forward,
8320
+ { rowPtr: state.rowPtr, colIdx: state.colIdx, S, ends, counters, depthK, sigmaK },
8321
+ forwardFields,
8322
+ forwardItems
8323
+ );
8324
+ }
8325
+ }
8326
+ recorded += levelsPerSubmit;
8327
+ batch.endPass();
8328
+ const endsCount = Math.min(n + 2, recorded + 1);
8329
+ const countersRequest = batch.readback(counters.buffer, counters.offset, FRONTIER_COUNTERS.byteLength);
8330
+ const endsRequest = batch.readback(ends.buffer, ends.offset, 4 * endsCount);
8331
+ const back = await submit(state, batch, signal);
8332
+ const words32 = new Uint32Array(back, countersRequest.offset, FRONTIER_COUNTERS.byteLength / 4);
8333
+ if (words32[W.done] !== 0) {
8334
+ levels = words32[W.level];
8335
+ overflow = words32[W.sigmaOverflow] !== 0;
8336
+ endsWords = new Uint32Array(back, endsRequest.offset, endsCount).slice(0, levels + 1);
8337
+ } else if (recorded > n + 2) {
8338
+ throw new WebGpuGraphError("E_VALIDATION", `${ALGORITHM$4}: the done flag never rose in ${recorded} levels`, {
8339
+ label: ALGORITHM$4,
8340
+ message: `the done flag never rose in ${recorded} levels`
8341
+ });
8342
+ }
8343
+ }
8344
+ const backwardLevels = [];
8345
+ for (let level = levels - 1; level >= 1; level--) {
8346
+ backwardLevels.push(level);
8347
+ }
8348
+ let arrays = null;
8349
+ for (let i = 0; ; i += BC_BACKWARD_LEVELS_PER_SUBMIT) {
8350
+ const chunk = backwardLevels.slice(i, i + BC_BACKWARD_LEVELS_PER_SUBMIT);
8351
+ const last = i + BC_BACKWARD_LEVELS_PER_SUBMIT >= backwardLevels.length;
8352
+ const batch = new CommandBatch(ctx, `${ALGORITHM$4}/backward`);
8353
+ const pass = batch.pass("backward");
8354
+ for (const level of chunk) {
8355
+ const start = endsWords[level];
8356
+ const count = endsWords[level + 1] - start;
8357
+ recordBc(
8358
+ state,
8359
+ pass,
8360
+ state.backward,
8361
+ { rowPtr: state.rowPtr, colIdx: state.colIdx, S, depthK, sigmaK, deltaK },
8362
+ { n, k, start, count },
8363
+ count
8364
+ );
8365
+ }
8366
+ if (last) {
8367
+ recordBc(state, pass, state.gather, { deltaK, bc: state.bc }, { n, k }, n);
8368
+ if (state.edgeGather !== null && state.arcScores !== null) {
8369
+ recordBc(
8370
+ state,
8371
+ pass,
8372
+ state.edgeGather,
8373
+ { rowPtr: state.rowPtr, colIdx: state.colIdx, depthK, sigmaK, deltaK, arcScores: state.arcScores },
8374
+ { n, k, count: state.arcCount },
8375
+ state.arcCount
8376
+ );
8377
+ }
8378
+ }
8379
+ batch.endPass();
8380
+ const wantArrays = last && tuning.readArrays === true;
8381
+ const requests = wantArrays ? [depthK, sigmaK, deltaK].map((b) => batch.readback(b.buffer, b.offset, 4 * words)) : [];
8382
+ const back = await submit(state, batch, signal);
8383
+ if (wantArrays) {
8384
+ arrays = {
8385
+ depthK: new Uint32Array(back, requests[0].offset, words).slice(),
8386
+ sigmaK: new Uint32Array(back, requests[1].offset, words).slice(),
8387
+ deltaK: new Float32Array(back, requests[2].offset, words).slice()
8388
+ };
8389
+ }
8390
+ if (last) {
8391
+ break;
8392
+ }
8393
+ }
8394
+ tuning.onBatch?.({
8395
+ sources,
8396
+ forward: form,
8397
+ levels,
8398
+ ends: endsWords.slice(),
8399
+ sigmaOverflow: overflow,
8400
+ depthK: arrays?.depthK ?? null,
8401
+ sigmaK: arrays?.sigmaK ?? null,
8402
+ deltaK: arrays?.deltaK ?? null
8403
+ });
8404
+ return { levels, overflow };
8405
+ }
8406
+ async function runRaw(ctx, s, sources, withEdges, tuning, options) {
8407
+ const n = s.nodeCount;
8408
+ const pinned = tuning.forward ?? "auto";
8409
+ const levelsPerSubmit = tuning.levelsPerSubmit ?? MAX_LEVELS_PER_SUBMIT;
8410
+ if (!Number.isInteger(levelsPerSubmit) || levelsPerSubmit < 1 || levelsPerSubmit > MAX_LEVELS_PER_SUBMIT) {
8411
+ throw badArgument("levelsPerSubmit", levelsPerSubmit, `an integer in [1, ${MAX_LEVELS_PER_SUBMIT}]`);
8412
+ }
8413
+ const limits = tuning.limits ?? ctx.caps.limits;
8414
+ const kMax = planBatchSize(n, sources.length, limits);
8415
+ const core = ctx.residency.core(s);
8416
+ assertWholeCore(core, s.arcCount, ctx.caps.limits.maxStorageBufferBindingSize, ALGORITHM$4);
8417
+ const { edgeCount } = s;
8418
+ const mayRunEdge = pinned === "edge" || pinned === "auto" && sources.length > kMax;
8419
+ const edgeView = mayRunEdge && edgeCount > 0 ? ctx.residency.view(s, "edgeList") : null;
8420
+ const scope = algorithmScope(ctx, ALGORITHM$4, RING_SLOTS$3);
8421
+ try {
8422
+ const arrayBytes = 4 * n * kMax;
8423
+ const lease = (bytes, label) => bindingOf(scope.scratch(bytes, label), bytes);
8424
+ const arcBytes = 4 * Math.max(1, s.arcCount);
8425
+ const [fill, finalize, forward, backward, gather] = await Promise.all(
8426
+ ["fill", "bc-finalize", "bc-forward", "bc-backward", "bc-gather"].map(
8427
+ (id) => ctx.pipelines.kernel(kernelSpec(id))
8428
+ )
8429
+ );
8430
+ const forwardEdge = edgeView === null ? null : await ctx.pipelines.kernel(kernelSpec("bc-forward-edge", { UNDIRECTED: !s.directed }));
8431
+ const edgeGather = withEdges ? await ctx.pipelines.kernel(kernelSpec("bc-edge-gather")) : null;
8432
+ const state = {
8433
+ ctx,
8434
+ scope,
8435
+ n,
8436
+ S: lease(arrayBytes, "S"),
8437
+ ends: lease(4 * (n + 2), "ends"),
8438
+ depthK: lease(arrayBytes, "depthK"),
8439
+ sigmaK: lease(arrayBytes, "sigmaK"),
8440
+ deltaK: lease(arrayBytes, "deltaK"),
8441
+ counters: lease(FRONTIER_COUNTERS.byteLength, "counters"),
8442
+ bc: lease(4 * n, "bc"),
8443
+ arcScores: withEdges ? lease(arcBytes, "arc-scores") : null,
8444
+ rowPtr: core.rowPtr,
8445
+ colIdx: core.colIdx ?? core.rowPtr,
8446
+ edgeSrc: edgeView?.bindings.src ?? null,
8447
+ edgeDst: edgeView?.bindings.dst ?? null,
8448
+ edgeCount,
8449
+ arcCount: s.arcCount,
8450
+ fill,
8451
+ finalize,
8452
+ forward,
8453
+ forwardEdge,
8454
+ backward,
8455
+ gather,
8456
+ edgeGather,
8457
+ bound: /* @__PURE__ */ new Map()
8458
+ };
8459
+ await ctx.allocator.check();
8460
+ const setup = new CommandBatch(ctx, `${ALGORITHM$4}/setup`);
8461
+ const setupPass = setup.pass("setup");
8462
+ recordFill(state, setupPass, state.bc, n, 0);
8463
+ if (state.arcScores !== null) {
8464
+ recordFill(state, setupPass, state.arcScores, arcBytes / 4, 0);
8465
+ }
8466
+ setup.endPass();
8467
+ await submit(state, setup, options?.signal);
8468
+ let overflow = false;
8469
+ let batches = 0;
8470
+ let previousLevels = -1;
8471
+ for (let start = 0; start < sources.length; ) {
8472
+ const k = planBatchSize(n, sources.length - start, limits);
8473
+ let form = pinned === "edge" ? "edge" : "frontier";
8474
+ if (pinned === "auto" && previousLevels >= 0) {
8475
+ form = previousLevels < BC_EDGE_PARALLEL_GAMMA * Math.log2(n) ? "edge" : "frontier";
8476
+ }
8477
+ if (state.forwardEdge === null) {
8478
+ form = "frontier";
8479
+ }
8480
+ const batch = sources.slice(start, start + k);
8481
+ const outcome = await runBatch(state, batch, form, levelsPerSubmit, tuning, options?.signal);
8482
+ overflow = overflow || outcome.overflow;
8483
+ previousLevels = outcome.levels;
8484
+ start += k;
8485
+ batches += 1;
8486
+ options?.onProgress?.(start, sources.length);
8487
+ }
8488
+ const result = new CommandBatch(ctx, `${ALGORITHM$4}/result`);
8489
+ const vertexRequest = result.readback(state.bc.buffer, state.bc.offset, 4 * n);
8490
+ const arcRequest = state.arcScores === null ? null : result.readback(state.arcScores.buffer, state.arcScores.offset, 4 * s.arcCount);
8491
+ const back = await submit(state, result, options?.signal);
8492
+ return {
8493
+ vertex: new Float32Array(back, vertexRequest.offset, n).slice(),
8494
+ perArc: arcRequest === null ? null : new Float32Array(back, arcRequest.offset, s.arcCount).slice(),
8495
+ sourcesUsed: sources.length,
8496
+ sigmaOverflow: overflow,
8497
+ batches
8498
+ };
8499
+ } finally {
8500
+ scope.dispose();
8501
+ }
8502
+ }
8503
+ function normaliser(s, normalized) {
8504
+ const n = s.nodeCount;
8505
+ const factor = s.directed ? (n - 1) * (n - 2) : (n - 1) * (n - 2) / 2;
8506
+ return normalized === true && factor > 0 ? factor : 1;
8507
+ }
8508
+ async function precheck(ctx, options) {
8509
+ ctx.assertReady();
8510
+ await assertDeviceComputes(ctx);
8511
+ if (options?.endpoints === true) {
8512
+ throw new WebGpuGraphError("E_UNSUPPORTED", `${ALGORITHM$4}: endpoints: true is not supported`, {
8513
+ feature: "betweenness.endpoints",
8514
+ hint: "the CPU endpoints branch in algorithms/src/algorithms/centrality/betweenness.ts (predecessors.length === 0 && w !== source) never fires, so there is no convention to match"
8515
+ });
8516
+ }
8517
+ }
8518
+ async function betweennessWithTuning(ctx, s, options, tuning) {
8519
+ await precheck(ctx, options);
8520
+ const n = s.nodeCount;
8521
+ const scores = checkDest$2(ALGORITHM$4, options?.dest, n) ?? new Float32Array(n);
8522
+ const sources = resolveSources(options, n);
8523
+ if (options?.signal?.aborted) {
8524
+ throw aborted(ALGORITHM$4);
8525
+ }
8526
+ if (n === 0 || sources.length === 0) {
8527
+ scores.fill(0);
8528
+ return { scores, iterations: 0, converged: true, precision: "f32", sourcesUsed: 0, sigmaOverflow: false };
8529
+ }
8530
+ const raw = await runRaw(ctx, s, sources, false, tuning, options);
8531
+ const divisor = (s.directed ? 1 : 2) * normaliser(s, options?.normalized);
8532
+ for (let v = 0; v < n; v++) {
8533
+ scores[v] = raw.vertex[v] / divisor;
8534
+ }
8535
+ return {
8536
+ scores,
8537
+ iterations: raw.batches,
8538
+ converged: true,
8539
+ precision: "f32",
8540
+ sourcesUsed: raw.sourcesUsed,
8541
+ sigmaOverflow: raw.sigmaOverflow
8542
+ };
8543
+ }
8544
+ async function edgeBetweennessWithTuning(ctx, s, options, tuning, onArcs) {
8545
+ await precheck(ctx, options);
8546
+ const n = s.nodeCount;
8547
+ const scores = checkDest$2("edgeBetweennessCentrality", options?.dest, s.edgeCount) ?? new Float32Array(s.edgeCount);
8548
+ const sources = resolveSources(options, n);
8549
+ if (options?.signal?.aborted) {
8550
+ throw aborted(ALGORITHM$4);
8551
+ }
8552
+ if (n === 0 || sources.length === 0 || s.arcCount === 0) {
8553
+ scores.fill(0);
8554
+ return { scores, precision: "f32", sourcesUsed: sources.length, sigmaOverflow: false };
8555
+ }
8556
+ const raw = await runRaw(ctx, s, sources, true, tuning, options);
8557
+ const perArc = raw.perArc ?? new Float32Array(s.arcCount);
8558
+ const folded = foldArcs(s, perArc, "sum");
8559
+ const divisor = (s.directed ? 1 : 2) * normaliser(s, options?.normalized);
8560
+ for (let e = 0; e < s.edgeCount; e++) {
8561
+ scores[e] = folded[e] / divisor;
8562
+ }
8563
+ return { scores, precision: "f32", sourcesUsed: raw.sourcesUsed, sigmaOverflow: raw.sigmaOverflow };
8564
+ }
8565
+ function betweennessCentrality(ctx, s, options) {
8566
+ return betweennessWithTuning(ctx, s, options, {});
8567
+ }
8568
+ function edgeBetweennessCentrality(ctx, s, options) {
8569
+ return edgeBetweennessWithTuning(ctx, s, options, {});
8570
+ }
8571
+ const ALGORITHM$3 = "closenessCentrality";
7203
8572
  const SOURCES_PER_BATCH = 32;
7204
8573
  const PER_SOURCE_WORDS = 4 * SOURCES_PER_BATCH;
7205
- const RING_SLOTS = 8 * MAX_LEVELS_PER_SUBMIT + 16;
8574
+ const RING_SLOTS$2 = 8 * MAX_LEVELS_PER_SUBMIT + 16;
7206
8575
  function reusingScratch(scope) {
7207
8576
  const held = /* @__PURE__ */ new Map();
7208
8577
  return {
@@ -7218,33 +8587,48 @@ function reusingScratch(scope) {
7218
8587
  }
7219
8588
  };
7220
8589
  }
7221
- async function weightedRoute(ctx, s, scores, options) {
8590
+ async function weightedRoute(ctx, s, scores, sources, options) {
7222
8591
  const n = s.nodeCount;
7223
- for (let source = 0; source < n; source++) {
8592
+ const count = sources?.length ?? n;
8593
+ const totals = sources === null ? null : new Float64Array(n);
8594
+ for (let i = 0; i < count; i++) {
7224
8595
  if (options?.signal?.aborted) {
7225
- throw aborted(ALGORITHM);
8596
+ throw aborted(ALGORITHM$3);
7226
8597
  }
8598
+ const source = sources === null ? i : sources[i];
7227
8599
  const { dist } = await sssp(ctx, s, source, { signal: options?.signal });
7228
8600
  let sum = 0;
7229
8601
  for (let v = 0; v < n; v++) {
7230
8602
  const d = dist[v];
7231
8603
  if (v !== source && d !== Infinity) {
7232
- sum += d;
8604
+ if (totals === null) {
8605
+ sum += d;
8606
+ } else {
8607
+ totals[v] += d;
8608
+ }
7233
8609
  }
7234
8610
  }
7235
- scores[source] = sum === 0 ? 0 : 1 / sum;
7236
- options?.onProgress?.(source + 1, n);
8611
+ if (totals === null) {
8612
+ scores[source] = sum === 0 ? 0 : 1 / sum;
8613
+ }
8614
+ options?.onProgress?.(i + 1, count);
8615
+ }
8616
+ if (totals !== null) {
8617
+ totals.forEach((sum, v) => {
8618
+ scores[v] = sum === 0 ? 0 : 1 / sum;
8619
+ });
7237
8620
  }
7238
- return { scores, iterations: n, converged: true, precision: "f32" };
8621
+ return { scores, iterations: count, converged: true, precision: "f32", sourcesUsed: count };
7239
8622
  }
7240
- async function sweepRoute(ctx, s, scores, levelsPerSubmit, options, tuning) {
8623
+ async function sweepRoute(ctx, s, scores, sources, levelsPerSubmit, options, tuning) {
7241
8624
  const n = s.nodeCount;
7242
- if (n === 0) {
7243
- return { scores, iterations: 0, converged: true, precision: "f32" };
8625
+ const seedCount = sources?.length ?? n;
8626
+ if (seedCount === 0) {
8627
+ return { scores, iterations: 0, converged: true, precision: "f32", sourcesUsed: 0 };
7244
8628
  }
7245
8629
  const core = ctx.residency.core(s);
7246
- assertWholeCore(core, s.arcCount, ctx.caps.limits.maxStorageBufferBindingSize, ALGORITHM);
7247
- const scope = algorithmScope(ctx, ALGORITHM, RING_SLOTS);
8630
+ assertWholeCore(core, s.arcCount, ctx.caps.limits.maxStorageBufferBindingSize, ALGORITHM$3);
8631
+ const scope = algorithmScope(ctx, ALGORITHM$3, RING_SLOTS$2);
7248
8632
  try {
7249
8633
  const wg = ctx.workgroupSize;
7250
8634
  const bytes = 4 * n;
@@ -7265,7 +8649,17 @@ async function sweepRoute(ctx, s, scores, levelsPerSubmit, options, tuning) {
7265
8649
  FRONTIER_COUNTERS.byteLength
7266
8650
  );
7267
8651
  const perSourceBytes = 4 * PER_SOURCE_WORDS;
7268
- const perSource = bindingOf(scope.scratch(perSourceBytes, "per-source"), perSourceBytes);
8652
+ const zeroedWords = PER_SOURCE_WORDS + (sources === null ? 0 : bitsBase);
8653
+ const perSourceAll = 4 * (zeroedWords + (sources === null ? 0 : sources.length));
8654
+ const perSource = bindingOf(scope.scratch(perSourceAll, "per-source"), perSourceAll);
8655
+ if (sources !== null) {
8656
+ ctx.device.queue.writeBuffer(
8657
+ perSource.buffer,
8658
+ perSource.offset + 4 * zeroedWords,
8659
+ Uint32Array.from(sources)
8660
+ );
8661
+ }
8662
+ const totals = sources === null ? null : new Float64Array(n);
7269
8663
  await ctx.allocator.check();
7270
8664
  const compact = await prepareCompact(reusingScratch(scope));
7271
8665
  const sweep = await ctx.pipelines.kernel(kernelSpec("closeness-sweep", graphOverrides(core, null)));
@@ -7275,29 +8669,34 @@ async function sweepRoute(ctx, s, scores, levelsPerSubmit, options, tuning) {
7275
8669
  const onePlan = plan1d(1, wg, ctx.caps);
7276
8670
  const regionPlan = plan1d(bitsBase, wg, ctx.caps);
7277
8671
  const sweepPlan = planGridStride(n, wg, ctx.caps);
7278
- const recordFill = (pass, dst, count, mode) => {
8672
+ const recordFill2 = (pass, dst, count, mode) => {
7279
8673
  const params = scope.params(FILL_PARAMS, { count, value: 0, mode, pad0: 0 });
7280
8674
  fill.dispatch(pass, fill.bind({ dst, P: params.binding }), plan1d(count, wg, ctx.caps), [params.offset]);
7281
8675
  };
7282
- const submit = (batch) => {
8676
+ const submit2 = (batch) => {
7283
8677
  scope.flush();
7284
8678
  return batch.submit();
7285
8679
  };
7286
- const setup = new CommandBatch(ctx, `${ALGORITHM}/setup`);
7287
- recordFill(setup.pass("fill"), iota, n, 1);
8680
+ const setup = new CommandBatch(ctx, `${ALGORITHM$3}/setup`);
8681
+ recordFill2(setup.pass("fill"), iota, n, 1);
7288
8682
  setup.endPass();
7289
- await submit(setup).readback;
8683
+ await submit2(setup).readback;
7290
8684
  ctx.assertReady();
7291
8685
  let batches = 0;
7292
- for (let batchStart = 0; batchStart < n; batchStart += SOURCES_PER_BATCH) {
8686
+ for (let batchStart = 0; batchStart < seedCount; batchStart += SOURCES_PER_BATCH) {
7293
8687
  let level = 0;
7294
8688
  for (let first = true; ; first = false) {
7295
- const batch = new CommandBatch(ctx, `${ALGORITHM}/levels`);
8689
+ const batch = new CommandBatch(ctx, `${ALGORITHM$3}/levels`);
7296
8690
  const pass = batch.pass("closeness");
7297
8691
  if (first) {
7298
- recordFill(pass, bits, 4 * bitsBase, 0);
7299
- recordFill(pass, perSource, PER_SOURCE_WORDS, 0);
7300
- const seed = scope.params(FRONTIER_PARAMS, { role: 1, n, bitsBase, source: batchStart });
8692
+ recordFill2(pass, bits, 4 * bitsBase, 0);
8693
+ recordFill2(pass, perSource, zeroedWords, 0);
8694
+ const seed = scope.params(FRONTIER_PARAMS, {
8695
+ role: sources === null ? 1 : 2,
8696
+ n: seedCount,
8697
+ bitsBase,
8698
+ source: batchStart
8699
+ });
7301
8700
  reduce.dispatch(pass, reduce.bind({ counters, perSource, bits, P: seed.binding }), onePlan, [
7302
8701
  seed.offset
7303
8702
  ]);
@@ -7315,7 +8714,8 @@ async function sweepRoute(ctx, s, scores, levelsPerSubmit, options, tuning) {
7315
8714
  arcBase: 0,
7316
8715
  arcEnd: s.arcCount,
7317
8716
  mode,
7318
- stride: sweepPlan.stride ?? wg
8717
+ stride: sweepPlan.stride ?? wg,
8718
+ perNode: sources === null ? 0 : 1
7319
8719
  });
7320
8720
  return {
7321
8721
  bound: sweep.bind({ ...graph, frontierList, counters, bits, perSource, P: params.binding }),
@@ -7340,18 +8740,26 @@ async function sweepRoute(ctx, s, scores, levelsPerSubmit, options, tuning) {
7340
8740
  batch.endPass();
7341
8741
  const doneRequest = batch.readback(counters.buffer, counters.offset + 4 * W.done, 4);
7342
8742
  const blockRequest = batch.readback(perSource.buffer, perSource.offset, perSourceBytes);
7343
- const submitted = submit(batch);
8743
+ const nodeRequest = totals === null ? null : batch.readback(perSource.buffer, perSource.offset + perSourceBytes, 4 * n);
8744
+ const submitted = submit2(batch);
7344
8745
  const back = await submitted.readback;
7345
8746
  ctx.assertReady();
7346
8747
  if (options?.signal?.aborted) {
7347
- throw aborted(ALGORITHM, submitted.id);
8748
+ throw aborted(ALGORITHM$3, submitted.id);
7348
8749
  }
7349
8750
  if (new Uint32Array(back, doneRequest.offset, 1)[0] !== 0) {
7350
8751
  const block = new Uint32Array(back, blockRequest.offset, PER_SOURCE_WORDS);
7351
- const count = Math.min(SOURCES_PER_BATCH, n - batchStart);
7352
- for (let i = 0; i < count; i++) {
7353
- const sum = block[3 * SOURCES_PER_BATCH + i] * 2 ** 32 + block[2 * SOURCES_PER_BATCH + i];
7354
- scores[batchStart + i] = sum === 0 ? 0 : 1 / sum;
8752
+ if (totals === null || nodeRequest === null) {
8753
+ const count = Math.min(SOURCES_PER_BATCH, n - batchStart);
8754
+ for (let i = 0; i < count; i++) {
8755
+ const sum = block[3 * SOURCES_PER_BATCH + i] * 2 ** 32 + block[2 * SOURCES_PER_BATCH + i];
8756
+ scores[batchStart + i] = sum === 0 ? 0 : 1 / sum;
8757
+ }
8758
+ } else {
8759
+ const sums = new Uint32Array(back, nodeRequest.offset, n);
8760
+ for (let v = 0; v < n; v++) {
8761
+ totals[v] += sums[v];
8762
+ }
7355
8763
  }
7356
8764
  tuning.onBatch?.(batchStart, block.slice());
7357
8765
  break;
@@ -7359,36 +8767,61 @@ async function sweepRoute(ctx, s, scores, levelsPerSubmit, options, tuning) {
7359
8767
  if (level > n + 3) {
7360
8768
  throw new WebGpuGraphError(
7361
8769
  "E_VALIDATION",
7362
- `${ALGORITHM}: the done flag never rose in ${level} levels of the batch at ${batchStart}`,
7363
- { label: ALGORITHM, message: `the done flag never rose in ${level} levels` }
8770
+ `${ALGORITHM$3}: the done flag never rose in ${level} levels of the batch at ${batchStart}`,
8771
+ { label: ALGORITHM$3, message: `the done flag never rose in ${level} levels` }
7364
8772
  );
7365
8773
  }
7366
8774
  }
7367
8775
  batches += 1;
7368
- options?.onProgress?.(Math.min(batchStart + SOURCES_PER_BATCH, n), n);
8776
+ options?.onProgress?.(Math.min(batchStart + SOURCES_PER_BATCH, seedCount), seedCount);
7369
8777
  }
7370
- return { scores, iterations: batches, converged: true, precision: "f32" };
8778
+ totals?.forEach((sum, v) => {
8779
+ scores[v] = sum === 0 ? 0 : 1 / sum;
8780
+ });
8781
+ return { scores, iterations: batches, converged: true, precision: "f32", sourcesUsed: seedCount };
7371
8782
  } finally {
7372
8783
  scope.dispose();
7373
8784
  }
7374
8785
  }
8786
+ function checkSources(s, sources) {
8787
+ if (sources === void 0) {
8788
+ return null;
8789
+ }
8790
+ if (s.directed) {
8791
+ throw new WebGpuGraphError("E_UNSUPPORTED", `${ALGORITHM$3}: sampled sources need an undirected snapshot`, {
8792
+ feature: "closenessCentrality.directedSources",
8793
+ hint: "run the CPU port, which searches the in-arcs"
8794
+ });
8795
+ }
8796
+ for (const v of sources) {
8797
+ if (!Number.isInteger(v) || v < 0 || v >= s.nodeCount) {
8798
+ throw new WebGpuGraphError("E_INVALID_ARGUMENT", `${ALGORITHM$3}: a source is not a node index`, {
8799
+ argument: "sources",
8800
+ value: v,
8801
+ expected: `an integer in [0, ${s.nodeCount})`
8802
+ });
8803
+ }
8804
+ }
8805
+ return sources;
8806
+ }
7375
8807
  async function closenessWithTuning(ctx, s, options, tuning) {
7376
8808
  ctx.assertReady();
7377
8809
  await assertDeviceComputes(ctx);
7378
8810
  for (const key of ["maxIterations", "tolerance"]) {
7379
8811
  if (options?.[key] !== void 0) {
7380
- throw new WebGpuGraphError("E_UNSUPPORTED", `${ALGORITHM}: ${key} has no meaning for an exact traversal`, {
8812
+ throw new WebGpuGraphError("E_UNSUPPORTED", `${ALGORITHM$3}: ${key} has no meaning for an exact traversal`, {
7381
8813
  option: key,
7382
8814
  hint: "closeness is an exact traversal; the option has no meaning here"
7383
8815
  });
7384
8816
  }
7385
8817
  }
7386
8818
  const n = s.nodeCount;
8819
+ const sources = checkSources(s, options?.sources);
7387
8820
  const levelsPerSubmit = tuning.levelsPerSubmit ?? MAX_LEVELS_PER_SUBMIT;
7388
8821
  if (!Number.isInteger(levelsPerSubmit) || levelsPerSubmit < 1 || levelsPerSubmit > MAX_LEVELS_PER_SUBMIT) {
7389
8822
  throw new WebGpuGraphError(
7390
8823
  "E_INVALID_ARGUMENT",
7391
- `${ALGORITHM}: levelsPerSubmit must be an integer in [1, ${MAX_LEVELS_PER_SUBMIT}]`,
8824
+ `${ALGORITHM$3}: levelsPerSubmit must be an integer in [1, ${MAX_LEVELS_PER_SUBMIT}]`,
7392
8825
  {
7393
8826
  argument: "levelsPerSubmit",
7394
8827
  value: levelsPerSubmit,
@@ -7396,16 +8829,16 @@ async function closenessWithTuning(ctx, s, options, tuning) {
7396
8829
  }
7397
8830
  );
7398
8831
  }
7399
- const scores = checkDest(ALGORITHM, options?.dest, n) ?? new Float32Array(n);
8832
+ const scores = checkDest$2(ALGORITHM$3, options?.dest, n) ?? new Float32Array(n);
7400
8833
  const weighted = options?.weighted ?? s.flags.weighted;
7401
8834
  if (options?.signal?.aborted) {
7402
- throw aborted(ALGORITHM);
8835
+ throw aborted(ALGORITHM$3);
7403
8836
  }
7404
8837
  if (weighted && s.weights !== null && !s.flags.allWeightsOne) {
7405
8838
  if (!s.flags.nonNegativeWeights) {
7406
8839
  throw new WebGpuGraphError(
7407
8840
  "E_UNSUPPORTED",
7408
- `${ALGORITHM}: a negative weight has no shortest-path distance to sum`,
8841
+ `${ALGORITHM$3}: a negative weight has no shortest-path distance to sum`,
7409
8842
  {
7410
8843
  feature: "closenessCentrality.negativeWeights",
7411
8844
  hint: "pass weighted: false to ignore the column"
@@ -7413,24 +8846,261 @@ async function closenessWithTuning(ctx, s, options, tuning) {
7413
8846
  );
7414
8847
  }
7415
8848
  if (!s.flags.finiteWeights) {
7416
- throw new WebGpuGraphError("E_UNSUPPORTED", `${ALGORITHM}: a NaN or infinite weight has no shortest path`, {
8849
+ throw new WebGpuGraphError("E_UNSUPPORTED", `${ALGORITHM$3}: a NaN or infinite weight has no shortest path`, {
7417
8850
  feature: "closenessCentrality.nonFiniteWeights"
7418
8851
  });
7419
8852
  }
7420
- return weightedRoute(ctx, s, scores, options);
8853
+ return weightedRoute(ctx, s, scores, sources, options);
7421
8854
  }
7422
- return sweepRoute(ctx, s, scores, levelsPerSubmit, options, tuning);
8855
+ return sweepRoute(ctx, s, scores, sources, levelsPerSubmit, options, tuning);
7423
8856
  }
7424
8857
  function closenessCentrality(ctx, s, options) {
7425
8858
  return closenessWithTuning(ctx, s, options, {});
7426
8859
  }
7427
- const U32_MAX = 4294967295;
7428
- async function prepareHistogram(scope) {
7429
- return prepareHistogramImpl(scope);
8860
+ const ALGORITHM$2 = "allPairsShortestPath";
8861
+ const DEFAULT_ROUNDS_PER_SUBMIT = Math.floor(APSP_MAX_DISPATCHES_PER_SUBMIT / 3);
8862
+ function allPairsCeiling(limits) {
8863
+ const binding = limits.maxStorageBufferBindingSize;
8864
+ const bufferSize = limits.maxBufferSize;
8865
+ const limitName = bufferSize < binding ? "maxBufferSize" : "maxStorageBufferBindingSize";
8866
+ const limit = Math.min(binding, bufferSize);
8867
+ let maxNodes = Math.floor(Math.sqrt(limit / 4));
8868
+ while (4 * maxNodes * maxNodes > limit) {
8869
+ maxNodes -= 1;
8870
+ }
8871
+ while (4 * (maxNodes + 1) * (maxNodes + 1) <= limit) {
8872
+ maxNodes += 1;
8873
+ }
8874
+ return { maxNodes, limit, limitName };
7430
8875
  }
7431
- async function prepareHistogramImpl(scope) {
7432
- const histogram = await scope.pipelines.kernel(kernelSpec("histogram"));
7433
- const fill = await scope.pipelines.kernel(kernelSpec("fill"));
8876
+ async function allPairsWithTuning(ctx, s, options, tuning) {
8877
+ ctx.assertReady();
8878
+ const n = s.nodeCount;
8879
+ const roundsPerSubmit = tuning.roundsPerSubmit ?? DEFAULT_ROUNDS_PER_SUBMIT;
8880
+ if (!Number.isInteger(roundsPerSubmit) || roundsPerSubmit < 1 || roundsPerSubmit > DEFAULT_ROUNDS_PER_SUBMIT) {
8881
+ throw new WebGpuGraphError(
8882
+ "E_INVALID_ARGUMENT",
8883
+ `${ALGORITHM$2}: roundsPerSubmit must be an integer in [1, ${DEFAULT_ROUNDS_PER_SUBMIT}]`,
8884
+ {
8885
+ argument: "roundsPerSubmit",
8886
+ value: roundsPerSubmit,
8887
+ expected: `an integer in [1, ${DEFAULT_ROUNDS_PER_SUBMIT}]`
8888
+ }
8889
+ );
8890
+ }
8891
+ const { maxNodes, limit, limitName } = allPairsCeiling(ctx.caps.limits);
8892
+ if (n > maxNodes) {
8893
+ throw new WebGpuGraphError(
8894
+ "E_TOO_LARGE",
8895
+ `${ALGORITHM$2}: ${n} nodes need a ${4 * n * n}-byte distance matrix in one storage binding; this device's ${limitName} of ${limit} bytes holds at most ${maxNodes} nodes -- raise it through GpuContextOptions.limits`,
8896
+ {
8897
+ needed: 4 * n * n,
8898
+ limit,
8899
+ path: "allPairs.matrix",
8900
+ algorithm: ALGORITHM$2,
8901
+ nodes: n,
8902
+ maxNodes,
8903
+ limitName,
8904
+ hint: `raise ${limitName} through GpuContextOptions.limits`
8905
+ }
8906
+ );
8907
+ }
8908
+ const dist = checkDest$2(ALGORITHM$2, options?.dest, n * n) ?? new Float32Array(n * n);
8909
+ const weighted = (options?.weighted ?? s.weights !== null) && s.weights !== null && !s.flags.allWeightsOne;
8910
+ if (weighted && !s.flags.nonNegativeWeights) {
8911
+ throw new WebGpuGraphError("E_UNSUPPORTED", `${ALGORITHM$2}: a negative weight is not supported`, {
8912
+ feature: "allPairs.negativeWeights",
8913
+ hint: "the blocked Floyd-Warshall sweep needs non-negative weights; pass weighted: false for hop counts"
8914
+ });
8915
+ }
8916
+ if (weighted && !s.flags.finiteWeights) {
8917
+ throw new WebGpuGraphError("E_UNSUPPORTED", `${ALGORITHM$2}: a NaN or infinite weight has no shortest path`, {
8918
+ feature: "allPairs.nonFiniteWeights"
8919
+ });
8920
+ }
8921
+ if (options?.signal?.aborted) {
8922
+ throw aborted(ALGORITHM$2);
8923
+ }
8924
+ if (n === 0) {
8925
+ return { dist, n };
8926
+ }
8927
+ await assertDeviceComputes(ctx);
8928
+ const core = ctx.residency.core(s);
8929
+ assertWholeCore(core, s.arcCount, ctx.caps.limits.maxStorageBufferBindingSize, ALGORITHM$2);
8930
+ const blocks = Math.ceil(n / APSP_TILE);
8931
+ const scope = algorithmScope(ctx, ALGORITHM$2, Math.min(roundsPerSubmit, blocks) + 2);
8932
+ try {
8933
+ const wg = ctx.workgroupSize;
8934
+ const bytes = 4 * n * n;
8935
+ const matrix = bindingOf(scope.scratch(bytes, "dist"), bytes);
8936
+ await ctx.allocator.check();
8937
+ const weightsBinding = weighted ? void 0 : null;
8938
+ const fill = await ctx.pipelines.kernel(kernelSpec("fill"));
8939
+ const init = await ctx.pipelines.kernel(kernelSpec("apsp-init", graphOverrides(core, null, weightsBinding)));
8940
+ const phases = await Promise.all(
8941
+ [0, 1, 2].map((phase) => ctx.pipelines.kernel(kernelSpec("apsp-fw", { PHASE: phase })))
8942
+ );
8943
+ const graph = graphBindings(core, null, weightsBinding);
8944
+ const others = blocks - 1;
8945
+ const phasePlans = [plan2d(1, ctx.caps), plan2d(2 * others, ctx.caps), plan2d(others * others, ctx.caps)];
8946
+ for (let first = 0; first < blocks; first += roundsPerSubmit) {
8947
+ const batch = new CommandBatch(ctx, `${ALGORITHM$2}/sweep`);
8948
+ const pass = batch.pass("apsp");
8949
+ if (first === 0) {
8950
+ const fillParams = scope.params(FILL_PARAMS, { count: n * n, value: F32_INF_BITS, mode: 0, pad0: 0 });
8951
+ fill.dispatch(pass, fill.bind({ dst: matrix, P: fillParams.binding }), plan1d(n * n, wg, ctx.caps), [
8952
+ fillParams.offset
8953
+ ]);
8954
+ const initParams = scope.params(APSP_PARAMS, { n, round: 0, blocks, infBits: F32_INF_BITS });
8955
+ init.dispatch(
8956
+ pass,
8957
+ init.bind({ ...graph, dist: matrix, P: initParams.binding }),
8958
+ plan1d(n, wg, ctx.caps),
8959
+ [initParams.offset]
8960
+ );
8961
+ }
8962
+ const last = Math.min(first + roundsPerSubmit, blocks);
8963
+ for (let round = first; round < last; round++) {
8964
+ const params = scope.params(APSP_PARAMS, { n, round, blocks, infBits: F32_INF_BITS });
8965
+ for (let phase = 0; phase < 3; phase++) {
8966
+ const kernel = phases[phase];
8967
+ kernel.dispatch(pass, kernel.bind({ dist: matrix, P: params.binding }), phasePlans[phase], [
8968
+ params.offset
8969
+ ]);
8970
+ }
8971
+ }
8972
+ batch.endPass();
8973
+ scope.flush();
8974
+ const submitted = batch.submit();
8975
+ await submitted.readback;
8976
+ ctx.assertReady();
8977
+ options?.onProgress?.(last, blocks);
8978
+ if (options?.signal?.aborted) {
8979
+ throw aborted(ALGORITHM$2, submitted.id);
8980
+ }
8981
+ }
8982
+ await ctx.readback.read(matrix.buffer, bytes, dist);
8983
+ ctx.assertReady();
8984
+ return { dist, n };
8985
+ } finally {
8986
+ scope.dispose();
8987
+ }
8988
+ }
8989
+ function allPairsShortestPath(ctx, s, options) {
8990
+ return allPairsWithTuning(ctx, s, options, {});
8991
+ }
8992
+ function planGroupRows(lengthBound, threadMax = GROUP_ROW_THREAD_MAX) {
8993
+ if (!Number.isInteger(threadMax) || threadMax < 0 || threadMax > GROUP_ROW_THREAD_LIMIT) {
8994
+ throw new WebGpuGraphError(
8995
+ "E_INVALID_ARGUMENT",
8996
+ `planGroupRows: threadMax must be an integer in [0, ${GROUP_ROW_THREAD_LIMIT}]`,
8997
+ { argument: "threadMax", value: threadMax, expected: `[0, ${GROUP_ROW_THREAD_LIMIT}]` }
8998
+ );
8999
+ }
9000
+ const n = lengthBound.length;
9001
+ const thread = [];
9002
+ const hash = [];
9003
+ for (let v = 0; v < n; v++) {
9004
+ (lengthBound[v] <= threadMax ? thread : hash).push(v);
9005
+ }
9006
+ const words = new Uint32Array(thread.length + 2 * hash.length);
9007
+ words.set(thread, 0);
9008
+ words.set(hash, thread.length);
9009
+ let next = 1;
9010
+ for (let g = 0; g < hash.length; g++) {
9011
+ words[thread.length + hash.length + g] = next;
9012
+ next += 2 * GROUP_HASH_LOAD_FACTOR * lengthBound[hash[g]];
9013
+ }
9014
+ return { words, threadCount: thread.length, hashCount: hash.length, regionWords: next };
9015
+ }
9016
+ async function prepareGroupByKeyRow(scope) {
9017
+ const kernels = /* @__PURE__ */ new Map();
9018
+ for (const tier of [0, 1]) {
9019
+ for (const weighted of [false, true]) {
9020
+ const spec = kernelSpec("group-by-key-row", { TIER: tier, WEIGHTED: weighted });
9021
+ kernels.set(`${tier}/${weighted}`, await scope.pipelines.kernel(spec));
9022
+ }
9023
+ }
9024
+ return new GroupByKeyPlannerImpl(scope, kernels);
9025
+ }
9026
+ function checkWords$2(name, binding, words) {
9027
+ if (binding.size < 4 * words) {
9028
+ throw new WebGpuGraphError("E_INVALID_ARGUMENT", `groupByKeyRow: ${name} is smaller than 4 x ${words} bytes`, {
9029
+ argument: name,
9030
+ value: binding.size,
9031
+ expected: 4 * words
9032
+ });
9033
+ }
9034
+ }
9035
+ class GroupByKeyPlannerImpl {
9036
+ /**
9037
+ * Wraps the compiled variants; use prepareGroupByKeyRow().
9038
+ * @param scope - the caller's scope
9039
+ * @param kernels - the variants by `tier/weighted`
9040
+ */
9041
+ constructor(scope, kernels) {
9042
+ this.scope = scope;
9043
+ this.kernels = kernels;
9044
+ }
9045
+ /**
9046
+ * Records the tier dispatches (see the interface).
9047
+ * @param pass - the compute pass
9048
+ * @param r - the record
9049
+ */
9050
+ record(pass, r) {
9051
+ const { threadCount, hashCount } = r.plan;
9052
+ const rows = threadCount + hashCount;
9053
+ checkWords$2("rows", r.rows, rows + hashCount);
9054
+ checkWords$2("hashRegion", r.hashRegion, r.plan.regionWords);
9055
+ checkWords$2("rowPtr", r.rowPtr, rows + 1);
9056
+ if (rows === 0) {
9057
+ return;
9058
+ }
9059
+ const weighted = r.weights !== null;
9060
+ const tiers = [
9061
+ [0, 0, threadCount],
9062
+ [1, threadCount, hashCount]
9063
+ ];
9064
+ for (const [tier, rowsBase, count] of tiers) {
9065
+ if (count === 0) {
9066
+ continue;
9067
+ }
9068
+ const kernel = this.kernels.get(`${tier}/${weighted}`);
9069
+ if (kernel === void 0) {
9070
+ throw new WebGpuGraphError("E_INVALID_ARGUMENT", "groupByKeyRow: no variant was prepared", {
9071
+ argument: "tier",
9072
+ value: tier
9073
+ });
9074
+ }
9075
+ const params = this.scope.params(GROUP_PARAMS, {
9076
+ rowsBase,
9077
+ basesBase: threadCount + hashCount,
9078
+ count,
9079
+ pad0: 0
9080
+ });
9081
+ const bound = kernel.bind({
9082
+ rowPtr: r.rowPtr,
9083
+ colIdx: r.colIdx,
9084
+ weights: r.weights ?? r.colIdx,
9085
+ keyIn: r.keyIn,
9086
+ rows: r.rows,
9087
+ hashRegion: r.hashRegion,
9088
+ bestKey: r.bestKey,
9089
+ bestScore: r.bestScore,
9090
+ P: params.binding
9091
+ });
9092
+ const plan = tier === 0 ? plan1d(count, this.scope.workgroupSize, this.scope.caps) : plan2d(count, this.scope.caps);
9093
+ kernel.dispatch(pass, bound, plan, [params.offset]);
9094
+ }
9095
+ }
9096
+ }
9097
+ const U32_MAX = 4294967295;
9098
+ async function prepareHistogram(scope) {
9099
+ return prepareHistogramImpl(scope);
9100
+ }
9101
+ async function prepareHistogramImpl(scope) {
9102
+ const histogram = await scope.pipelines.kernel(kernelSpec("histogram"));
9103
+ const fill = await scope.pipelines.kernel(kernelSpec("fill"));
7434
9104
  return new HistogramPlannerImpl(scope, histogram, fill);
7435
9105
  }
7436
9106
  async function prepareCountingSort(scope) {
@@ -7439,7 +9109,7 @@ async function prepareCountingSort(scope) {
7439
9109
  const scatter = await scope.pipelines.kernel(kernelSpec("counting-scatter"));
7440
9110
  return new CountingSortPlannerImpl(scope, histogram, scan, scatter);
7441
9111
  }
7442
- function checkWords(name, binding, words) {
9112
+ function checkWords$1(name, binding, words) {
7443
9113
  if (binding.size < 4 * words) {
7444
9114
  throw new WebGpuGraphError("E_INVALID_ARGUMENT", `histogram: ${name} is smaller than 4 x ${words} bytes`, {
7445
9115
  argument: name,
@@ -7448,122 +9118,867 @@ function checkWords(name, binding, words) {
7448
9118
  });
7449
9119
  }
7450
9120
  }
7451
- function checkHistogramArguments(keys, count, bins, hist) {
7452
- if (!Number.isSafeInteger(count) || count < 0 || count > U32_MAX) {
7453
- throw new WebGpuGraphError("E_INVALID_ARGUMENT", "histogram: count must be a non-negative integer below 2^32", {
7454
- argument: "count",
7455
- value: count
7456
- });
9121
+ function checkHistogramArguments(keys, count, bins, hist) {
9122
+ if (!Number.isSafeInteger(count) || count < 0 || count > U32_MAX) {
9123
+ throw new WebGpuGraphError("E_INVALID_ARGUMENT", "histogram: count must be a non-negative integer below 2^32", {
9124
+ argument: "count",
9125
+ value: count
9126
+ });
9127
+ }
9128
+ if (!Number.isSafeInteger(bins) || bins < 1 || bins > U32_MAX) {
9129
+ throw new WebGpuGraphError("E_INVALID_ARGUMENT", "histogram: bins must be an integer in [1, 2^32)", {
9130
+ argument: "bins",
9131
+ value: bins
9132
+ });
9133
+ }
9134
+ checkWords$1("keys", keys, count);
9135
+ checkWords$1("hist", hist, bins);
9136
+ }
9137
+ class HistogramPlannerImpl {
9138
+ /**
9139
+ * Wraps the resolved kernels; use prepareHistogram().
9140
+ * @param scope - the caller's scope
9141
+ * @param histogram - the `histogram` kernel
9142
+ * @param fill - the `fill` kernel
9143
+ */
9144
+ constructor(scope, histogram, fill) {
9145
+ this.dispatches = 0;
9146
+ this.scope = scope;
9147
+ this.histogram = histogram;
9148
+ this.fill = fill;
9149
+ }
9150
+ /**
9151
+ * Dispatches the last record() issued.
9152
+ * @returns the count
9153
+ */
9154
+ get lastDispatches() {
9155
+ return this.dispatches;
9156
+ }
9157
+ /**
9158
+ * Records the fill and the histogram (see the interface).
9159
+ * @param pass - the compute pass
9160
+ * @param keys - the keys
9161
+ * @param count - the key count
9162
+ * @param bins - the bin count
9163
+ * @param hist - the counts
9164
+ */
9165
+ record(pass, keys, count, bins, hist) {
9166
+ checkHistogramArguments(keys, count, bins, hist);
9167
+ this.recordZero(pass, hist, bins);
9168
+ this.dispatches = 1;
9169
+ if (count === 0) {
9170
+ return;
9171
+ }
9172
+ const params = this.scope.params(HIST_PARAMS, { count, bins, pad0: 0, pad1: 0 });
9173
+ const bound = this.histogram.bind({ keys, hist, P: params.binding });
9174
+ this.histogram.dispatch(pass, bound, plan1d(count, this.scope.workgroupSize, this.scope.caps), [params.offset]);
9175
+ this.dispatches = 2;
9176
+ }
9177
+ /**
9178
+ * Records a `fill` of `words` zeros into `dst` (PD-12: a dispatch inside the pass, never an encoder clear).
9179
+ * @param pass - the compute pass
9180
+ * @param dst - the words to zero
9181
+ * @param words - how many (>= 1)
9182
+ */
9183
+ recordZero(pass, dst, words) {
9184
+ const params = this.scope.params(FILL_PARAMS, { count: words, value: 0, mode: 0, pad0: 0 });
9185
+ const bound = this.fill.bind({ dst, P: params.binding });
9186
+ this.fill.dispatch(pass, bound, plan1d(words, this.scope.workgroupSize, this.scope.caps), [params.offset]);
9187
+ }
9188
+ }
9189
+ class CountingSortPlannerImpl {
9190
+ /**
9191
+ * Wraps the resolved planners and kernel; use prepareCountingSort().
9192
+ * @param scope - the caller's scope
9193
+ * @param histogram - the histogram planner (also the zeroing of the cursor)
9194
+ * @param scan - the scan planner
9195
+ * @param scatter - the `counting-scatter` kernel
9196
+ */
9197
+ constructor(scope, histogram, scan, scatter) {
9198
+ this.dispatches = 0;
9199
+ this.scope = scope;
9200
+ this.histogram = histogram;
9201
+ this.scan = scan;
9202
+ this.scatter = scatter;
9203
+ }
9204
+ /**
9205
+ * Dispatches the last record() issued.
9206
+ * @returns the count
9207
+ */
9208
+ get lastDispatches() {
9209
+ return this.dispatches;
9210
+ }
9211
+ /**
9212
+ * Records the four stages (see the interface).
9213
+ * @param pass - the compute pass
9214
+ * @param keys - the keys
9215
+ * @param count - the key count
9216
+ * @param bins - the bin count
9217
+ * @param scratch - the histogram and cursor
9218
+ * @param outIndex - the sorted indices
9219
+ * @param outStart - the bin starts
9220
+ */
9221
+ record(pass, keys, count, bins, scratch, outIndex, outStart) {
9222
+ checkHistogramArguments(keys, count, bins, scratch.hist);
9223
+ checkWords$1("cursor", scratch.cursor, bins);
9224
+ checkWords$1("outIndex", outIndex, count);
9225
+ checkWords$1("outStart", outStart, bins);
9226
+ this.histogram.record(pass, keys, count, bins, scratch.hist);
9227
+ this.scan.record(pass, scratch.hist, bins, outStart);
9228
+ this.histogram.recordZero(pass, scratch.cursor, bins);
9229
+ this.dispatches = this.histogram.lastDispatches + this.scan.lastDispatches + 1;
9230
+ if (count === 0) {
9231
+ return;
9232
+ }
9233
+ const params = this.scope.params(HIST_PARAMS, { count, bins, pad0: 0, pad1: 0 });
9234
+ const bound = this.scatter.bind({ keys, start: outStart, cursor: scratch.cursor, outIndex, P: params.binding });
9235
+ this.scatter.dispatch(pass, bound, plan1d(count, this.scope.workgroupSize, this.scope.caps), [params.offset]);
9236
+ this.dispatches += 1;
9237
+ }
9238
+ }
9239
+ async function prepareCooToCsr(scope) {
9240
+ const histogram = await prepareHistogram(scope);
9241
+ const scan = await prepareScan(scope);
9242
+ const fill = await scope.pipelines.kernel(kernelSpec("fill"));
9243
+ const scatter = /* @__PURE__ */ new Map();
9244
+ for (const sorted of [false, true]) {
9245
+ for (const weighted of [false, true]) {
9246
+ const spec = kernelSpec("coo-scatter", { SORTED_INPUT: sorted, WEIGHTED: weighted });
9247
+ scatter.set(`${sorted}/${weighted}`, await scope.pipelines.kernel(spec));
9248
+ }
9249
+ }
9250
+ return new CooToCsrPlannerImpl(scope, histogram, scan, fill, scatter);
9251
+ }
9252
+ function checkWords(name, binding, words) {
9253
+ if (binding.size < 4 * words) {
9254
+ throw new WebGpuGraphError("E_INVALID_ARGUMENT", `cooToCsr: ${name} is smaller than 4 x ${words} bytes`, {
9255
+ argument: name,
9256
+ value: binding.size,
9257
+ expected: 4 * words
9258
+ });
9259
+ }
9260
+ }
9261
+ function checkRecord(r) {
9262
+ for (const [name, value] of [
9263
+ ["count", r.count],
9264
+ ["n", r.n]
9265
+ ]) {
9266
+ if (!Number.isSafeInteger(value) || value < 0 || value >= U32_MAX$2) {
9267
+ throw new WebGpuGraphError("E_INVALID_ARGUMENT", `cooToCsr: ${name} must be an integer in [0, 2^32 - 1)`, {
9268
+ argument: name,
9269
+ value
9270
+ });
9271
+ }
9272
+ }
9273
+ if (r.weights === null !== (r.out.weights === null)) {
9274
+ throw new WebGpuGraphError(
9275
+ "E_INVALID_ARGUMENT",
9276
+ "cooToCsr: weights and out.weights must be both present or both null",
9277
+ {
9278
+ argument: "weights",
9279
+ value: r.weights === null ? "null" : "present",
9280
+ expected: r.out.weights === null ? "null" : "present"
9281
+ }
9282
+ );
9283
+ }
9284
+ checkWords("src", r.src, r.count);
9285
+ checkWords("dst", r.dst, r.count);
9286
+ checkWords("out.rowPtr", r.out.rowPtr, r.n + 1);
9287
+ checkWords("out.colIdx", r.out.colIdx, r.count);
9288
+ checkWords("out.flag", r.out.flag, 1);
9289
+ if (r.weights !== null && r.out.weights !== null) {
9290
+ checkWords("weights", r.weights, r.count);
9291
+ checkWords("out.weights", r.out.weights, r.count);
9292
+ }
9293
+ }
9294
+ class CooToCsrPlannerImpl {
9295
+ /**
9296
+ * Wraps the resolved planners and kernels; use prepareCooToCsr().
9297
+ * @param scope - the caller's scope
9298
+ * @param histogram - the histogram planner
9299
+ * @param scan - the scan planner
9300
+ * @param fill - the `fill` kernel
9301
+ * @param scatter - the `coo-scatter` variants by `sorted/weighted`
9302
+ */
9303
+ constructor(scope, histogram, scan, fill, scatter) {
9304
+ this.scope = scope;
9305
+ this.histogram = histogram;
9306
+ this.scan = scan;
9307
+ this.fill = fill;
9308
+ this.scatter = scatter;
9309
+ }
9310
+ /**
9311
+ * Records one build (see the interface).
9312
+ * @param pass - the compute pass
9313
+ * @param r - the arcs and the output
9314
+ */
9315
+ record(pass, r) {
9316
+ checkRecord(r);
9317
+ const { scope } = this;
9318
+ const wg = scope.workgroupSize;
9319
+ const degreeBytes = 4 * (r.n + 1);
9320
+ const degrees = {
9321
+ buffer: scope.scratch(degreeBytes, "cooToCsr/degrees"),
9322
+ offset: 0,
9323
+ size: degreeBytes,
9324
+ window: null
9325
+ };
9326
+ this.histogram.record(pass, r.src, r.count, r.n + 1, degrees);
9327
+ this.scan.record(pass, degrees, r.n + 1, r.out.rowPtr);
9328
+ let cursors = r.out.flag;
9329
+ if (r.sortedInput) {
9330
+ this.zero(pass, r.out.flag, 1);
9331
+ } else if (r.n > 0) {
9332
+ const bytes = 4 * r.n;
9333
+ cursors = { buffer: scope.scratch(bytes, "cooToCsr/cursors"), offset: 0, size: bytes, window: null };
9334
+ this.zero(pass, cursors, r.n);
9335
+ }
9336
+ if (r.count === 0) {
9337
+ return;
9338
+ }
9339
+ const weighted = r.weights !== null;
9340
+ const kernel = this.scatter.get(`${r.sortedInput}/${weighted}`);
9341
+ if (kernel === void 0) {
9342
+ throw new WebGpuGraphError("E_INVALID_ARGUMENT", "cooToCsr: no scatter variant was prepared", {
9343
+ argument: "sortedInput",
9344
+ value: r.sortedInput
9345
+ });
9346
+ }
9347
+ const outWeight = r.out.weights ?? {
9348
+ buffer: scope.scratch(4, "cooToCsr/weight-dummy"),
9349
+ offset: 0,
9350
+ size: 4,
9351
+ window: null
9352
+ };
9353
+ const params = scope.params(COO_PARAMS, { count: r.count, pad0: 0, pad1: 0, pad2: 0 });
9354
+ const bound = kernel.bind({
9355
+ src: r.src,
9356
+ dst: r.dst,
9357
+ weight: r.weights ?? r.dst,
9358
+ rowPtr: r.out.rowPtr,
9359
+ cursors,
9360
+ colIdx: r.out.colIdx,
9361
+ outWeight,
9362
+ P: params.binding
9363
+ });
9364
+ kernel.dispatch(pass, bound, plan1d(r.count, wg, scope.caps), [params.offset]);
9365
+ }
9366
+ /**
9367
+ * Records a `fill` of `words` zeros.
9368
+ * @param pass - the compute pass
9369
+ * @param dst - the words
9370
+ * @param words - how many (>= 1)
9371
+ */
9372
+ zero(pass, dst, words) {
9373
+ const params = this.scope.params(FILL_PARAMS, { count: words, value: 0, mode: 0, pad0: 0 });
9374
+ const bound = this.fill.bind({ dst, P: params.binding });
9375
+ this.fill.dispatch(pass, bound, plan1d(words, this.scope.workgroupSize, this.scope.caps), [params.offset]);
9376
+ }
9377
+ }
9378
+ function sortBitsFor(n) {
9379
+ for (const bits of [8, 16, 24]) {
9380
+ if (n < 2 ** bits) {
9381
+ return bits;
9382
+ }
9383
+ }
9384
+ return 32;
9385
+ }
9386
+ function scratchWords(scope, words, label) {
9387
+ const size = 4 * Math.max(1, words);
9388
+ return { buffer: scope.scratch(size, label), offset: 0, size, window: null };
9389
+ }
9390
+ function assertBindable(ctx, needed, path, algorithm) {
9391
+ const limit = ctx.caps.limits.maxStorageBufferBindingSize;
9392
+ if (needed > limit) {
9393
+ throw new WebGpuGraphError(
9394
+ "E_TOO_LARGE",
9395
+ `${algorithm}: ${path} needs ${needed} bytes in one binding, above the device limit of ${limit}`,
9396
+ { needed, limit, path, algorithm }
9397
+ );
9398
+ }
9399
+ }
9400
+ function assertMergeable(n, src, dst, algorithm) {
9401
+ const incident = new Uint32Array(n);
9402
+ for (let e = 0; e < src.length; e++) {
9403
+ if (src[e] !== dst[e]) {
9404
+ incident[src[e]]++;
9405
+ incident[dst[e]]++;
9406
+ }
9407
+ }
9408
+ const heavy = /* @__PURE__ */ new Map();
9409
+ for (let v = 0; v < n; v++) {
9410
+ if (incident[v] > PARALLEL_MERGE_LIMIT) {
9411
+ heavy.set(v, heavy.size);
9412
+ }
9413
+ }
9414
+ if (heavy.size < 2) {
9415
+ return;
9416
+ }
9417
+ const pairs = new Uint32Array(heavy.size * heavy.size);
9418
+ for (let e = 0; e < src.length; e++) {
9419
+ const a = heavy.get(src[e]);
9420
+ const b = heavy.get(dst[e]);
9421
+ if (a === void 0 || b === void 0 || a === b) {
9422
+ continue;
9423
+ }
9424
+ const key = Math.min(a, b) * heavy.size + Math.max(a, b);
9425
+ if (++pairs[key] > PARALLEL_MERGE_LIMIT) {
9426
+ throw new WebGpuGraphError(
9427
+ "E_UNSUPPORTED",
9428
+ `${algorithm}: nodes ${src[e]} and ${dst[e]} are joined by more than ${PARALLEL_MERGE_LIMIT} parallel edges, more than one weighted merge sums`,
9429
+ { feature: `${algorithm}.parallelEdges`, hint: "run it unweighted, or merge the parallel edges first" }
9430
+ );
9431
+ }
9432
+ }
9433
+ }
9434
+ function assertBuildSorted(bytes, build, label) {
9435
+ if (new Uint32Array(bytes, build.flag.offset, 1)[0] !== 0) {
9436
+ throw new WebGpuGraphError("E_VALIDATION", `${label}: the simple graph's arcs reached cooToCsr out of order`, {
9437
+ label: `${label}/simple-graph`,
9438
+ message: "the sorted-input precondition of cooToCsr failed on the device"
9439
+ });
9440
+ }
9441
+ }
9442
+ async function buildSimpleSymmetric(ctx, s, scope, withWeights, label) {
9443
+ const n = s.nodeCount;
9444
+ const wg = ctx.workgroupSize;
9445
+ const cooToCsr = await prepareCooToCsr(scope);
9446
+ const flag = scratchWords(scope, 1, "simple/flag");
9447
+ const rowPtr = scratchWords(scope, n + 1, "simple/rowPtr");
9448
+ const { edgeCount } = s;
9449
+ const list = s.edgeList();
9450
+ let selfLoops = 0;
9451
+ for (let e = 0; e < edgeCount; e++) {
9452
+ if (list.src[e] === list.dst[e]) {
9453
+ selfLoops++;
9454
+ }
9455
+ }
9456
+ const valid = 2 * (edgeCount - selfLoops);
9457
+ const empty2 = () => {
9458
+ const batch = new CommandBatch(ctx, `${label}/simple-graph`);
9459
+ cooToCsr.record(batch.pass("simple-graph"), {
9460
+ src: flag,
9461
+ dst: flag,
9462
+ weights: null,
9463
+ count: 0,
9464
+ n,
9465
+ sortedInput: true,
9466
+ out: { rowPtr, colIdx: flag, weights: null, flag }
9467
+ });
9468
+ batch.endPass();
9469
+ const graph2 = { n, arcCount: 0, rowPtr, colIdx: null, weights: null, src: null };
9470
+ return { graph: graph2, batch, flag: batch.readback(flag.buffer, 0, 4) };
9471
+ };
9472
+ if (valid === 0) {
9473
+ return empty2();
9474
+ }
9475
+ const arcs = 2 * edgeCount;
9476
+ const tableBytes = Math.max(4, radixHistBytes(arcs, wg));
9477
+ assertBindable(ctx, Math.max(4 * (arcs + 1), tableBytes), "the simple graph's arc arrays", label);
9478
+ if (withWeights) {
9479
+ assertMergeable(n, list.src, list.dst, label);
9480
+ }
9481
+ ctx.residency.core(s, ["rowPtr"]);
9482
+ const edges = ctx.residency.view(s, "edgeList");
9483
+ const edgeWeights = edges.bindings.weights ?? null;
9484
+ const emitPlan = plan1d(arcs, wg, ctx.caps);
9485
+ const emit = /* @__PURE__ */ new Map();
9486
+ for (const indexed of [false, true]) {
9487
+ const spec = kernelSpec("coo-emit", { INDEXED: indexed, WEIGHTED: edgeWeights !== null });
9488
+ emit.set(indexed, await ctx.pipelines.kernel(spec));
9489
+ }
9490
+ const fill = await ctx.pipelines.kernel(kernelSpec("fill"));
9491
+ const runFlags = await ctx.pipelines.kernel(kernelSpec("run-flags"));
9492
+ const sort = await prepareRadixSort(scope);
9493
+ const scan = await prepareScan(scope);
9494
+ const compact = await prepareCompact(scope);
9495
+ const aSrc = scratchWords(scope, arcs, "simple/aSrc");
9496
+ const aDst = scratchWords(scope, arcs, "simple/aDst");
9497
+ const aW = scratchWords(scope, arcs, "simple/aW");
9498
+ const vals = scratchWords(scope, arcs, "simple/vals");
9499
+ const sKeys = scratchWords(scope, arcs, "simple/sortKeys");
9500
+ const sVals = scratchWords(scope, arcs, "simple/sortVals");
9501
+ const hist = {
9502
+ buffer: scope.scratch(tableBytes, "simple/hist"),
9503
+ offset: 0,
9504
+ size: tableBytes,
9505
+ window: null
9506
+ };
9507
+ const offsets = {
9508
+ buffer: scope.scratch(tableBytes, "simple/offsets"),
9509
+ offset: 0,
9510
+ size: tableBytes,
9511
+ window: null
9512
+ };
9513
+ const sortedSrc = scratchWords(scope, arcs, "simple/sortedSrc");
9514
+ const sortedDst = scratchWords(scope, arcs, "simple/sortedDst");
9515
+ const sortedW = scratchWords(scope, arcs, "simple/sortedW");
9516
+ const flags = scratchWords(scope, arcs, "simple/flags");
9517
+ const runIndex = scratchWords(scope, arcs, "simple/runIndex");
9518
+ const dummy = scratchWords(scope, 1, "simple/dummy");
9519
+ await ctx.allocator.check();
9520
+ const recordEmit = (pass3, order, out) => {
9521
+ const kernel = emit.get(order !== null);
9522
+ if (kernel === void 0) {
9523
+ throw new WebGpuGraphError("E_INVALID_ARGUMENT", "buildSimpleSymmetric: no coo-emit variant", {
9524
+ argument: "order",
9525
+ value: order === null ? "null" : "present"
9526
+ });
9527
+ }
9528
+ const params2 = scope.params(COO_PARAMS, { count: arcs, pad0: 0, pad1: 0, pad2: 0 });
9529
+ const bound = kernel.bind({
9530
+ edgeSrc: edges.bindings.src,
9531
+ edgeDst: edges.bindings.dst,
9532
+ edgeWeight: edgeWeights ?? edges.bindings.src,
9533
+ order: order ?? dummy,
9534
+ outSrc: out[0],
9535
+ outDst: out[1],
9536
+ outWeight: out[2],
9537
+ P: params2.binding
9538
+ });
9539
+ kernel.dispatch(pass3, bound, emitPlan, [params2.offset]);
9540
+ };
9541
+ const recordIota = (pass3, dst, words) => {
9542
+ const params2 = scope.params(FILL_PARAMS, { count: words, value: 0, mode: 1, pad0: 0 });
9543
+ fill.dispatch(pass3, fill.bind({ dst, P: params2.binding }), plan1d(words, wg, ctx.caps), [params2.offset]);
9544
+ };
9545
+ const bits = sortBitsFor(n);
9546
+ const first = new CommandBatch(ctx, `${label}/simple-sort`);
9547
+ const pass = first.pass("simple-sort");
9548
+ recordEmit(pass, null, [aSrc, aDst, aW]);
9549
+ recordIota(pass, vals, arcs);
9550
+ const byTarget = sort.record(pass, aDst, vals, arcs, bits, { keys: sKeys, vals: sVals, hist, offsets });
9551
+ recordEmit(pass, byTarget.vals, [aSrc, aDst, aW]);
9552
+ const free = byTarget.vals.buffer === vals.buffer ? sVals : vals;
9553
+ const bySource = sort.record(pass, aSrc, byTarget.vals, arcs, bits, {
9554
+ keys: byTarget.keys.buffer === aDst.buffer ? sKeys : aDst,
9555
+ vals: free,
9556
+ hist,
9557
+ offsets
9558
+ });
9559
+ recordEmit(pass, bySource.vals, [sortedSrc, sortedDst, sortedW]);
9560
+ const params = scope.params(COO_PARAMS, { count: arcs, pad0: 0, pad1: 0, pad2: 0 });
9561
+ runFlags.dispatch(pass, runFlags.bind({ keysA: sortedSrc, keysB: sortedDst, flags, P: params.binding }), emitPlan, [
9562
+ params.offset
9563
+ ]);
9564
+ const total = scan.record(pass, flags, arcs, runIndex);
9565
+ first.endPass();
9566
+ const totalRequest = first.readback(total.binding.buffer, total.binding.offset + 4 * total.index, 4);
9567
+ scope.flush();
9568
+ const back = await first.submit().readback;
9569
+ ctx.assertReady();
9570
+ const unique = new Uint32Array(back, totalRequest.offset, 1)[0];
9571
+ if (unique === 0 || unique > valid) {
9572
+ throw new WebGpuGraphError("E_VALIDATION", `${label}: ${unique} distinct arcs from ${valid} valid ones`, {
9573
+ label: `${label}/simple-graph`,
9574
+ message: "the run count of the simple graph build is outside [1, valid arcs]"
9575
+ });
9576
+ }
9577
+ const uSrc = scratchWords(scope, arcs, "simple/src");
9578
+ const uDst = scratchWords(scope, arcs, "simple/dst");
9579
+ const runStart = scratchWords(scope, arcs + 1, "simple/runStart");
9580
+ const counts = scratchWords(scope, 1, "simple/count");
9581
+ const colIdx = scratchWords(scope, unique, "simple/colIdx");
9582
+ const weights = withWeights ? scratchWords(scope, unique, "simple/weights") : null;
9583
+ const merged = withWeights ? scratchWords(scope, unique, "simple/merged") : null;
9584
+ ctx.device.queue.writeBuffer(runStart.buffer, 4 * unique, Uint32Array.of(valid));
9585
+ const reduce = merged === null ? null : await prepareSegmentedReduce(scope, runCore(runStart, unique, sortedDst, sortedW, valid), {
9586
+ op: "sum",
9587
+ valueSnippet: "v = weight;",
9588
+ tiers: null
9589
+ });
9590
+ const second = new CommandBatch(ctx, `${label}/simple-graph`);
9591
+ const pass2 = second.pass("simple-graph");
9592
+ const positions = vals;
9593
+ recordIota(pass2, positions, arcs);
9594
+ for (const [queue, out] of [
9595
+ [sortedSrc, uSrc],
9596
+ [sortedDst, uDst],
9597
+ [positions, runStart]
9598
+ ]) {
9599
+ compact.record(pass2, { queue, flags, count: arcs, out, outCount: counts, outIndex: 0 });
9600
+ }
9601
+ if (reduce !== null && merged !== null) {
9602
+ reduce.record(pass2, runCore(runStart, unique, sortedDst, sortedW, valid), merged);
9603
+ }
9604
+ cooToCsr.record(pass2, {
9605
+ src: uSrc,
9606
+ dst: uDst,
9607
+ weights: merged,
9608
+ count: unique,
9609
+ n,
9610
+ sortedInput: true,
9611
+ out: { rowPtr, colIdx, weights, flag }
9612
+ });
9613
+ second.endPass();
9614
+ const graph = { n, arcCount: unique, rowPtr, colIdx, weights, src: uSrc };
9615
+ return { graph, batch: second, flag: second.readback(flag.buffer, 0, 4) };
9616
+ }
9617
+ function runCore(runStart, runs, sortedDst, sortedW, valid) {
9618
+ return {
9619
+ serial: -1,
9620
+ plan: "perArray",
9621
+ rowPtr: { ...runStart, size: 4 * (runs + 1) },
9622
+ colIdx: { ...sortedDst, size: 4 * valid },
9623
+ weights: { ...sortedW, size: 4 * valid },
9624
+ arcToEdge: null,
9625
+ edgeToArc: null,
9626
+ windows: null,
9627
+ arcBuffers: null,
9628
+ hasWeights: true
9629
+ };
9630
+ }
9631
+ const ALGORITHM$1 = "labelPropagation";
9632
+ const DEFAULT_MAX_ITERATIONS = 100;
9633
+ const RING_SLOTS$1 = 1024;
9634
+ function checkDest$1(dest, n) {
9635
+ if (dest === void 0) {
9636
+ return null;
9637
+ }
9638
+ if (dest instanceof Uint32Array && dest.length === n && dest.buffer instanceof ArrayBuffer) {
9639
+ return dest;
9640
+ }
9641
+ throw new WebGpuGraphError(
9642
+ "E_INVALID_ARGUMENT",
9643
+ `${ALGORITHM$1}: dest must be a Uint32Array of length ${n} over an ArrayBuffer`,
9644
+ {
9645
+ argument: "dest",
9646
+ value: `${dest.constructor.name}(${dest.length})`,
9647
+ expected: `Uint32Array(${n}) over an ArrayBuffer`
9648
+ }
9649
+ );
9650
+ }
9651
+ function maxIterationsOf(value) {
9652
+ if (value === void 0) {
9653
+ return DEFAULT_MAX_ITERATIONS;
9654
+ }
9655
+ if (!Number.isSafeInteger(value) || value < 0) {
9656
+ throw new WebGpuGraphError("E_INVALID_ARGUMENT", `${ALGORITHM$1}: maxIterations must be a non-negative integer`, {
9657
+ argument: "maxIterations",
9658
+ value,
9659
+ expected: "a non-negative integer"
9660
+ });
9661
+ }
9662
+ return value;
9663
+ }
9664
+ function neighbourBound(s) {
9665
+ const out = s.outDegree();
9666
+ if (!s.directed) {
9667
+ return out;
7457
9668
  }
7458
- if (!Number.isSafeInteger(bins) || bins < 1 || bins > U32_MAX) {
7459
- throw new WebGpuGraphError("E_INVALID_ARGUMENT", "histogram: bins must be an integer in [1, 2^32)", {
7460
- argument: "bins",
7461
- value: bins
7462
- });
9669
+ const inDegree = s.inDegree();
9670
+ const bound = new Uint32Array(s.nodeCount);
9671
+ for (let v = 0; v < bound.length; v++) {
9672
+ bound[v] = out[v] + inDegree[v];
7463
9673
  }
7464
- checkWords("keys", keys, count);
7465
- checkWords("hist", hist, bins);
9674
+ return bound;
7466
9675
  }
7467
- class HistogramPlannerImpl {
7468
- /**
7469
- * Wraps the resolved kernels; use prepareHistogram().
7470
- * @param scope - the caller's scope
7471
- * @param histogram - the `histogram` kernel
7472
- * @param fill - the `fill` kernel
7473
- */
7474
- constructor(scope, histogram, fill) {
7475
- this.dispatches = 0;
7476
- this.scope = scope;
7477
- this.histogram = histogram;
7478
- this.fill = fill;
9676
+ function resultOf(raw, dest) {
9677
+ const n = raw.length;
9678
+ for (let v = 0; v < n; v++) {
9679
+ if (raw[v] >= n) {
9680
+ throw new WebGpuGraphError("E_VALIDATION", `${ALGORITHM$1}: labels[${v}] = ${raw[v]} is not a node index`, {
9681
+ label: `${ALGORITHM$1}/labels`,
9682
+ message: `the device produced a label outside [0, ${n})`
9683
+ });
9684
+ }
7479
9685
  }
7480
- /**
7481
- * Dispatches the last record() issued.
7482
- * @returns the count
7483
- */
7484
- get lastDispatches() {
7485
- return this.dispatches;
9686
+ const { labels, count } = renumberPartition(raw, dest ?? void 0);
9687
+ return labelResult(labels, count);
9688
+ }
9689
+ function identityResult(n, dest) {
9690
+ const labels = dest ?? new Uint32Array(n);
9691
+ for (let v = 0; v < n; v++) {
9692
+ labels[v] = v;
7486
9693
  }
7487
- /**
7488
- * Records the fill and the histogram (see the interface).
7489
- * @param pass - the compute pass
7490
- * @param keys - the keys
7491
- * @param count - the key count
7492
- * @param bins - the bin count
7493
- * @param hist - the counts
7494
- */
7495
- record(pass, keys, count, bins, hist) {
7496
- checkHistogramArguments(keys, count, bins, hist);
7497
- this.recordZero(pass, hist, bins);
7498
- this.dispatches = 1;
7499
- if (count === 0) {
7500
- return;
9694
+ return labelResult(labels, n);
9695
+ }
9696
+ async function labelPropagation(ctx, s, options) {
9697
+ ctx.assertReady();
9698
+ await assertDeviceComputes(ctx);
9699
+ const n = s.nodeCount;
9700
+ const dest = checkDest$1(options?.dest, n);
9701
+ const maxIterations = maxIterationsOf(options?.maxIterations);
9702
+ const weighted = options?.weighted !== false;
9703
+ if (options?.signal?.aborted) {
9704
+ throw new WebGpuGraphError("E_ABORTED", `${ALGORITHM$1}: the signal was aborted before any work started`, {});
9705
+ }
9706
+ if (n === 0 || s.edgeCount === 0 || maxIterations === 0) {
9707
+ options?.onProgress?.(1, 1);
9708
+ return identityResult(n, dest);
9709
+ }
9710
+ const plan = planGroupRows(neighbourBound(s));
9711
+ assertBindable(ctx, 4 * plan.regionWords, "the group-by hash region", ALGORITHM$1);
9712
+ const scope = algorithmScope(ctx, ALGORITHM$1, RING_SLOTS$1);
9713
+ try {
9714
+ const build = await buildSimpleSymmetric(ctx, s, scope, weighted, ALGORITHM$1);
9715
+ const { graph } = build;
9716
+ if (graph.colIdx === null) {
9717
+ scope.flush();
9718
+ const bytes = await build.batch.submit().readback;
9719
+ ctx.assertReady();
9720
+ assertBuildSorted(bytes, build, ALGORITHM$1);
9721
+ options?.onProgress?.(1, 1);
9722
+ return identityResult(n, dest);
7501
9723
  }
7502
- const params = this.scope.params(HIST_PARAMS, { count, bins, pad0: 0, pad1: 0 });
7503
- const bound = this.histogram.bind({ keys, hist, P: params.binding });
7504
- this.histogram.dispatch(pass, bound, plan1d(count, this.scope.workgroupSize, this.scope.caps), [params.offset]);
7505
- this.dispatches = 2;
9724
+ const wg = ctx.workgroupSize;
9725
+ const words = (count, label) => {
9726
+ const size = 4 * Math.max(1, count);
9727
+ return { buffer: scope.scratch(size, label), offset: 0, size, window: null };
9728
+ };
9729
+ const rows = words(plan.words.length, "rows");
9730
+ const region = words(plan.regionWords, "hashRegion");
9731
+ const labelsA = words(n, "labelsA");
9732
+ const labelsB = words(n, "labelsB");
9733
+ const bestKey = words(n, "bestKey");
9734
+ const bestScore = words(n, "bestScore");
9735
+ const counters = words(LABEL_PROP_PASSES_PER_SUBMIT, "counters");
9736
+ await ctx.allocator.check();
9737
+ const { queue } = ctx.device;
9738
+ queue.writeBuffer(rows.buffer, 0, plan.words);
9739
+ queue.writeBuffer(region.buffer, 0, new Uint32Array(1));
9740
+ const groupBy = await prepareGroupByKeyRow(scope);
9741
+ const step = await ctx.pipelines.kernel(kernelSpec("lpa-step"));
9742
+ const fill = await ctx.pipelines.kernel(kernelSpec("fill"));
9743
+ const nodePlan = plan1d(n, wg, ctx.caps);
9744
+ let { batch } = build;
9745
+ let first = true;
9746
+ let cur = labelsA;
9747
+ let next = labelsB;
9748
+ let done = 0;
9749
+ let previousLast = -1;
9750
+ for (; ; ) {
9751
+ const k = Math.min(LABEL_PROP_PASSES_PER_SUBMIT, maxIterations - done);
9752
+ queue.writeBuffer(counters.buffer, 0, new Uint32Array(k));
9753
+ const pass = batch.pass("passes");
9754
+ if (first) {
9755
+ const iota = scope.params(FILL_PARAMS, { count: n, value: 0, mode: 1, pad0: 0 });
9756
+ fill.dispatch(pass, fill.bind({ dst: labelsA, P: iota.binding }), nodePlan, [iota.offset]);
9757
+ }
9758
+ for (let i = 0; i < k; i++) {
9759
+ groupBy.record(pass, {
9760
+ rowPtr: graph.rowPtr,
9761
+ colIdx: graph.colIdx,
9762
+ weights: weighted ? graph.weights : null,
9763
+ keyIn: cur,
9764
+ plan,
9765
+ rows,
9766
+ hashRegion: region,
9767
+ bestKey,
9768
+ bestScore
9769
+ });
9770
+ const params = scope.params(LPA_PARAMS, {
9771
+ n,
9772
+ direction: (done + i) % 2,
9773
+ counterIndex: i,
9774
+ pad0: 0
9775
+ });
9776
+ step.dispatch(
9777
+ pass,
9778
+ step.bind({ labelsIn: cur, bestKey, labelsOut: next, counters, P: params.binding }),
9779
+ nodePlan,
9780
+ [params.offset]
9781
+ );
9782
+ [cur, next] = [next, cur];
9783
+ }
9784
+ batch.endPass();
9785
+ const movesRequest = batch.readback(counters.buffer, 0, 4 * k);
9786
+ const exhaustedRequest = batch.readback(region.buffer, 0, 4);
9787
+ scope.flush();
9788
+ const submitted = batch.submit();
9789
+ const bytes = await submitted.readback;
9790
+ ctx.assertReady();
9791
+ if (first) {
9792
+ assertBuildSorted(bytes, build, ALGORITHM$1);
9793
+ first = false;
9794
+ }
9795
+ if (new Uint32Array(bytes, exhaustedRequest.offset, 1)[0] !== 0) {
9796
+ throw new WebGpuGraphError("E_VALIDATION", `${ALGORITHM$1}: a hash probe exhausted its bound`, {
9797
+ label: `${ALGORITHM$1}/group-by-key`,
9798
+ message: "a compare-exchange loop of the workgroup tier ran out of steps"
9799
+ });
9800
+ }
9801
+ const moves = new Uint32Array(bytes, movesRequest.offset, k);
9802
+ done += k;
9803
+ const lastTwo = k >= 2 ? moves[k - 2] + moves[k - 1] : previousLast + moves[0];
9804
+ previousLast = moves[k - 1];
9805
+ if (options?.signal?.aborted) {
9806
+ throw new WebGpuGraphError("E_ABORTED", `${ALGORITHM$1}: the signal was aborted`, {
9807
+ batchId: submitted.id
9808
+ });
9809
+ }
9810
+ options?.onProgress?.(done, maxIterations);
9811
+ if (lastTwo === 0 || done >= maxIterations) {
9812
+ break;
9813
+ }
9814
+ batch = new CommandBatch(ctx, `${ALGORITHM$1}/passes`);
9815
+ }
9816
+ const raw = new Uint32Array(n);
9817
+ await ctx.readback.read(cur.buffer, 4 * n, raw);
9818
+ ctx.assertReady();
9819
+ return resultOf(raw, dest);
9820
+ } finally {
9821
+ scope.dispose();
7506
9822
  }
7507
- /**
7508
- * Records a `fill` of `words` zeros into `dst` (PD-12: a dispatch inside the pass, never an encoder clear).
7509
- * @param pass - the compute pass
7510
- * @param dst - the words to zero
7511
- * @param words - how many (>= 1)
7512
- */
7513
- recordZero(pass, dst, words) {
7514
- const params = this.scope.params(FILL_PARAMS, { count: words, value: 0, mode: 0, pad0: 0 });
7515
- const bound = this.fill.bind({ dst, P: params.binding });
7516
- this.fill.dispatch(pass, bound, plan1d(words, this.scope.workgroupSize, this.scope.caps), [params.offset]);
9823
+ }
9824
+ const ALGORITHM = "triangleCount";
9825
+ const RING_SLOTS = 1024;
9826
+ function checkDest(dest, n) {
9827
+ if (dest === void 0) {
9828
+ return null;
9829
+ }
9830
+ if (dest instanceof Uint32Array && dest.length === n && dest.buffer instanceof ArrayBuffer) {
9831
+ return dest;
9832
+ }
9833
+ throw new WebGpuGraphError(
9834
+ "E_INVALID_ARGUMENT",
9835
+ `${ALGORITHM}: dest must be a Uint32Array of length ${n} over an ArrayBuffer`,
9836
+ {
9837
+ argument: "dest",
9838
+ value: `${dest.constructor.name}(${dest.length})`,
9839
+ expected: `Uint32Array(${n}) over an ArrayBuffer`
9840
+ }
9841
+ );
9842
+ }
9843
+ function epilogue(perNode, rowPtr) {
9844
+ const n = perNode.length;
9845
+ const coefficient = new Float32Array(n);
9846
+ let sum = 0;
9847
+ let triples = 0;
9848
+ for (let v = 0; v < n; v++) {
9849
+ const d = rowPtr[v + 1] - rowPtr[v];
9850
+ const t = perNode[v];
9851
+ sum += t;
9852
+ if (d >= 2) {
9853
+ const pairs = d * (d - 1) / 2;
9854
+ triples += pairs;
9855
+ coefficient[v] = t / pairs;
9856
+ }
7517
9857
  }
9858
+ return { perNode, total: sum / 3, coefficient, transitivity: triples === 0 ? 0 : sum / triples };
7518
9859
  }
7519
- class CountingSortPlannerImpl {
7520
- /**
7521
- * Wraps the resolved planners and kernel; use prepareCountingSort().
7522
- * @param scope - the caller's scope
7523
- * @param histogram - the histogram planner (also the zeroing of the cursor)
7524
- * @param scan - the scan planner
7525
- * @param scatter - the `counting-scatter` kernel
7526
- */
7527
- constructor(scope, histogram, scan, scatter) {
7528
- this.dispatches = 0;
7529
- this.scope = scope;
7530
- this.histogram = histogram;
7531
- this.scan = scan;
7532
- this.scatter = scatter;
9860
+ async function triangleCount(ctx, s, options) {
9861
+ return await triangleCountWithSearch(ctx, s, 0, options);
9862
+ }
9863
+ async function triangleCountWithSearch(ctx, s, search, options) {
9864
+ ctx.assertReady();
9865
+ await assertDeviceComputes(ctx);
9866
+ const n = s.nodeCount;
9867
+ const dest = checkDest(options?.dest, n);
9868
+ if (options?.signal?.aborted) {
9869
+ throw new WebGpuGraphError("E_ABORTED", `${ALGORITHM}: the signal was aborted before any work started`, {});
7533
9870
  }
7534
- /**
7535
- * Dispatches the last record() issued.
7536
- * @returns the count
7537
- */
7538
- get lastDispatches() {
7539
- return this.dispatches;
9871
+ if (n === 0) {
9872
+ options?.onProgress?.(1, 1);
9873
+ return epilogue(dest ?? new Uint32Array(0), new Uint32Array(1));
7540
9874
  }
7541
- /**
7542
- * Records the four stages (see the interface).
7543
- * @param pass - the compute pass
7544
- * @param keys - the keys
7545
- * @param count - the key count
7546
- * @param bins - the bin count
7547
- * @param scratch - the histogram and cursor
7548
- * @param outIndex - the sorted indices
7549
- * @param outStart - the bin starts
7550
- */
7551
- record(pass, keys, count, bins, scratch, outIndex, outStart) {
7552
- checkHistogramArguments(keys, count, bins, scratch.hist);
7553
- checkWords("cursor", scratch.cursor, bins);
7554
- checkWords("outIndex", outIndex, count);
7555
- checkWords("outStart", outStart, bins);
7556
- this.histogram.record(pass, keys, count, bins, scratch.hist);
7557
- this.scan.record(pass, scratch.hist, bins, outStart);
7558
- this.histogram.recordZero(pass, scratch.cursor, bins);
7559
- this.dispatches = this.histogram.lastDispatches + this.scan.lastDispatches + 1;
7560
- if (count === 0) {
7561
- return;
9875
+ const scope = algorithmScope(ctx, ALGORITHM, RING_SLOTS);
9876
+ try {
9877
+ const build = await buildSimpleSymmetric(ctx, s, scope, false, ALGORITHM);
9878
+ const { graph, batch } = build;
9879
+ const wg = ctx.workgroupSize;
9880
+ const countsBytes = 4 * n;
9881
+ const counts = {
9882
+ buffer: scope.scratch(countsBytes, "counts"),
9883
+ offset: 0,
9884
+ size: countsBytes,
9885
+ window: null
9886
+ };
9887
+ const fill = await ctx.pipelines.kernel(kernelSpec("fill"));
9888
+ const pass = batch.pass("triangles");
9889
+ let orientedFlag = null;
9890
+ const zero = scope.params(FILL_PARAMS, { count: n, value: 0, mode: 0, pad0: 0 });
9891
+ fill.dispatch(pass, fill.bind({ dst: counts, P: zero.binding }), plan1d(n, wg, ctx.caps), [zero.offset]);
9892
+ if (graph.arcCount > 0 && graph.colIdx !== null && graph.src !== null) {
9893
+ const oriented = graph.arcCount / 2;
9894
+ const orient = await ctx.pipelines.kernel(kernelSpec("orient-flags"));
9895
+ const intersect = await ctx.pipelines.kernel(kernelSpec("tri-intersect", { SEARCH: search }));
9896
+ const compact = await prepareCompact(scope);
9897
+ const cooToCsr = await prepareCooToCsr(scope);
9898
+ const words = (count, label) => ({
9899
+ buffer: scope.scratch(4 * count, label),
9900
+ offset: 0,
9901
+ size: 4 * count,
9902
+ window: null
9903
+ });
9904
+ const flags = words(graph.arcCount, "orient/flags");
9905
+ const oSrc = words(graph.arcCount, "orient/src");
9906
+ const oDst = words(graph.arcCount, "orient/dst");
9907
+ const oRowPtr = words(n + 1, "orient/rowPtr");
9908
+ const oColIdx = words(oriented, "orient/colIdx");
9909
+ const scratch = words(1, "orient/count");
9910
+ const oFlag = words(1, "orient/flag");
9911
+ const arcPlan = plan1d(graph.arcCount, wg, ctx.caps);
9912
+ const p1 = scope.params(COO_PARAMS, { count: graph.arcCount, pad0: 0, pad1: 0, pad2: 0 });
9913
+ orient.dispatch(
9914
+ pass,
9915
+ orient.bind({ rowPtr: graph.rowPtr, colIdx: graph.colIdx, src: graph.src, flags, P: p1.binding }),
9916
+ arcPlan,
9917
+ [p1.offset]
9918
+ );
9919
+ compact.record(pass, {
9920
+ queue: graph.src,
9921
+ flags,
9922
+ count: graph.arcCount,
9923
+ out: oSrc,
9924
+ outCount: scratch,
9925
+ outIndex: 0
9926
+ });
9927
+ compact.record(pass, {
9928
+ queue: graph.colIdx,
9929
+ flags,
9930
+ count: graph.arcCount,
9931
+ out: oDst,
9932
+ outCount: scratch,
9933
+ outIndex: 0
9934
+ });
9935
+ cooToCsr.record(pass, {
9936
+ src: oSrc,
9937
+ dst: oDst,
9938
+ weights: null,
9939
+ count: oriented,
9940
+ n,
9941
+ sortedInput: true,
9942
+ out: { rowPtr: oRowPtr, colIdx: oColIdx, weights: null, flag: oFlag }
9943
+ });
9944
+ orientedFlag = oFlag;
9945
+ const p2 = scope.params(COO_PARAMS, { count: oriented, pad0: 0, pad1: 0, pad2: 0 });
9946
+ intersect.dispatch(
9947
+ pass,
9948
+ intersect.bind({ rowPtr: oRowPtr, colIdx: oColIdx, src: oSrc, counts, P: p2.binding }),
9949
+ plan1d(oriented, wg, ctx.caps),
9950
+ [p2.offset]
9951
+ );
7562
9952
  }
7563
- const params = this.scope.params(HIST_PARAMS, { count, bins, pad0: 0, pad1: 0 });
7564
- const bound = this.scatter.bind({ keys, start: outStart, cursor: scratch.cursor, outIndex, P: params.binding });
7565
- this.scatter.dispatch(pass, bound, plan1d(count, this.scope.workgroupSize, this.scope.caps), [params.offset]);
7566
- this.dispatches += 1;
9953
+ batch.endPass();
9954
+ const countsRequest = batch.readback(counts.buffer, 0, countsBytes);
9955
+ const rowsRequest = batch.readback(graph.rowPtr.buffer, 0, 4 * (n + 1));
9956
+ const orientedRequest = orientedFlag === null ? null : batch.readback(orientedFlag.buffer, 0, 4);
9957
+ scope.flush();
9958
+ const submitted = batch.submit();
9959
+ const bytes = await submitted.readback;
9960
+ ctx.assertReady();
9961
+ if (options?.signal?.aborted) {
9962
+ throw new WebGpuGraphError("E_ABORTED", `${ALGORITHM}: the signal was aborted`, { batchId: submitted.id });
9963
+ }
9964
+ assertBuildSorted(bytes, build, ALGORITHM);
9965
+ if (orientedRequest !== null && new Uint32Array(bytes, orientedRequest.offset, 1)[0] !== 0) {
9966
+ throw new WebGpuGraphError(
9967
+ "E_VALIDATION",
9968
+ `${ALGORITHM}: the oriented arcs reached cooToCsr out of order`,
9969
+ {
9970
+ label: `${ALGORITHM}/oriented`,
9971
+ message: "the sorted-input precondition of cooToCsr failed on the device"
9972
+ }
9973
+ );
9974
+ }
9975
+ const perNode = dest ?? new Uint32Array(n);
9976
+ perNode.set(new Uint32Array(bytes, countsRequest.offset, n));
9977
+ const rowPtr = new Uint32Array(bytes, rowsRequest.offset, n + 1);
9978
+ options?.onProgress?.(1, 1);
9979
+ return epilogue(perNode, rowPtr);
9980
+ } finally {
9981
+ scope.dispose();
7567
9982
  }
7568
9983
  }
7569
9984
  function nextPow2(x) {
@@ -12412,6 +14827,15 @@ function copyBetweenness(defaults) {
12412
14827
  }
12413
14828
  return Object.freeze(copy);
12414
14829
  }
14830
+ function withBetweennessDefaults(defaults, options, nodeCount) {
14831
+ if (defaults === void 0 || options?.sources !== void 0 || options?.k !== void 0) {
14832
+ return options;
14833
+ }
14834
+ if (defaults.sources !== void 0) {
14835
+ return { ...options, sources: defaults.sources.filter((v) => v < nodeCount) };
14836
+ }
14837
+ return { ...options, k: defaults.k !== void 0 && defaults.k < nodeCount ? defaults.k : void 0 };
14838
+ }
12415
14839
  function copyAlgorithms(algorithms) {
12416
14840
  const copy = { ...algorithms };
12417
14841
  if (algorithms.betweenness !== void 0) {
@@ -12573,17 +14997,95 @@ function createAccelerator(ctx, options) {
12573
14997
  ctx.assertReady();
12574
14998
  return await bellmanFord(ctx, gs, source, o);
12575
14999
  },
15000
+ /**
15001
+ * Betweenness centrality on the device (spec 8.4): exact, or sampled through `sources` / `k` (the call's own,
15002
+ * else the accelerator's `algorithms.betweenness` defaults), the unscaled sum over the sources run.
15003
+ * `endpoints: true` is refused.
15004
+ * @param gs - the snapshot
15005
+ * @param o - the seam's `BetweennessAcceleratorOptions`
15006
+ * @returns the f32 scores with `sourcesUsed` and `sigmaOverflow`
15007
+ */
15008
+ async betweennessCentrality(gs, o) {
15009
+ ctx.assertReady();
15010
+ return await betweennessCentrality(
15011
+ ctx,
15012
+ gs,
15013
+ withBetweennessDefaults(frozen.algorithms?.betweenness, o, gs.nodeCount)
15014
+ );
15015
+ },
15016
+ /**
15017
+ * Edge betweenness on the device (spec 8.4): one score per edge, arcs summed and halved when undirected; sampling
15018
+ * and defaults as `betweennessCentrality`.
15019
+ * @param gs - the snapshot
15020
+ * @param o - the seam's `BetweennessAcceleratorOptions`
15021
+ * @returns the f32 per-edge scores with `sourcesUsed` and `sigmaOverflow`
15022
+ */
15023
+ async edgeBetweennessCentrality(gs, o) {
15024
+ ctx.assertReady();
15025
+ return await edgeBetweennessCentrality(
15026
+ ctx,
15027
+ gs,
15028
+ withBetweennessDefaults(frozen.algorithms?.betweenness, o, gs.nodeCount)
15029
+ );
15030
+ },
12576
15031
  /**
12577
15032
  * Closeness centrality on the device (spec 8.4; P8-T13): the bit-parallel multi-source sweep, or one `sssp`
12578
15033
  * per source when `weighted`. `maxIterations` / `tolerance` are refused when defined (P8 PD-25).
12579
15034
  * @param gs - the snapshot
12580
- * @param o - the seam's placeholder `HitsOptionsLike` (`weighted`)
12581
- * @returns the f32 scores with `precision: "f32"` (spec 9.7)
15035
+ * @param o - `weighted`, and a sampled run's `sources` (undirected snapshots only)
15036
+ * @returns the f32 scores with `precision: "f32"` (spec 9.7) and `sourcesUsed`
12582
15037
  */
12583
15038
  async closenessCentrality(gs, o) {
12584
15039
  ctx.assertReady();
12585
15040
  return await closenessCentrality(ctx, gs, o);
12586
15041
  },
15042
+ /**
15043
+ * All-pairs shortest paths on the device (design 8.7): blocked Floyd-Warshall, `E_TOO_LARGE` above the device's
15044
+ * storage-binding ceiling. The seam passes `SsspOptions`; neither of its keys has an all-pairs meaning, so a
15045
+ * defined `cutoff` (it would change what `+Infinity` means) or `weights` (a per-arc override is a different
15046
+ * matrix from the snapshot's resident column) is `E_UNSUPPORTED { option }`, never silently dropped.
15047
+ * @param gs - the snapshot
15048
+ * @param o - the seam's `SsspOptions`; both keys refused when defined
15049
+ * @returns the row-major `n x n` distances and `n` (spec 3.3 line 835)
15050
+ */
15051
+ async allPairsShortestPath(gs, o) {
15052
+ ctx.assertReady();
15053
+ for (const key of ["cutoff", "weights"]) {
15054
+ if (o?.[key] !== void 0) {
15055
+ throw new WebGpuGraphError("E_UNSUPPORTED", `allPairsShortestPath: ${key} is not supported`, {
15056
+ option: key,
15057
+ hint: "all-pairs shortest paths runs over the snapshot's own weights with no cutoff"
15058
+ });
15059
+ }
15060
+ }
15061
+ return await allPairsShortestPath(ctx, gs);
15062
+ },
15063
+ /**
15064
+ * Triangle counting with the clustering coefficient and the transitivity (design 8.5; P11).
15065
+ * @param gs - the snapshot
15066
+ * @returns perNode, total, coefficient and transitivity
15067
+ */
15068
+ async triangleCount(gs) {
15069
+ ctx.assertReady();
15070
+ return await triangleCount(ctx, gs);
15071
+ },
15072
+ /**
15073
+ * Label propagation (design 8.6; P11): `maxIterations` and `weighted` are honoured, `tolerance` is refused when
15074
+ * defined (a label propagation stops at a fixed point, not below a tolerance).
15075
+ * @param gs - the snapshot
15076
+ * @param o - the seam's placeholder `HitsOptionsLike`
15077
+ * @returns the labels dense in first-seen order, the community count and groups()
15078
+ */
15079
+ async labelPropagation(gs, o) {
15080
+ ctx.assertReady();
15081
+ if (o?.tolerance !== void 0) {
15082
+ throw new WebGpuGraphError("E_UNSUPPORTED", "labelPropagation: tolerance has no meaning here", {
15083
+ option: "tolerance",
15084
+ hint: "label propagation stops at a fixed point or after maxIterations passes"
15085
+ });
15086
+ }
15087
+ return await labelPropagation(ctx, gs, { maxIterations: o?.maxIterations, weighted: o?.weighted });
15088
+ },
12587
15089
  /**
12588
15090
  * Destroys every device buffer recorded for the snapshot (spec 4.5); delegates to ctx.release.
12589
15091
  * @param s - the snapshot the app is done with
@@ -12693,7 +15195,7 @@ async function calibrateLayout(ctx, options) {
12693
15195
  };
12694
15196
  }
12695
15197
  export {
12696
- J as ARC_WINDOW_ALIGN,
15198
+ a0 as ARC_WINDOW_ALIGN,
12697
15199
  EXACT_MAX_NODES,
12698
15200
  FA2_DEFAULTS,
12699
15201
  FR_DEFAULTS,
@@ -12701,12 +15203,14 @@ export {
12701
15203
  LAYOUT_TUNING_DEFAULTS,
12702
15204
  MAX_1D_ITEMS,
12703
15205
  MAX_WORKGROUPS_PER_DIM,
12704
- K as PASSTHROUGH_FORMAT_CODES,
15206
+ a1 as PASSTHROUGH_FORMAT_CODES,
12705
15207
  SE_DEFAULTS,
12706
- N as STORAGE_ALIGN,
12707
- O as WORKGROUP_SIZE,
15208
+ a2 as STORAGE_ALIGN,
15209
+ a3 as WORKGROUP_SIZE,
12708
15210
  WebGpuGraphError,
15211
+ allPairsShortestPath,
12709
15212
  bellmanFord,
15213
+ betweennessCentrality,
12710
15214
  breadthFirstSearch,
12711
15215
  calibrateLayout,
12712
15216
  closenessCentrality,
@@ -12716,16 +15220,19 @@ export {
12716
15220
  createFruchtermanReingold,
12717
15221
  createSpringElectrical,
12718
15222
  degree,
15223
+ edgeBetweennessCentrality,
12719
15224
  eigenvectorCentrality,
12720
15225
  hasErrorCode,
12721
15226
  hits,
12722
- Q as isSoftwareAdapter,
15227
+ a4 as isSoftwareAdapter,
12723
15228
  isWebGpuGraphError,
12724
15229
  katzCentrality,
15230
+ labelPropagation,
12725
15231
  pageRank,
12726
15232
  personalizedPageRank,
12727
15233
  seedPositions,
12728
15234
  sssp,
15235
+ triangleCount,
12729
15236
  verifyDevice
12730
15237
  };
12731
15238
  //# sourceMappingURL=webgpu-graph-algorithms.js.map