@graphty/webgpu-graph-algorithms 0.2.1 → 0.3.0

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 (115) hide show
  1. package/README.md +45 -36
  2. package/dist/browser.js +1 -1
  3. package/dist/chunks/{context-E6iKaeuJ.js → context-CRbw2Wyo.js} +178 -19
  4. package/dist/chunks/{context-E6iKaeuJ.js.map → context-CRbw2Wyo.js.map} +1 -1
  5. package/dist/node.js +1 -1
  6. package/dist/src/accelerator.d.ts +9 -6
  7. package/dist/src/accelerator.d.ts.map +1 -1
  8. package/dist/src/accelerator.js +85 -6
  9. package/dist/src/accelerator.js.map +1 -1
  10. package/dist/src/algorithms/components.d.ts +30 -0
  11. package/dist/src/algorithms/components.d.ts.map +1 -0
  12. package/dist/src/algorithms/components.js +300 -0
  13. package/dist/src/algorithms/components.js.map +1 -0
  14. package/dist/src/algorithms/pagerank.d.ts +39 -0
  15. package/dist/src/algorithms/pagerank.d.ts.map +1 -0
  16. package/dist/src/algorithms/pagerank.js +298 -0
  17. package/dist/src/algorithms/pagerank.js.map +1 -0
  18. package/dist/src/algorithms/power-iteration.d.ts +109 -0
  19. package/dist/src/algorithms/power-iteration.d.ts.map +1 -0
  20. package/dist/src/algorithms/power-iteration.js +206 -0
  21. package/dist/src/algorithms/power-iteration.js.map +1 -0
  22. package/dist/src/algorithms/scope.d.ts +26 -0
  23. package/dist/src/algorithms/scope.d.ts.map +1 -0
  24. package/dist/src/algorithms/scope.js +41 -0
  25. package/dist/src/algorithms/scope.js.map +1 -0
  26. package/dist/src/algorithms/spectral.d.ts +50 -0
  27. package/dist/src/algorithms/spectral.d.ts.map +1 -0
  28. package/dist/src/algorithms/spectral.js +247 -0
  29. package/dist/src/algorithms/spectral.js.map +1 -0
  30. package/dist/src/index.d.ts +4 -0
  31. package/dist/src/index.d.ts.map +1 -1
  32. package/dist/src/index.js +4 -0
  33. package/dist/src/index.js.map +1 -1
  34. package/dist/src/kernel/dispatch.d.ts +4 -1
  35. package/dist/src/kernel/dispatch.d.ts.map +1 -1
  36. package/dist/src/kernel/dispatch.js +12 -5
  37. package/dist/src/kernel/dispatch.js.map +1 -1
  38. package/dist/src/kernels.d.ts +20 -4
  39. package/dist/src/kernels.d.ts.map +1 -1
  40. package/dist/src/kernels.js +172 -2
  41. package/dist/src/kernels.js.map +1 -1
  42. package/dist/src/memory/residency.d.ts.map +1 -1
  43. package/dist/src/memory/residency.js +164 -11
  44. package/dist/src/memory/residency.js.map +1 -1
  45. package/dist/src/primitives/core-shape.d.ts +41 -0
  46. package/dist/src/primitives/core-shape.d.ts.map +1 -0
  47. package/dist/src/primitives/core-shape.js +89 -0
  48. package/dist/src/primitives/core-shape.js.map +1 -0
  49. package/dist/src/primitives/segmented-reduce.d.ts.map +1 -1
  50. package/dist/src/primitives/segmented-reduce.js +4 -30
  51. package/dist/src/primitives/segmented-reduce.js.map +1 -1
  52. package/dist/src/primitives/spmv.d.ts +56 -0
  53. package/dist/src/primitives/spmv.d.ts.map +1 -0
  54. package/dist/src/primitives/spmv.js +101 -0
  55. package/dist/src/primitives/spmv.js.map +1 -0
  56. package/dist/src/types/accelerator.d.ts +14 -2
  57. package/dist/src/types/accelerator.d.ts.map +1 -1
  58. package/dist/src/types/algorithms.d.ts +73 -0
  59. package/dist/src/types/algorithms.d.ts.map +1 -0
  60. package/dist/src/types/algorithms.js +17 -0
  61. package/dist/src/types/algorithms.js.map +1 -0
  62. package/dist/src/wgsl/pr-finalize.wgsl.d.ts +11 -0
  63. package/dist/src/wgsl/pr-finalize.wgsl.d.ts.map +1 -0
  64. package/dist/src/wgsl/pr-finalize.wgsl.js +36 -0
  65. package/dist/src/wgsl/pr-finalize.wgsl.js.map +1 -0
  66. package/dist/src/wgsl/pr-scale.wgsl.d.ts +14 -0
  67. package/dist/src/wgsl/pr-scale.wgsl.d.ts.map +1 -0
  68. package/dist/src/wgsl/pr-scale.wgsl.js +48 -0
  69. package/dist/src/wgsl/pr-scale.wgsl.js.map +1 -0
  70. package/dist/src/wgsl/spmv-pull.wgsl.d.ts +15 -0
  71. package/dist/src/wgsl/spmv-pull.wgsl.d.ts.map +1 -0
  72. package/dist/src/wgsl/spmv-pull.wgsl.js +47 -0
  73. package/dist/src/wgsl/spmv-pull.wgsl.js.map +1 -0
  74. package/dist/src/wgsl/wcc-compress.wgsl.d.ts +9 -0
  75. package/dist/src/wgsl/wcc-compress.wgsl.d.ts.map +1 -0
  76. package/dist/src/wgsl/wcc-compress.wgsl.js +26 -0
  77. package/dist/src/wgsl/wcc-compress.wgsl.js.map +1 -0
  78. package/dist/src/wgsl/wcc-link-edges.wgsl.d.ts +13 -0
  79. package/dist/src/wgsl/wcc-link-edges.wgsl.d.ts.map +1 -0
  80. package/dist/src/wgsl/wcc-link-edges.wgsl.js +46 -0
  81. package/dist/src/wgsl/wcc-link-edges.wgsl.js.map +1 -0
  82. package/dist/src/wgsl/wcc-link-sample.wgsl.d.ts +11 -0
  83. package/dist/src/wgsl/wcc-link-sample.wgsl.d.ts.map +1 -0
  84. package/dist/src/wgsl/wcc-link-sample.wgsl.js +45 -0
  85. package/dist/src/wgsl/wcc-link-sample.wgsl.js.map +1 -0
  86. package/dist/src/wgsl/wcc-sample.wgsl.d.ts +10 -0
  87. package/dist/src/wgsl/wcc-sample.wgsl.d.ts.map +1 -0
  88. package/dist/src/wgsl/wcc-sample.wgsl.js +18 -0
  89. package/dist/src/wgsl/wcc-sample.wgsl.js.map +1 -0
  90. package/dist/tsconfig.build.tsbuildinfo +1 -1
  91. package/dist/webgpu-graph-algorithms.js +1550 -29
  92. package/dist/webgpu-graph-algorithms.js.map +1 -1
  93. package/package.json +1 -1
  94. package/src/accelerator.ts +101 -7
  95. package/src/algorithms/components.ts +348 -0
  96. package/src/algorithms/pagerank.ts +343 -0
  97. package/src/algorithms/power-iteration.ts +278 -0
  98. package/src/algorithms/scope.ts +52 -0
  99. package/src/algorithms/spectral.ts +300 -0
  100. package/src/index.ts +18 -0
  101. package/src/kernel/dispatch.ts +12 -5
  102. package/src/kernels.ts +206 -5
  103. package/src/memory/residency.ts +200 -11
  104. package/src/primitives/core-shape.ts +103 -0
  105. package/src/primitives/segmented-reduce.ts +4 -36
  106. package/src/primitives/spmv.ts +155 -0
  107. package/src/types/accelerator.ts +28 -2
  108. package/src/types/algorithms.ts +82 -0
  109. package/src/wgsl/pr-finalize.wgsl.ts +36 -0
  110. package/src/wgsl/pr-scale.wgsl.ts +48 -0
  111. package/src/wgsl/spmv-pull.wgsl.ts +47 -0
  112. package/src/wgsl/wcc-compress.wgsl.ts +26 -0
  113. package/src/wgsl/wcc-link-edges.wgsl.ts +46 -0
  114. package/src/wgsl/wcc-link-sample.wgsl.ts +45 -0
  115. package/src/wgsl/wcc-sample.wgsl.ts +18 -0
@@ -1,6 +1,6 @@
1
- import { W as WebGpuGraphError, M as MAX_WORKGROUPS_PER_DIM, a as WGSL_RESERVED_WORDS, S as STATE_HEADER_BYTES, B as BufferUsage, i as isWebGpuGraphError, d as deviceLostError, U as UNIFORM_SLOT_BYTES, F as FA2_DEFAULTS, b as MAX_ITERATIONS_PER_STEP, c as MAX_1D_ITEMS, h as hasErrorCode, e as FA2_FLAG_FIRST, P as PARTIAL_BYTES, L as LAYOUT_TUNING_DEFAULTS, T as TRACE_RECORD_BYTES } from "./chunks/context-E6iKaeuJ.js";
2
- import { A, E, G, f, g, j, k } from "./chunks/context-E6iKaeuJ.js";
3
- import { makeMask, maskTest, expandEdges } from "@graphty/graph-format";
1
+ import { W as WebGpuGraphError, M as MAX_WORKGROUPS_PER_DIM, a as WGSL_RESERVED_WORDS, S as STATE_HEADER_BYTES, B as BufferUsage, i as isWebGpuGraphError, d as deviceLostError, U as UNIFORM_SLOT_BYTES, b as U32_MAX, F as FA2_DEFAULTS, c as MAX_ITERATIONS_PER_STEP, e as MAX_1D_ITEMS, h as hasErrorCode, f as FA2_FLAG_FIRST, P as PARTIAL_BYTES, L as LAYOUT_TUNING_DEFAULTS, T as TRACE_RECORD_BYTES } from "./chunks/context-CRbw2Wyo.js";
2
+ import { A, E, G, g, j, k, l } from "./chunks/context-CRbw2Wyo.js";
3
+ import { renumberPartition, makeMask, maskTest, expandEdges } from "@graphty/graph-format";
4
4
  function perDimension(caps) {
5
5
  return Math.min(MAX_WORKGROUPS_PER_DIM, caps.limits.maxComputeWorkgroupsPerDimension);
6
6
  }
@@ -55,6 +55,19 @@ function plan1d(items, wg, caps) {
55
55
  assertWorkgroupSize(wg);
56
56
  return grid(Math.ceil(items / wg), items, caps);
57
57
  }
58
+ function planGridStride(items, wg, caps, maxGroups) {
59
+ assertCount("items", items);
60
+ assertWorkgroupSize(wg);
61
+ if (items === 0) {
62
+ return { x: 0, y: 1, z: 1, items, stride: null };
63
+ }
64
+ const cap = Math.min(caps.software ? 64 : 4096, perDimension(caps));
65
+ const groups = Math.min(Math.ceil(items / wg), cap);
66
+ return { x: groups, y: 1, z: 1, items, stride: groups * wg };
67
+ }
68
+ function groupsOf(plan) {
69
+ return plan.x * plan.y;
70
+ }
58
71
  const TYPE_INFO = Object.freeze({
59
72
  u32: { size: 4, align: 4, width: 1, scalar: "u32" },
60
73
  i32: { size: 4, align: 4, width: 1, scalar: "i32" },
@@ -247,7 +260,7 @@ ${lines.join("\n")}
247
260
  throw new WebGpuGraphError("E_INVALID_ARGUMENT", `UniformBlock ${this.name}: unknown field "${key}"`, {
248
261
  argument: "values",
249
262
  value: key,
250
- expected: this.laidOut.map((f2) => f2.name).join(", ")
263
+ expected: this.laidOut.map((f) => f.name).join(", ")
251
264
  });
252
265
  }
253
266
  }
@@ -337,7 +350,7 @@ ${lines.join("\n")}
337
350
  throw new WebGpuGraphError("E_INVALID_ARGUMENT", `UniformBlock ${this.name}: unknown field "${field}"`, {
338
351
  argument: "field",
339
352
  value: field,
340
- expected: this.laidOut.map((f2) => f2.name).join(", ")
353
+ expected: this.laidOut.map((f) => f.name).join(", ")
341
354
  });
342
355
  }
343
356
  return layout;
@@ -674,6 +687,73 @@ fn fill(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid
674
687
  }
675
688
  `
676
689
  );
690
+ const prFinalizeWgsl = (
691
+ /* wgsl */
692
+ `
693
+ @compute @workgroup_size(WG)
694
+ fn pr_finalize(@builtin(local_invocation_id) lid: vec3<u32>) {
695
+ var d = 0.0;
696
+ var e = 0.0;
697
+ var m = 0.0;
698
+ for (var g = lid.x; g < P.groups; g = g + WG) {
699
+ d = d + partials[1u + g].danglingMass;
700
+ e = e + partials[1u + g].delta;
701
+ m = m + partials[1u + g].norm;
702
+ }
703
+ let folded = wg_reduce_vec4(vec4f(d, e, m, 0.0), lid.x, 0u);
704
+ if (lid.x == 0u) {
705
+ partials[0].danglingMass = folded.x;
706
+ partials[0].delta = folded.y;
707
+ var norm = folded.z;
708
+ if (NORM_MODE == 2u) { norm = sqrt(max(0.0, folded.z)); }
709
+ partials[0].norm = norm;
710
+ partials[0].iteration = P.iteration;
711
+ let unset = partials[0].firstConverged == U32_MAX;
712
+ if (P.trackConvergence == 1u && P.iteration >= 2u && folded.y < P.convergeThreshold && unset) {
713
+ partials[0].firstConverged = P.iteration - 1u;
714
+ }
715
+ }
716
+ }
717
+ `
718
+ );
719
+ const prScaleWgsl = (
720
+ /* wgsl */
721
+ `
722
+ @compute @workgroup_size(WG)
723
+ fn pr_scale(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
724
+ let u = linear_id(wid, lid.x);
725
+ let inRange = u < P.n;
726
+ var x = 0.0;
727
+ var prev = 0.0;
728
+ if (inRange) { x = rankIn[u]; prev = rankPrev[u]; }
729
+ var dangling = 0.0;
730
+ var delta = 0.0;
731
+ var normTerm = 0.0;
732
+ if (inRange) {
733
+ delta = abs(x - prev);
734
+ if (NORM_MODE == 0u) {
735
+ let divisor = outWeightSum[u];
736
+ if (divisor <= 0.0) { dangling = x; xNorm[u] = 0.0; } else { xNorm[u] = x / divisor; }
737
+ }
738
+ if (NORM_MODE == 1u) { normTerm = abs(x); }
739
+ if (NORM_MODE == 2u) { normTerm = x * x; }
740
+ if (NORM_MODE == 3u) {
741
+ var scale = partials[0].norm;
742
+ if (scale <= 0.0) { scale = 1.0; }
743
+ xNorm[u] = x / scale;
744
+ }
745
+ if (NORM_MODE == 4u) { xNorm[u] = x; }
746
+ }
747
+ let folded = wg_reduce_vec4(vec4f(dangling, delta, normTerm, 0.0), lid.x, 0u);
748
+ if (lid.x == 0u) {
749
+ let slot = 1u + group_id(wid);
750
+ partials[slot].danglingMass = folded.x;
751
+ partials[slot].delta = folded.y;
752
+ partials[slot].norm = folded.z;
753
+ }
754
+ }
755
+ `
756
+ );
677
757
  const reduceWgsl = (
678
758
  /* wgsl */
679
759
  `
@@ -757,6 +837,150 @@ fn segmented_reduce(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invoca
757
837
  }
758
838
  `
759
839
  );
840
+ const spmvPullWgsl = (
841
+ /* wgsl */
842
+ `
843
+ @compute @workgroup_size(WG)
844
+ fn spmv_pull(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
845
+ var dangling = 0.0;
846
+ if (USE_DANGLING) { dangling = partials[0].danglingMass; }
847
+ let first = linear_id(wid, lid.x);
848
+ for (var row = first; row < P.n; row = row + P.stride) {
849
+ let v = select(row, perm[row], USE_PERM);
850
+ let a0 = max(rowPtr[v], P.arcBase);
851
+ let a1 = min(rowPtr[v + 1u], P.arcEnd);
852
+ var acc = 0.0;
853
+ var chunk = 0.0;
854
+ var inChunk = 0u;
855
+ for (var arc = a0; arc < a1; arc = arc + 1u) {
856
+ let nbr = colIdx[arc - P.arcBase]; // \`target\` is a WGSL reserved word (spec 16.2)
857
+ var weight = 1.0;
858
+ if (HAS_WEIGHTS) { weight = weights[arc - P.arcBase]; }
859
+ // two-level sum: 64 terms into chunk, chunk into acc (see the header; no compensation, no select)
860
+ chunk = chunk + (weight * xNorm[nbr]);
861
+ inChunk = inChunk + 1u;
862
+ if (inChunk == 64u) {
863
+ acc = acc + chunk;
864
+ chunk = 0.0;
865
+ inChunk = 0u;
866
+ }
867
+ }
868
+ acc = acc + chunk;
869
+ var pv = P.uniformP;
870
+ if (HAS_PERSONALIZATION) { pv = personalization[v]; }
871
+ rankOut[v] = (P.beta * pv) + (P.alpha * (acc + (dangling * pv)));
872
+ }
873
+ }
874
+ `
875
+ );
876
+ const wccCompressWgsl = (
877
+ /* wgsl */
878
+ `
879
+ @compute @workgroup_size(WG)
880
+ fn wcc_compress(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
881
+ let first = linear_id(wid, lid.x);
882
+ for (var v = first; v < P.items; v = v + P.stride) {
883
+ var root = atomicLoad(&comp[v]);
884
+ var steps = 0u;
885
+ loop {
886
+ let parent = atomicLoad(&comp[root]);
887
+ if (parent == root) { break; }
888
+ if (steps >= P.maxSteps) { break; }
889
+ steps = steps + 1u;
890
+ root = parent;
891
+ }
892
+ atomicStore(&comp[v], root);
893
+ }
894
+ }
895
+ `
896
+ );
897
+ const wccLinkEdgesWgsl = (
898
+ /* wgsl */
899
+ `
900
+ fn link_pair(a: u32, b: u32) {
901
+ var p1 = atomicLoad(&comp[a]);
902
+ var p2 = atomicLoad(&comp[b]);
903
+ var steps = 0u;
904
+ loop {
905
+ if (p1 == p2) { break; }
906
+ if (steps >= P.maxSteps) { atomicStore(&comp[P.flagIndex], 1u); break; }
907
+ steps = steps + 1u;
908
+ let hi = max(p1, p2);
909
+ let lo = min(p1, p2);
910
+ let pHigh = atomicLoad(&comp[hi]);
911
+ if (pHigh == lo) { break; }
912
+ if (pHigh == hi) {
913
+ let swapped = atomicCompareExchangeWeak(&comp[hi], hi, lo);
914
+ if (swapped.exchanged) { atomicStore(&comp[P.flagIndex], 1u); break; }
915
+ }
916
+ p1 = atomicLoad(&comp[atomicLoad(&comp[hi])]);
917
+ p2 = atomicLoad(&comp[lo]);
918
+ }
919
+ }
920
+
921
+ @compute @workgroup_size(WG)
922
+ fn wcc_link_edges(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
923
+ let first = linear_id(wid, lid.x);
924
+ for (var e = first; e < P.items; e = e + P.stride) {
925
+ let u = edgeSrc[e];
926
+ let v = edgeDst[e];
927
+ if (u == v) { continue; }
928
+ if (atomicLoad(&comp[u]) == P.giant && atomicLoad(&comp[v]) == P.giant) { continue; }
929
+ link_pair(u, v);
930
+ }
931
+ }
932
+ `
933
+ );
934
+ const wccLinkSampleWgsl = (
935
+ /* wgsl */
936
+ `
937
+ fn link_pair(a: u32, b: u32) {
938
+ var p1 = atomicLoad(&comp[a]);
939
+ var p2 = atomicLoad(&comp[b]);
940
+ var steps = 0u;
941
+ loop {
942
+ if (p1 == p2) { break; }
943
+ if (steps >= P.maxSteps) { atomicStore(&comp[P.flagIndex], 1u); break; }
944
+ steps = steps + 1u;
945
+ let hi = max(p1, p2);
946
+ let lo = min(p1, p2);
947
+ let pHigh = atomicLoad(&comp[hi]);
948
+ if (pHigh == lo) { break; }
949
+ if (pHigh == hi) {
950
+ let swapped = atomicCompareExchangeWeak(&comp[hi], hi, lo);
951
+ if (swapped.exchanged) { atomicStore(&comp[P.flagIndex], 1u); break; }
952
+ }
953
+ p1 = atomicLoad(&comp[atomicLoad(&comp[hi])]);
954
+ p2 = atomicLoad(&comp[lo]);
955
+ }
956
+ }
957
+
958
+ @compute @workgroup_size(WG)
959
+ fn wcc_link_sample(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
960
+ let first = linear_id(wid, lid.x);
961
+ for (var row = first; row < P.items; row = row + P.stride) {
962
+ let v = select(row, perm[row], USE_PERM);
963
+ let a0 = rowPtr[v];
964
+ let a1 = rowPtr[v + 1u];
965
+ if (a0 + P.r < a1) {
966
+ link_pair(v, colIdx[a0 + P.r]);
967
+ }
968
+ }
969
+ }
970
+ `
971
+ );
972
+ const wccSampleWgsl = (
973
+ /* wgsl */
974
+ `
975
+ @compute @workgroup_size(WG)
976
+ fn wcc_sample(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
977
+ let i = linear_id(wid, lid.x);
978
+ if (i >= P.items) { return; }
979
+ let v = lowbias32(i + P.r) % P.n;
980
+ hist[i] = atomicLoad(&comp[v]);
981
+ }
982
+ `
983
+ );
760
984
  const RANGE_PARAMS = UniformBlock.define("RangeParams", [
761
985
  ["start", "u32"],
762
986
  ["end", "u32"],
@@ -855,6 +1079,50 @@ const FA2_PARTIAL = UniformBlock.define(
855
1079
  ],
856
1080
  { layout: "storage" }
857
1081
  );
1082
+ const SPMV_PARAMS = UniformBlock.define("SpmvParams", [
1083
+ ["n", "u32"],
1084
+ ["arcBase", "u32"],
1085
+ ["arcEnd", "u32"],
1086
+ ["stride", "u32"],
1087
+ ["alpha", "f32"],
1088
+ ["beta", "f32"],
1089
+ ["uniformP", "f32"],
1090
+ ["pad0", "u32"]
1091
+ ]);
1092
+ const PR_PARAMS = UniformBlock.define("PrParams", [
1093
+ ["n", "u32"],
1094
+ ["groups", "u32"],
1095
+ ["iteration", "u32"],
1096
+ ["trackConvergence", "u32"],
1097
+ ["convergeThreshold", "f32"],
1098
+ ["pad0", "u32"],
1099
+ ["pad1", "u32"],
1100
+ ["pad2", "u32"]
1101
+ ]);
1102
+ const PR_PARTIAL = UniformBlock.define(
1103
+ "PrPartial",
1104
+ [
1105
+ ["danglingMass", "f32"],
1106
+ ["delta", "f32"],
1107
+ ["firstConverged", "u32"],
1108
+ ["iteration", "u32"],
1109
+ ["norm", "f32"],
1110
+ ["pad0", "f32"],
1111
+ ["pad1", "f32"],
1112
+ ["pad2", "f32"]
1113
+ ],
1114
+ { layout: "storage" }
1115
+ );
1116
+ const WCC_PARAMS = UniformBlock.define("WccParams", [
1117
+ ["n", "u32"],
1118
+ ["items", "u32"],
1119
+ ["stride", "u32"],
1120
+ ["r", "u32"],
1121
+ ["flagIndex", "u32"],
1122
+ ["giant", "u32"],
1123
+ ["maxSteps", "u32"],
1124
+ ["pad0", "u32"]
1125
+ ]);
858
1126
  function decl(group, binding, name, kind, wgslType) {
859
1127
  return { group, binding, name, kind, wgslType };
860
1128
  }
@@ -1027,6 +1295,111 @@ const FA2_TO_SCENE = {
1027
1295
  snippetSlots: [],
1028
1296
  phase: "P3"
1029
1297
  };
1298
+ const SPMV_PULL = {
1299
+ id: "spmv-pull",
1300
+ body: spmvPullWgsl,
1301
+ entryPoint: "spmv_pull",
1302
+ bindings: GRAPH_SLOTS.concat(
1303
+ decl(1, 0, "xNorm", "storage-ro", "array<f32>"),
1304
+ decl(1, 1, "rankOut", "storage", "array<f32>"),
1305
+ decl(1, 2, "personalization", "storage-ro", "array<f32>"),
1306
+ decl(1, 3, "partials", "storage-ro", "array<PrPartial>"),
1307
+ decl(2, 0, "P", "uniform", "SpmvParams")
1308
+ ),
1309
+ overrideDecls: [
1310
+ { name: "HAS_PERSONALIZATION", type: "bool", default: false },
1311
+ { name: "USE_DANGLING", type: "bool", default: false }
1312
+ ],
1313
+ uniforms: [SPMV_PARAMS, PR_PARTIAL],
1314
+ needs: [],
1315
+ snippetSlots: [],
1316
+ phase: "P7"
1317
+ };
1318
+ const PR_SCALE = {
1319
+ id: "pr-scale",
1320
+ body: prScaleWgsl,
1321
+ entryPoint: "pr_scale",
1322
+ bindings: [
1323
+ decl(1, 0, "rankIn", "storage-ro", "array<f32>"),
1324
+ decl(1, 1, "rankPrev", "storage-ro", "array<f32>"),
1325
+ decl(1, 2, "outWeightSum", "storage-ro", "array<f32>"),
1326
+ decl(1, 3, "xNorm", "storage", "array<f32>"),
1327
+ decl(1, 4, "partials", "storage", "array<PrPartial>"),
1328
+ decl(2, 0, "P", "uniform", "PrParams")
1329
+ ],
1330
+ overrideDecls: [{ name: "NORM_MODE", type: "u32", default: 0 }],
1331
+ uniforms: [PR_PARAMS, PR_PARTIAL],
1332
+ needs: ["subgroups"],
1333
+ snippetSlots: [],
1334
+ phase: "P7"
1335
+ };
1336
+ const PR_FINALIZE = {
1337
+ id: "pr-finalize",
1338
+ body: prFinalizeWgsl,
1339
+ entryPoint: "pr_finalize",
1340
+ bindings: [decl(1, 0, "partials", "storage", "array<PrPartial>"), decl(2, 0, "P", "uniform", "PrParams")],
1341
+ overrideDecls: [{ name: "NORM_MODE", type: "u32", default: 0 }],
1342
+ uniforms: [PR_PARAMS, PR_PARTIAL],
1343
+ needs: ["subgroups"],
1344
+ snippetSlots: [],
1345
+ phase: "P7"
1346
+ };
1347
+ const WCC_LINK_SAMPLE = {
1348
+ id: "wcc-link-sample",
1349
+ body: wccLinkSampleWgsl,
1350
+ entryPoint: "wcc_link_sample",
1351
+ bindings: GRAPH_SLOTS.concat(
1352
+ decl(1, 0, "comp", "storage", "array<atomic<u32>>"),
1353
+ decl(2, 0, "P", "uniform", "WccParams")
1354
+ ),
1355
+ overrideDecls: [],
1356
+ uniforms: [WCC_PARAMS],
1357
+ needs: [],
1358
+ snippetSlots: [],
1359
+ phase: "P7"
1360
+ };
1361
+ const WCC_LINK_EDGES = {
1362
+ id: "wcc-link-edges",
1363
+ body: wccLinkEdgesWgsl,
1364
+ entryPoint: "wcc_link_edges",
1365
+ bindings: [
1366
+ decl(1, 0, "edgeSrc", "storage-ro", "array<u32>"),
1367
+ decl(1, 1, "edgeDst", "storage-ro", "array<u32>"),
1368
+ decl(1, 2, "comp", "storage", "array<atomic<u32>>"),
1369
+ decl(2, 0, "P", "uniform", "WccParams")
1370
+ ],
1371
+ overrideDecls: [],
1372
+ uniforms: [WCC_PARAMS],
1373
+ needs: [],
1374
+ snippetSlots: [],
1375
+ phase: "P7"
1376
+ };
1377
+ const WCC_COMPRESS = {
1378
+ id: "wcc-compress",
1379
+ body: wccCompressWgsl,
1380
+ entryPoint: "wcc_compress",
1381
+ bindings: [decl(1, 0, "comp", "storage", "array<atomic<u32>>"), decl(2, 0, "P", "uniform", "WccParams")],
1382
+ overrideDecls: [],
1383
+ uniforms: [WCC_PARAMS],
1384
+ needs: [],
1385
+ snippetSlots: [],
1386
+ phase: "P7"
1387
+ };
1388
+ const WCC_SAMPLE = {
1389
+ id: "wcc-sample",
1390
+ body: wccSampleWgsl,
1391
+ entryPoint: "wcc_sample",
1392
+ bindings: [
1393
+ decl(1, 0, "comp", "storage", "array<atomic<u32>>"),
1394
+ decl(1, 1, "hist", "storage", "array<u32>"),
1395
+ decl(2, 0, "P", "uniform", "WccParams")
1396
+ ],
1397
+ overrideDecls: [],
1398
+ uniforms: [WCC_PARAMS],
1399
+ needs: [],
1400
+ snippetSlots: [],
1401
+ phase: "P7"
1402
+ };
1030
1403
  const REGISTRY = Object.freeze({
1031
1404
  degree: DEGREE,
1032
1405
  reduce: REDUCE,
@@ -1037,7 +1410,14 @@ const REGISTRY = Object.freeze({
1037
1410
  "fa2-repulsion-exact": FA2_REPULSION_EXACT,
1038
1411
  "fa2-speed-finalize": FA2_SPEED_FINALIZE,
1039
1412
  "fa2-integrate": FA2_INTEGRATE,
1040
- "fa2-to-scene": FA2_TO_SCENE
1413
+ "fa2-to-scene": FA2_TO_SCENE,
1414
+ "spmv-pull": SPMV_PULL,
1415
+ "pr-scale": PR_SCALE,
1416
+ "pr-finalize": PR_FINALIZE,
1417
+ "wcc-link-sample": WCC_LINK_SAMPLE,
1418
+ "wcc-link-edges": WCC_LINK_EDGES,
1419
+ "wcc-compress": WCC_COMPRESS,
1420
+ "wcc-sample": WCC_SAMPLE
1041
1421
  });
1042
1422
  const bodyOverrides = /* @__PURE__ */ new Map();
1043
1423
  function entryOf(id) {
@@ -1078,7 +1458,7 @@ function graphOverrides(core, perm, weights) {
1078
1458
  const resolved = weights === void 0 ? core.weights : weights;
1079
1459
  return { USE_PERM: perm !== null, HAS_WEIGHTS: resolved !== null };
1080
1460
  }
1081
- function checkDest(dest, n) {
1461
+ function checkDest$3(dest, n) {
1082
1462
  if (dest === void 0) {
1083
1463
  return null;
1084
1464
  }
@@ -1095,7 +1475,7 @@ function checkDest(dest, n) {
1095
1475
  }
1096
1476
  );
1097
1477
  }
1098
- function coreOf(ctx, s) {
1478
+ function coreOf$3(ctx, s) {
1099
1479
  try {
1100
1480
  return ctx.residency.core(s);
1101
1481
  } catch (error) {
@@ -1112,7 +1492,7 @@ function coreOf(ctx, s) {
1112
1492
  async function degree(ctx, s, options) {
1113
1493
  ctx.assertReady();
1114
1494
  const n = s.nodeCount;
1115
- const dest = checkDest(options?.dest, n);
1495
+ const dest = checkDest$3(options?.dest, n);
1116
1496
  if (options?.signal?.aborted) {
1117
1497
  throw new WebGpuGraphError("E_ABORTED", "degree: the signal was aborted before any work started", {});
1118
1498
  }
@@ -1120,7 +1500,7 @@ async function degree(ctx, s, options) {
1120
1500
  options?.onProgress?.(1, 1);
1121
1501
  return dest ?? new Uint32Array(0);
1122
1502
  }
1123
- const core = coreOf(ctx, s);
1503
+ const core = coreOf$3(ctx, s);
1124
1504
  if (s.arcCount === 0) {
1125
1505
  const zeros = dest ?? new Uint32Array(n);
1126
1506
  zeros.fill(0);
@@ -1165,8 +1545,8 @@ async function degree(ctx, s, options) {
1165
1545
  const LOSS_GRACE_MS = 2e3;
1166
1546
  async function settled(pending, graceMs) {
1167
1547
  let timer;
1168
- const grace = new Promise((resolve) => {
1169
- timer = setTimeout(resolve, graceMs);
1548
+ const grace = new Promise((resolve2) => {
1549
+ timer = setTimeout(resolve2, graceMs);
1170
1550
  });
1171
1551
  await Promise.race([
1172
1552
  pending.then(
@@ -1393,9 +1773,9 @@ class CommandBatch {
1393
1773
  }
1394
1774
  }
1395
1775
  let timer;
1396
- const grace = new Promise((resolve) => {
1776
+ const grace = new Promise((resolve2) => {
1397
1777
  timer = setTimeout(() => {
1398
- resolve(null);
1778
+ resolve2(null);
1399
1779
  }, LOSS_GRACE_MS);
1400
1780
  });
1401
1781
  const late = await Promise.race([
@@ -1638,6 +2018,1068 @@ class UniformRing {
1638
2018
  }
1639
2019
  }
1640
2020
  }
2021
+ function algorithmScope(ctx, label, slots) {
2022
+ const lease = ctx.pool.lease();
2023
+ const ring = new UniformRing(ctx.device, ctx.allocator, slots, `${label}/ring`);
2024
+ return {
2025
+ device: ctx.device,
2026
+ caps: ctx.caps,
2027
+ pipelines: ctx.pipelines,
2028
+ pool: ctx.pool,
2029
+ workgroupSize: ctx.workgroupSize,
2030
+ scratch: (byteLength, scratchLabel) => lease.storage(byteLength, `${label}/${scratchLabel}`),
2031
+ params(block, values) {
2032
+ const slot = ring.reserve(1);
2033
+ ring.write(slot, block, values);
2034
+ return { binding: ring.binding(block), offset: ring.offsetOf(slot) };
2035
+ },
2036
+ flush: () => {
2037
+ ring.flush();
2038
+ },
2039
+ dispose() {
2040
+ ring.destroy();
2041
+ lease.release();
2042
+ }
2043
+ };
2044
+ }
2045
+ const ALGORITHM = "connectedComponents";
2046
+ const ROUNDS_PER_BATCH = 4;
2047
+ const MAX_WCC_ROUNDS = 64;
2048
+ const SAMPLE_SIZE = 1024;
2049
+ const MAX_STEPS = 1024;
2050
+ const RING_SLOTS$2 = 2 * ROUNDS_PER_BATCH;
2051
+ function checkDest$2(dest, n) {
2052
+ if (dest === void 0) {
2053
+ return null;
2054
+ }
2055
+ if (dest instanceof Uint32Array && dest.length === n && dest.buffer instanceof ArrayBuffer) {
2056
+ return dest;
2057
+ }
2058
+ throw new WebGpuGraphError(
2059
+ "E_INVALID_ARGUMENT",
2060
+ `${ALGORITHM}: dest must be a Uint32Array of length ${n} over an ArrayBuffer`,
2061
+ {
2062
+ argument: "dest",
2063
+ value: `${dest.constructor.name}(${dest.length})`,
2064
+ expected: `Uint32Array(${n}) over an ArrayBuffer`
2065
+ }
2066
+ );
2067
+ }
2068
+ function coreOf$2(ctx, s) {
2069
+ try {
2070
+ return ctx.residency.core(s);
2071
+ } catch (error) {
2072
+ if (isWebGpuGraphError(error) && error.code === "E_TOO_LARGE" && error.details.path === "windowed") {
2073
+ throw new WebGpuGraphError(
2074
+ "E_TOO_LARGE",
2075
+ `${ALGORITHM}: the arc arrays need a windowed upload, which P1-P3 plan but do not execute`,
2076
+ { ...error.details, algorithm: ALGORITHM }
2077
+ );
2078
+ }
2079
+ throw error;
2080
+ }
2081
+ }
2082
+ function bindingOf$1(buffer, size) {
2083
+ return { buffer, offset: 0, size, window: null };
2084
+ }
2085
+ function labelResult(labels, count) {
2086
+ let groups = null;
2087
+ return {
2088
+ labels,
2089
+ count,
2090
+ groups() {
2091
+ if (groups !== null) {
2092
+ return groups;
2093
+ }
2094
+ const n = labels.length;
2095
+ const dense = new Uint32Array(n).fill(U32_MAX);
2096
+ const sizes = new Uint32Array(count);
2097
+ let next = 0;
2098
+ for (let v = 0; v < n; v++) {
2099
+ let k2 = dense[labels[v]];
2100
+ if (k2 === U32_MAX) {
2101
+ k2 = next++;
2102
+ dense[labels[v]] = k2;
2103
+ }
2104
+ sizes[k2]++;
2105
+ }
2106
+ const built = Array.from(sizes, (size) => new Uint32Array(size));
2107
+ const filled = new Uint32Array(count);
2108
+ for (let v = 0; v < n; v++) {
2109
+ const k2 = dense[labels[v]];
2110
+ built[k2][filled[k2]++] = v;
2111
+ }
2112
+ groups = built;
2113
+ return built;
2114
+ }
2115
+ };
2116
+ }
2117
+ function modeOf(sample) {
2118
+ const counts = /* @__PURE__ */ new Map();
2119
+ let best = sample[0];
2120
+ let bestCount = 0;
2121
+ for (const label of sample) {
2122
+ const c = (counts.get(label) ?? 0) + 1;
2123
+ counts.set(label, c);
2124
+ if (c > bestCount) {
2125
+ best = label;
2126
+ bestCount = c;
2127
+ }
2128
+ }
2129
+ return best;
2130
+ }
2131
+ function checkLabels(raw) {
2132
+ const n = raw.length;
2133
+ const seen = new Uint8Array(n);
2134
+ let count = 0;
2135
+ for (let v = 0; v < n; v++) {
2136
+ const label = raw[v];
2137
+ if (label >= n) {
2138
+ throw new WebGpuGraphError("E_VALIDATION", `${ALGORITHM}: labels[${v}] = ${label} is not a node index`, {
2139
+ label: `${ALGORITHM}/labels`,
2140
+ message: `the device produced a label outside [0, ${n})`
2141
+ });
2142
+ }
2143
+ if (seen[label] === 0) {
2144
+ seen[label] = 1;
2145
+ count++;
2146
+ }
2147
+ }
2148
+ return count;
2149
+ }
2150
+ async function connectedComponents(ctx, s, options) {
2151
+ ctx.assertReady();
2152
+ const n = s.nodeCount;
2153
+ const renumber = options?.renumber !== false;
2154
+ const dest = checkDest$2(options?.dest, n);
2155
+ if (options?.signal?.aborted) {
2156
+ throw new WebGpuGraphError("E_ABORTED", `${ALGORITHM}: the signal was aborted before any work started`, {});
2157
+ }
2158
+ if (n === 0) {
2159
+ options?.onProgress?.(1, 1);
2160
+ return labelResult(dest ?? new Uint32Array(0), 0);
2161
+ }
2162
+ const core = coreOf$2(ctx, s);
2163
+ if (s.arcCount === 0) {
2164
+ const labels = dest ?? new Uint32Array(n);
2165
+ for (let v = 0; v < n; v++) {
2166
+ labels[v] = v;
2167
+ }
2168
+ options?.onProgress?.(1, 1);
2169
+ return labelResult(labels, n);
2170
+ }
2171
+ const edges = ctx.residency.view(s, "edgeList");
2172
+ const edgeCount = edges.scalars.edgeCount[0];
2173
+ const scope = algorithmScope(ctx, ALGORITHM, RING_SLOTS$2);
2174
+ try {
2175
+ const compBytes = 4 * (n + 1);
2176
+ const comp = scope.scratch(compBytes, "comp");
2177
+ const items = Math.min(SAMPLE_SIZE, n);
2178
+ const hist = scope.scratch(4 * items, "hist");
2179
+ await ctx.allocator.check();
2180
+ const fill = await ctx.pipelines.kernel(kernelSpec("fill", {}));
2181
+ const linkSample = await ctx.pipelines.kernel(kernelSpec("wcc-link-sample", graphOverrides(core, null)));
2182
+ const linkEdges = await ctx.pipelines.kernel(kernelSpec("wcc-link-edges", {}));
2183
+ const compress = await ctx.pipelines.kernel(kernelSpec("wcc-compress", {}));
2184
+ const sample = await ctx.pipelines.kernel(kernelSpec("wcc-sample", {}));
2185
+ const { queue } = ctx.device;
2186
+ const compBinding = bindingOf$1(comp, compBytes);
2187
+ const histBinding = bindingOf$1(hist, 4 * items);
2188
+ const rowPlan = planGridStride(n, ctx.workgroupSize, ctx.caps);
2189
+ const edgePlan = planGridStride(edgeCount, ctx.workgroupSize, ctx.caps);
2190
+ const flagIndex = n;
2191
+ const zero = new Uint32Array(1);
2192
+ const wccParams = (fields) => scope.params(WCC_PARAMS, { n, ...fields, flagIndex, maxSteps: MAX_STEPS, pad0: 0 });
2193
+ const recordCompress = (pass2) => {
2194
+ const params = wccParams({ items: n, stride: rowPlan.stride ?? n, r: 0, giant: U32_MAX });
2195
+ compress.dispatch(pass2, compress.bind({ comp: compBinding, P: params.binding }), rowPlan, [params.offset]);
2196
+ };
2197
+ const submit = (batch) => {
2198
+ scope.flush();
2199
+ return batch.submit();
2200
+ };
2201
+ queue.writeBuffer(comp, 4 * flagIndex, zero);
2202
+ const setup = new CommandBatch(ctx, `${ALGORITHM}/setup`);
2203
+ let pass = setup.pass("sample-rounds");
2204
+ const fillParams = scope.params(FILL_PARAMS, { count: n, value: 0, mode: 1, pad0: 0 });
2205
+ fill.dispatch(
2206
+ pass,
2207
+ fill.bind({ dst: bindingOf$1(comp, 4 * n), P: fillParams.binding }),
2208
+ plan1d(n, ctx.workgroupSize, ctx.caps),
2209
+ [fillParams.offset]
2210
+ );
2211
+ const graph = graphBindings(core, null);
2212
+ for (let r = 0; r < 2; r++) {
2213
+ const params = wccParams({ items: n, stride: rowPlan.stride ?? n, r, giant: U32_MAX });
2214
+ const bound = linkSample.bind({ ...graph, comp: compBinding, P: params.binding });
2215
+ linkSample.dispatch(pass, bound, rowPlan, [params.offset]);
2216
+ }
2217
+ recordCompress(pass);
2218
+ setup.endPass();
2219
+ await submit(setup).readback;
2220
+ ctx.assertReady();
2221
+ const sampler = new CommandBatch(ctx, `${ALGORITHM}/sample`);
2222
+ pass = sampler.pass("sample");
2223
+ const sampleParams = wccParams({ items, stride: 0, r: 0, giant: U32_MAX });
2224
+ sample.dispatch(
2225
+ pass,
2226
+ sample.bind({ comp: compBinding, hist: histBinding, P: sampleParams.binding }),
2227
+ plan1d(items, ctx.workgroupSize, ctx.caps),
2228
+ [sampleParams.offset]
2229
+ );
2230
+ sampler.endPass();
2231
+ const histRequest = sampler.readback(hist, 0, 4 * items);
2232
+ const histBytes = await submit(sampler).readback;
2233
+ ctx.assertReady();
2234
+ const giant = modeOf(new Uint32Array(histBytes, histRequest.offset, items));
2235
+ const edgeBindings = { edgeSrc: edges.bindings.src, edgeDst: edges.bindings.dst, comp: compBinding };
2236
+ let rounds = 0;
2237
+ for (; ; ) {
2238
+ queue.writeBuffer(comp, 4 * flagIndex, zero);
2239
+ const batch = new CommandBatch(ctx, `${ALGORITHM}/rounds`);
2240
+ pass = batch.pass("edge-rounds");
2241
+ for (let i = 0; i < ROUNDS_PER_BATCH; i++) {
2242
+ const params = wccParams({ items: edgeCount, stride: edgePlan.stride ?? edgeCount, r: 0, giant });
2243
+ const bound = linkEdges.bind({ ...edgeBindings, P: params.binding });
2244
+ linkEdges.dispatch(pass, bound, edgePlan, [params.offset]);
2245
+ recordCompress(pass);
2246
+ }
2247
+ batch.endPass();
2248
+ const flagRequest = batch.readback(comp, 4 * flagIndex, 4);
2249
+ const submitted = submit(batch);
2250
+ const back = await submitted.readback;
2251
+ rounds += ROUNDS_PER_BATCH;
2252
+ ctx.assertReady();
2253
+ if (options?.signal?.aborted) {
2254
+ throw new WebGpuGraphError("E_ABORTED", `${ALGORITHM}: the signal was aborted`, { batchId: submitted.id });
2255
+ }
2256
+ if (new Uint32Array(back, flagRequest.offset, 1)[0] === 0) {
2257
+ break;
2258
+ }
2259
+ if (rounds >= MAX_WCC_ROUNDS) {
2260
+ throw new WebGpuGraphError(
2261
+ "E_VALIDATION",
2262
+ `${ALGORITHM}: the changed flag never settled in ${MAX_WCC_ROUNDS} rounds`,
2263
+ { label: ALGORITHM, message: `the changed flag never settled in ${MAX_WCC_ROUNDS} rounds` }
2264
+ );
2265
+ }
2266
+ }
2267
+ const final = new CommandBatch(ctx, `${ALGORITHM}/final`);
2268
+ recordCompress(final.pass("compress"));
2269
+ final.endPass();
2270
+ await submit(final).readback;
2271
+ ctx.assertReady();
2272
+ const raw = !renumber && dest !== null ? dest : new Uint32Array(n);
2273
+ await ctx.readback.read(comp, 4 * n, raw);
2274
+ ctx.assertReady();
2275
+ const distinct = checkLabels(raw);
2276
+ options?.onProgress?.(1, 1);
2277
+ if (!renumber) {
2278
+ return labelResult(raw, distinct);
2279
+ }
2280
+ const { labels, count } = renumberPartition(raw, dest ?? void 0);
2281
+ return labelResult(labels, count);
2282
+ } finally {
2283
+ scope.dispose();
2284
+ }
2285
+ }
2286
+ function rowCountOf(core, primitive) {
2287
+ const bytes = core.rowPtr.size;
2288
+ if (bytes < 4 || bytes % 4 !== 0) {
2289
+ throw new WebGpuGraphError(
2290
+ "E_INVALID_ARGUMENT",
2291
+ `${primitive}: a rowPtr binding of ${bytes} bytes is not 4(n + 1)`,
2292
+ {
2293
+ argument: "core.rowPtr",
2294
+ value: bytes,
2295
+ expected: "a positive multiple of 4"
2296
+ }
2297
+ );
2298
+ }
2299
+ return bytes / 4 - 1;
2300
+ }
2301
+ function arcCountOf(core) {
2302
+ return core.colIdx === null ? 0 : core.colIdx.size / 4;
2303
+ }
2304
+ function assertNotWindowed(core, primitive) {
2305
+ if (core.plan === "windowed" || core.windows !== null) {
2306
+ throw new WebGpuGraphError("E_UNSUPPORTED", `${primitive}: windowed cores are executed at P4`, {
2307
+ feature: `${primitive}.windowed`
2308
+ });
2309
+ }
2310
+ }
2311
+ function coreOfView(v, arcCount) {
2312
+ const { rowPtr } = v.bindings;
2313
+ if (rowPtr === void 0) {
2314
+ throw new WebGpuGraphError("E_INVALID_ARGUMENT", `the ${v.view} view has no rowPtr binding`, {
2315
+ argument: "view",
2316
+ value: v.view,
2317
+ expected: "a view with a rowPtr binding (reverse)"
2318
+ });
2319
+ }
2320
+ const colIdx = v.bindings.colIdx ?? null;
2321
+ const weights = v.bindings.weights ?? null;
2322
+ const bound = colIdx === null ? 0 : colIdx.size / 4;
2323
+ if (bound !== arcCount) {
2324
+ throw new WebGpuGraphError(
2325
+ "E_INVALID_ARGUMENT",
2326
+ `the ${v.view} view binds ${bound} arcs but its arcCount scalar says ${arcCount}`,
2327
+ {
2328
+ argument: "arcCount",
2329
+ value: arcCount,
2330
+ expected: bound
2331
+ }
2332
+ );
2333
+ }
2334
+ return Object.freeze({
2335
+ serial: -1,
2336
+ plan: "perArray",
2337
+ rowPtr,
2338
+ colIdx,
2339
+ weights,
2340
+ arcToEdge: null,
2341
+ edgeToArc: null,
2342
+ windows: null,
2343
+ hasWeights: weights !== null
2344
+ });
2345
+ }
2346
+ const VALUE_SNIPPET_VOCABULARY = /* @__PURE__ */ new Set(["row", "arc", "nbr", "weight", "v"]);
2347
+ const VALUE_SNIPPET_WGSL_WORDS = /* @__PURE__ */ new Set([
2348
+ "if",
2349
+ "else",
2350
+ "let",
2351
+ "var",
2352
+ "const",
2353
+ "true",
2354
+ "false",
2355
+ "f32",
2356
+ "u32",
2357
+ "i32",
2358
+ "bool",
2359
+ "abs",
2360
+ "ceil",
2361
+ "clamp",
2362
+ "exp",
2363
+ "exp2",
2364
+ "floor",
2365
+ "fract",
2366
+ "inverseSqrt",
2367
+ "log",
2368
+ "log2",
2369
+ "max",
2370
+ "min",
2371
+ "mix",
2372
+ "pow",
2373
+ "round",
2374
+ "select",
2375
+ "sign",
2376
+ "sqrt",
2377
+ "step",
2378
+ "trunc"
2379
+ ]);
2380
+ function opCode(op) {
2381
+ switch (op) {
2382
+ case "sum":
2383
+ return 0;
2384
+ case "min":
2385
+ return 1;
2386
+ case "max":
2387
+ return 2;
2388
+ default:
2389
+ throw new WebGpuGraphError("E_INVALID_ARGUMENT", `segmentedReduce: unknown op ${String(op)}`, {
2390
+ argument: "op",
2391
+ value: op,
2392
+ expected: "sum | min | max"
2393
+ });
2394
+ }
2395
+ }
2396
+ function stripComments(text) {
2397
+ return text.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/\/\/[^\n]*/g, " ");
2398
+ }
2399
+ function validateValueSnippet(snippet) {
2400
+ const code = stripComments(snippet);
2401
+ if (!/\bv\s*[-+*/]?=(?!=)/.test(code)) {
2402
+ throw new WebGpuGraphError("E_SHADER_COMPILE", "segmentedReduce: the VALUE snippet never assigns v", {
2403
+ id: "segmented-reduce",
2404
+ stage: "compose",
2405
+ slot: "VALUE"
2406
+ });
2407
+ }
2408
+ const tokens = code.match(/[A-Za-z_][A-Za-z0-9_]*|[0-9][0-9A-Za-z_.]*/g) ?? [];
2409
+ for (const token of tokens) {
2410
+ if (/^[0-9]/.test(token) || VALUE_SNIPPET_VOCABULARY.has(token) || VALUE_SNIPPET_WGSL_WORDS.has(token)) {
2411
+ continue;
2412
+ }
2413
+ throw new WebGpuGraphError(
2414
+ "E_SHADER_COMPILE",
2415
+ `segmentedReduce: the VALUE snippet names "${token}"; the only identifiers allowed are row, arc, nbr, weight and v`,
2416
+ { id: "segmented-reduce", stage: "compose", slot: "VALUE", identifier: token }
2417
+ );
2418
+ }
2419
+ }
2420
+ class ThreadPerRowPlanner {
2421
+ /**
2422
+ * Wraps a compiled thread-per-row pipeline with the pattern it was compiled for.
2423
+ * @param scope - the scope the pipeline was prepared in
2424
+ * @param kernel - the compiled kernel
2425
+ * @param hasWeights - the HAS_WEIGHTS the pipeline was compiled with
2426
+ * @param accumulate - whether record() combines into out instead of overwriting
2427
+ */
2428
+ constructor(scope, kernel, hasWeights, accumulate) {
2429
+ this.scope = scope;
2430
+ this.kernel = kernel;
2431
+ this.hasWeights = hasWeights;
2432
+ this.accumulate = accumulate;
2433
+ }
2434
+ /**
2435
+ * Records the dispatch: rows [0, n), arcs [0, arcCount), plan1d(n); nothing for n = 0 (no zero-length binding is
2436
+ * ever created).
2437
+ * @param pass - the pass to record into
2438
+ * @param core - a core with the SAME weights pattern as the one prepared (any snapshot)
2439
+ * @param out - at least 4n bytes of f32
2440
+ */
2441
+ record(pass, core, out) {
2442
+ assertNotWindowed(core, "segmentedReduce");
2443
+ if (core.weights !== null !== this.hasWeights) {
2444
+ throw new WebGpuGraphError(
2445
+ "E_INVALID_ARGUMENT",
2446
+ "segmentedReduce: the core's weights pattern differs from the one prepared",
2447
+ {
2448
+ argument: "core",
2449
+ value: core.weights !== null,
2450
+ expected: this.hasWeights
2451
+ }
2452
+ );
2453
+ }
2454
+ const n = rowCountOf(core, "segmentedReduce");
2455
+ if (n === 0) {
2456
+ return;
2457
+ }
2458
+ const arcCount = core.colIdx === null ? 0 : core.colIdx.size / 4;
2459
+ if (out.size < 4 * n) {
2460
+ throw new WebGpuGraphError(
2461
+ "E_INVALID_ARGUMENT",
2462
+ `segmentedReduce: out holds ${out.size} bytes, ${4 * n} needed`,
2463
+ {
2464
+ argument: "out",
2465
+ value: out.size,
2466
+ expected: `>= ${4 * n}`
2467
+ }
2468
+ );
2469
+ }
2470
+ const params = this.scope.params(RANGE_PARAMS, {
2471
+ start: 0,
2472
+ end: n,
2473
+ arcBase: 0,
2474
+ arcEnd: arcCount,
2475
+ accumulate: this.accumulate ? 1 : 0,
2476
+ n
2477
+ });
2478
+ const bound = this.kernel.bind({ ...graphBindings(core, null), out, P: params.binding });
2479
+ this.kernel.dispatch(pass, bound, plan1d(n, this.scope.workgroupSize, this.scope.caps), [params.offset]);
2480
+ }
2481
+ }
2482
+ async function prepareSegmentedReduce(scope, core, options) {
2483
+ assertNotWindowed(core, "segmentedReduce");
2484
+ const op = opCode(options.op);
2485
+ validateValueSnippet(options.valueSnippet);
2486
+ const overrides = { ...graphOverrides(core, null), OP: op, TIER: 0 };
2487
+ const spec = kernelSpec("segmented-reduce", overrides, { VALUE: options.valueSnippet });
2488
+ const kernel = await scope.pipelines.kernel(spec);
2489
+ return new ThreadPerRowPlanner(scope, kernel, core.weights !== null, options.accumulate === true);
2490
+ }
2491
+ class SpmvPullPlannerImpl {
2492
+ /**
2493
+ * Wraps a compiled pipeline with the weights choice it was compiled for.
2494
+ * @param scope - the scope the pipeline was prepared in
2495
+ * @param kernel - the compiled kernel
2496
+ * @param weights - the weights option the pipeline's HAS_WEIGHTS was derived from; record() binds the same way
2497
+ */
2498
+ constructor(scope, kernel, weights) {
2499
+ this.dispatches = 0;
2500
+ this.scope = scope;
2501
+ this.kernel = kernel;
2502
+ this.weights = weights;
2503
+ }
2504
+ /**
2505
+ * Dispatches the last record() issued.
2506
+ * @returns 1, or 0 when the last record covered no rows
2507
+ */
2508
+ get lastDispatches() {
2509
+ return this.dispatches;
2510
+ }
2511
+ /**
2512
+ * Records the dispatch: rows [0, n), arcs [0, arcCount), planGridStride(n); nothing for n = 0 (no zero-length
2513
+ * binding is ever created). `personalization ?? xNorm` follows the group-0 dummy rule: both slots are
2514
+ * storage-ro, so the aliasing check of Kernel.bind does not fire, and HAS_PERSONALIZATION false never reads it.
2515
+ * @param pass - the pass to record into
2516
+ * @param rev - the reverse core (any snapshot with the weights pattern the planner was prepared for)
2517
+ * @param resources - xNorm, rankOut, personalization, partials
2518
+ * @param coefficients - alpha, beta, uniformP
2519
+ */
2520
+ record(pass, rev, resources, coefficients) {
2521
+ const n = rowCountOf(rev, "spmvPull");
2522
+ const plan = planGridStride(n, this.scope.workgroupSize, this.scope.caps);
2523
+ if (plan.x === 0) {
2524
+ this.dispatches = 0;
2525
+ return;
2526
+ }
2527
+ const params = this.scope.params(SPMV_PARAMS, {
2528
+ n,
2529
+ arcBase: 0,
2530
+ arcEnd: arcCountOf(rev),
2531
+ stride: plan.stride ?? n,
2532
+ alpha: coefficients.alpha,
2533
+ beta: coefficients.beta,
2534
+ uniformP: coefficients.uniformP,
2535
+ pad0: 0
2536
+ });
2537
+ const bound = this.kernel.bind({
2538
+ ...graphBindings(rev, null, this.weights),
2539
+ xNorm: resources.xNorm,
2540
+ rankOut: resources.rankOut,
2541
+ personalization: resources.personalization ?? resources.xNorm,
2542
+ partials: resources.partials,
2543
+ P: params.binding
2544
+ });
2545
+ this.kernel.dispatch(pass, bound, plan, [params.offset]);
2546
+ this.dispatches = 1;
2547
+ }
2548
+ }
2549
+ async function prepareSpmvPull(scope, rev, options) {
2550
+ if (options.tiers !== null) {
2551
+ throw new WebGpuGraphError("E_UNSUPPORTED", "spmvPull: the in-degree tiers land at P4; pass tiers: null", {
2552
+ feature: "spmvPull.tiers"
2553
+ });
2554
+ }
2555
+ assertNotWindowed(rev, "spmvPull");
2556
+ const spec = kernelSpec("spmv-pull", {
2557
+ ...graphOverrides(rev, null, options.weights),
2558
+ HAS_PERSONALIZATION: options.personalization,
2559
+ USE_DANGLING: options.dangling
2560
+ });
2561
+ const kernel = await scope.pipelines.kernel(spec);
2562
+ return new SpmvPullPlannerImpl(scope, kernel, options.weights);
2563
+ }
2564
+ const PR_BATCH = 8;
2565
+ const RING_SLOTS$1 = 2 * PR_BATCH + 1;
2566
+ function checkDest$1(dest, n, algorithm) {
2567
+ if (dest === void 0) {
2568
+ return null;
2569
+ }
2570
+ if (dest instanceof Float32Array && dest.length === n && dest.buffer instanceof ArrayBuffer) {
2571
+ return dest;
2572
+ }
2573
+ throw new WebGpuGraphError(
2574
+ "E_INVALID_ARGUMENT",
2575
+ `${algorithm}: dest must be a Float32Array of length ${n} over an ArrayBuffer`,
2576
+ {
2577
+ argument: "dest",
2578
+ value: `${dest.constructor.name}(${dest.length})`,
2579
+ expected: `Float32Array(${n}) over an ArrayBuffer`
2580
+ }
2581
+ );
2582
+ }
2583
+ function coreOf$1(ctx, s, algorithm) {
2584
+ try {
2585
+ return ctx.residency.core(s);
2586
+ } catch (error) {
2587
+ if (isWebGpuGraphError(error) && error.code === "E_TOO_LARGE" && error.details.path === "windowed") {
2588
+ throw new WebGpuGraphError(
2589
+ "E_TOO_LARGE",
2590
+ `${algorithm}: the arc arrays need a windowed upload, which P1-P3 plan but do not execute`,
2591
+ { ...error.details, algorithm }
2592
+ );
2593
+ }
2594
+ throw error;
2595
+ }
2596
+ }
2597
+ function bindingOf(buffer, size) {
2598
+ return { buffer, offset: 0, size, window: null };
2599
+ }
2600
+ function aborted$1(algorithm, batchId) {
2601
+ return new WebGpuGraphError(
2602
+ "E_ABORTED",
2603
+ `${algorithm}: the signal was aborted`,
2604
+ batchId === void 0 ? {} : { batchId }
2605
+ );
2606
+ }
2607
+ async function run(ctx, s, personalization, options, algorithm) {
2608
+ ctx.assertReady();
2609
+ const n = s.nodeCount;
2610
+ const alpha = options?.dampingFactor ?? 0.85;
2611
+ const maxIterations = options?.maxIterations ?? 100;
2612
+ const tolerance = options?.tolerance ?? 1e-6;
2613
+ const useWeights = options?.weighted !== false;
2614
+ if (!Number.isInteger(maxIterations) || maxIterations < 1) {
2615
+ throw new WebGpuGraphError("E_INVALID_ARGUMENT", `${algorithm}: maxIterations must be a positive integer`, {
2616
+ argument: "maxIterations",
2617
+ value: maxIterations,
2618
+ expected: "a positive integer"
2619
+ });
2620
+ }
2621
+ const dest = checkDest$1(options?.dest, n, algorithm);
2622
+ if (options?.signal?.aborted) {
2623
+ throw aborted$1(algorithm);
2624
+ }
2625
+ if (n === 0) {
2626
+ options?.onProgress?.(maxIterations, maxIterations);
2627
+ return { scores: dest ?? new Float32Array(0), iterations: 0, converged: true, danglingMass: 0, precision: "f32" };
2628
+ }
2629
+ const core = coreOf$1(ctx, s, algorithm);
2630
+ if (s.arcCount === 0) {
2631
+ const scores = dest ?? new Float32Array(n);
2632
+ if (personalization === null) {
2633
+ scores.fill(1 / n);
2634
+ } else {
2635
+ scores.set(personalization);
2636
+ }
2637
+ options?.onProgress?.(maxIterations, maxIterations);
2638
+ return { scores, iterations: 0, converged: true, danglingMass: 1, precision: "f32" };
2639
+ }
2640
+ const view = ctx.residency.view(s, "reverse");
2641
+ const rev = coreOfView(view, view.scalars.arcCount[0]);
2642
+ const weights = useWeights ? void 0 : null;
2643
+ const weightedCore = useWeights ? core : { ...core, weights: null, hasWeights: false };
2644
+ const weightedRev = useWeights ? rev : { ...rev, weights: null, hasWeights: false };
2645
+ const scope = algorithmScope(ctx, algorithm, RING_SLOTS$1);
2646
+ let uploaded = null;
2647
+ try {
2648
+ const bytes = 4 * n;
2649
+ const rankA = scope.scratch(bytes, "rankA");
2650
+ const rankB = scope.scratch(bytes, "rankB");
2651
+ const xNorm = scope.scratch(bytes, "xNorm");
2652
+ const outWeightSum = scope.scratch(bytes, "outWeightSum");
2653
+ const scalePlan = plan1d(n, ctx.workgroupSize, ctx.caps);
2654
+ const groups = groupsOf(scalePlan);
2655
+ const partialsBytes = PR_PARTIAL.byteLength * (1 + groups);
2656
+ const partials = scope.scratch(partialsBytes, "partials");
2657
+ if (personalization !== null) {
2658
+ uploaded = ctx.residency.array(personalization, `${algorithm}/personalization`);
2659
+ }
2660
+ await ctx.allocator.check();
2661
+ const normaliser = await prepareSegmentedReduce(scope, weightedCore, {
2662
+ op: "sum",
2663
+ valueSnippet: "v = weight;",
2664
+ tiers: null
2665
+ });
2666
+ const pull = await prepareSpmvPull(scope, weightedRev, {
2667
+ personalization: personalization !== null,
2668
+ dangling: true,
2669
+ weights,
2670
+ tiers: null
2671
+ });
2672
+ const scale = await ctx.pipelines.kernel(kernelSpec("pr-scale", { NORM_MODE: 0 }));
2673
+ const finalize = await ctx.pipelines.kernel(kernelSpec("pr-finalize", { NORM_MODE: 0 }));
2674
+ const finalizePlan = plan1d(1, ctx.workgroupSize, ctx.caps);
2675
+ const { queue } = ctx.device;
2676
+ queue.writeBuffer(rankA, 0, new Float32Array(n).fill(1 / n));
2677
+ queue.writeBuffer(rankB, 0, new Float32Array(n));
2678
+ const header = new ArrayBuffer(PR_PARTIAL.byteLength);
2679
+ PR_PARTIAL.write(new DataView(header), { firstConverged: U32_MAX, iteration: 0 });
2680
+ queue.writeBuffer(partials, 0, header);
2681
+ const rank = [bindingOf(rankA, bytes), bindingOf(rankB, bytes)];
2682
+ const xNormBinding = bindingOf(xNorm, bytes);
2683
+ const outWeightSumBinding = bindingOf(outWeightSum, bytes);
2684
+ const partialsBinding = bindingOf(partials, partialsBytes);
2685
+ const coefficients = { alpha, beta: 1 - alpha, uniformP: 1 / n };
2686
+ let cur = 0;
2687
+ let iterationsRun = 0;
2688
+ for (; ; ) {
2689
+ const k2 = Math.min(PR_BATCH, maxIterations - iterationsRun);
2690
+ const batch = new CommandBatch(ctx, algorithm);
2691
+ const pass = batch.pass("iterations");
2692
+ if (iterationsRun === 0) {
2693
+ normaliser.record(pass, weightedCore, outWeightSumBinding);
2694
+ }
2695
+ for (let i = 0; i < k2; i++) {
2696
+ const params = scope.params(PR_PARAMS, {
2697
+ n,
2698
+ groups,
2699
+ iteration: iterationsRun + i + 1,
2700
+ trackConvergence: 1,
2701
+ convergeThreshold: tolerance * n
2702
+ });
2703
+ const other = 1 - cur;
2704
+ const scaleBound = scale.bind({
2705
+ rankIn: rank[cur],
2706
+ rankPrev: rank[other],
2707
+ outWeightSum: outWeightSumBinding,
2708
+ xNorm: xNormBinding,
2709
+ partials: partialsBinding,
2710
+ P: params.binding
2711
+ });
2712
+ scale.dispatch(pass, scaleBound, scalePlan, [params.offset]);
2713
+ const finalizeBound = finalize.bind({ partials: partialsBinding, P: params.binding });
2714
+ finalize.dispatch(pass, finalizeBound, finalizePlan, [params.offset]);
2715
+ pull.record(
2716
+ pass,
2717
+ weightedRev,
2718
+ {
2719
+ xNorm: xNormBinding,
2720
+ rankOut: rank[other],
2721
+ personalization: uploaded?.binding ?? null,
2722
+ partials: partialsBinding
2723
+ },
2724
+ coefficients
2725
+ );
2726
+ cur = other;
2727
+ }
2728
+ batch.endPass();
2729
+ const headerRequest = batch.readback(partials, 0, PR_PARTIAL.byteLength);
2730
+ const scoresRequest = batch.readback(rank[cur].buffer, 0, bytes);
2731
+ scope.flush();
2732
+ const submitted = batch.submit();
2733
+ const back = await submitted.readback;
2734
+ iterationsRun += k2;
2735
+ ctx.assertReady();
2736
+ if (options?.signal?.aborted) {
2737
+ throw aborted$1(algorithm, submitted.id);
2738
+ }
2739
+ options?.onProgress?.(iterationsRun, maxIterations);
2740
+ const folded = PR_PARTIAL.read(new DataView(back), headerRequest.offset);
2741
+ const firstConverged = folded.firstConverged;
2742
+ const converged = firstConverged !== U32_MAX;
2743
+ if (converged || iterationsRun >= maxIterations) {
2744
+ const scores = dest ?? new Float32Array(n);
2745
+ scores.set(new Float32Array(back, scoresRequest.offset, n));
2746
+ if (converged && iterationsRun < maxIterations) {
2747
+ options?.onProgress?.(maxIterations, maxIterations);
2748
+ }
2749
+ return {
2750
+ scores,
2751
+ iterations: converged ? firstConverged : iterationsRun,
2752
+ converged,
2753
+ danglingMass: folded.danglingMass,
2754
+ precision: "f32"
2755
+ };
2756
+ }
2757
+ }
2758
+ } finally {
2759
+ uploaded?.destroy();
2760
+ scope.dispose();
2761
+ }
2762
+ }
2763
+ function pageRank(ctx, s, options) {
2764
+ return run(ctx, s, null, options, "pageRank");
2765
+ }
2766
+ async function personalizedPageRank(ctx, s, personalization, options) {
2767
+ const n = s.nodeCount;
2768
+ const invalid2 = (value, expected) => new WebGpuGraphError("E_INVALID_ARGUMENT", `personalizedPageRank: personalization must be ${expected}`, {
2769
+ argument: "personalization",
2770
+ value,
2771
+ expected
2772
+ });
2773
+ if (!(personalization instanceof Float32Array) || personalization.length !== n) {
2774
+ throw invalid2(`${personalization.constructor.name}(${personalization.length})`, `a Float32Array of length ${n}`);
2775
+ }
2776
+ let total = 0;
2777
+ for (let v = 0; v < n; v++) {
2778
+ const mass = personalization[v];
2779
+ if (!Number.isFinite(mass) || mass < 0) {
2780
+ throw invalid2(mass, "finite and non-negative in every entry");
2781
+ }
2782
+ total += mass;
2783
+ }
2784
+ if (n > 0 && !(total > 0)) {
2785
+ throw invalid2(total, "a vector whose entries sum to a positive number");
2786
+ }
2787
+ const normalised2 = new Float32Array(n);
2788
+ for (let v = 0; v < n; v++) {
2789
+ normalised2[v] = personalization[v] / total;
2790
+ }
2791
+ return run(ctx, s, normalised2, options, "personalizedPageRank");
2792
+ }
2793
+ const BATCH = 8;
2794
+ const RING_SLOTS = 4 * BATCH + 8;
2795
+ function checkDest(dest, n, algorithm) {
2796
+ if (dest === void 0) {
2797
+ return null;
2798
+ }
2799
+ if (dest instanceof Float32Array && dest.length === n && dest.buffer instanceof ArrayBuffer) {
2800
+ return dest;
2801
+ }
2802
+ throw new WebGpuGraphError(
2803
+ "E_INVALID_ARGUMENT",
2804
+ `${algorithm}: dest must be a Float32Array of length ${n} over an ArrayBuffer`,
2805
+ {
2806
+ argument: "dest",
2807
+ value: `${dest.constructor.name}(${dest.length})`,
2808
+ expected: `Float32Array(${n}) over an ArrayBuffer`
2809
+ }
2810
+ );
2811
+ }
2812
+ function coreOf(ctx, s, algorithm) {
2813
+ try {
2814
+ return ctx.residency.core(s);
2815
+ } catch (error) {
2816
+ if (isWebGpuGraphError(error) && error.code === "E_TOO_LARGE" && error.details.path === "windowed") {
2817
+ throw new WebGpuGraphError(
2818
+ "E_TOO_LARGE",
2819
+ `${algorithm}: the arc arrays need a windowed upload, which P1-P3 plan but do not execute`,
2820
+ { ...error.details, algorithm }
2821
+ );
2822
+ }
2823
+ throw error;
2824
+ }
2825
+ }
2826
+ function reverseOf(ctx, s) {
2827
+ const view = ctx.residency.view(s, "reverse");
2828
+ return coreOfView(view, view.scalars.arcCount[0]);
2829
+ }
2830
+ function aborted(algorithm, batchId) {
2831
+ return new WebGpuGraphError(
2832
+ "E_ABORTED",
2833
+ `${algorithm}: the signal was aborted`,
2834
+ batchId === void 0 ? {} : { batchId }
2835
+ );
2836
+ }
2837
+ function whole(buffer, size) {
2838
+ return { buffer, offset: 0, size, window: null };
2839
+ }
2840
+ async function runPowerIteration(ctx, n, config) {
2841
+ const scope = algorithmScope(ctx, config.label, RING_SLOTS);
2842
+ try {
2843
+ const bytes = 4 * n;
2844
+ const ring = (config.alternate === null ? ["rankA", "rankB"] : ["rankA", "rankB", "rankC"]).map(
2845
+ (label) => whole(scope.scratch(bytes, label), bytes)
2846
+ );
2847
+ const xNorm = whole(scope.scratch(bytes, "xNorm"), bytes);
2848
+ const scalePlan = plan1d(n, ctx.workgroupSize, ctx.caps);
2849
+ const groups = groupsOf(scalePlan);
2850
+ const partialsBytes = PR_PARTIAL.byteLength * (1 + groups);
2851
+ const partialsBuffer = scope.scratch(partialsBytes, "partials");
2852
+ const partials = whole(partialsBuffer, partialsBytes);
2853
+ await ctx.allocator.check();
2854
+ const pullOptions = { personalization: false, dangling: false, weights: config.weights, tiers: null };
2855
+ const pulls = [{ core: config.adjacency, pull: await prepareSpmvPull(scope, config.adjacency, pullOptions) }];
2856
+ if (config.alternate !== null) {
2857
+ pulls.push({ core: config.alternate, pull: await prepareSpmvPull(scope, config.alternate, pullOptions) });
2858
+ }
2859
+ const scaleNorm = await ctx.pipelines.kernel(kernelSpec("pr-scale", { NORM_MODE: config.normMode }));
2860
+ const scaleApply = config.normMode === 4 ? null : await ctx.pipelines.kernel(kernelSpec("pr-scale", { NORM_MODE: 3 }));
2861
+ const finalize = await ctx.pipelines.kernel(kernelSpec("pr-finalize", { NORM_MODE: config.normMode }));
2862
+ const finalizePlan = plan1d(1, ctx.workgroupSize, ctx.caps);
2863
+ const { queue } = ctx.device;
2864
+ queue.writeBuffer(ring[0].buffer, 0, new Float32Array(n).fill(1 / n));
2865
+ for (const slot of ring.slice(1)) {
2866
+ queue.writeBuffer(slot.buffer, 0, new Float32Array(n));
2867
+ }
2868
+ const header = new ArrayBuffer(PR_PARTIAL.byteLength);
2869
+ PR_PARTIAL.write(new DataView(header), { firstConverged: U32_MAX, iteration: 0 });
2870
+ queue.writeBuffer(partialsBuffer, 0, header);
2871
+ const coefficients = { alpha: config.alpha, beta: config.beta, uniformP: config.uniformP };
2872
+ let iterationsRun = 0;
2873
+ for (; ; ) {
2874
+ const k2 = Math.min(BATCH, config.maxIterations - iterationsRun);
2875
+ const batch = new CommandBatch(ctx, config.label);
2876
+ const pass = batch.pass("iterations");
2877
+ for (let i = 0; i < k2; i++) {
2878
+ const iteration = iterationsRun + i + 1;
2879
+ const params = scope.params(PR_PARAMS, {
2880
+ n,
2881
+ groups,
2882
+ iteration,
2883
+ trackConvergence: 1,
2884
+ convergeThreshold: config.tolerance * n
2885
+ });
2886
+ const rankIn = ring[(iteration - 1) % ring.length];
2887
+ const rankOut = ring[iteration % ring.length];
2888
+ const { core, pull } = pulls[(iteration - 1) % pulls.length];
2889
+ const scaleBindings = { rankIn, rankPrev: rankOut, outWeightSum: rankIn, xNorm, partials, P: params.binding };
2890
+ scaleNorm.dispatch(pass, scaleNorm.bind(scaleBindings), scalePlan, [params.offset]);
2891
+ finalize.dispatch(pass, finalize.bind({ partials, P: params.binding }), finalizePlan, [params.offset]);
2892
+ if (scaleApply !== null) {
2893
+ scaleApply.dispatch(pass, scaleApply.bind(scaleBindings), scalePlan, [params.offset]);
2894
+ }
2895
+ pull.record(pass, core, { xNorm, rankOut, personalization: null, partials }, coefficients);
2896
+ }
2897
+ batch.endPass();
2898
+ const headerRequest = batch.readback(partialsBuffer, 0, PR_PARTIAL.byteLength);
2899
+ const scoresRequest = batch.readback(ring[(iterationsRun + k2) % ring.length].buffer, 0, bytes);
2900
+ const previousRequest = config.alternate === null ? null : batch.readback(ring[(iterationsRun + k2 - 1) % ring.length].buffer, 0, bytes);
2901
+ scope.flush();
2902
+ const submitted = batch.submit();
2903
+ const back = await submitted.readback;
2904
+ iterationsRun += k2;
2905
+ ctx.assertReady();
2906
+ if (config.signal?.aborted === true) {
2907
+ throw aborted(config.label, submitted.id);
2908
+ }
2909
+ config.onProgress?.(iterationsRun, config.maxIterations);
2910
+ const folded = PR_PARTIAL.read(new DataView(back), headerRequest.offset);
2911
+ const firstConverged = folded.firstConverged;
2912
+ const converged = firstConverged !== U32_MAX;
2913
+ if (converged || iterationsRun >= config.maxIterations) {
2914
+ if (converged && iterationsRun < config.maxIterations) {
2915
+ config.onProgress?.(config.maxIterations, config.maxIterations);
2916
+ }
2917
+ return {
2918
+ scores: new Float32Array(back, scoresRequest.offset, n).slice(),
2919
+ previous: previousRequest === null ? null : new Float32Array(back, previousRequest.offset, n).slice(),
2920
+ iterations: converged ? firstConverged : iterationsRun,
2921
+ converged,
2922
+ iterationsRun
2923
+ };
2924
+ }
2925
+ }
2926
+ } finally {
2927
+ scope.dispose();
2928
+ }
2929
+ }
2930
+ function resolve(ctx, s, options, algorithm) {
2931
+ ctx.assertReady();
2932
+ const n = s.nodeCount;
2933
+ const maxIterations = options?.maxIterations ?? 100;
2934
+ const tolerance = options?.tolerance ?? 1e-6;
2935
+ if (!Number.isInteger(maxIterations) || maxIterations < 1) {
2936
+ throw new WebGpuGraphError("E_INVALID_ARGUMENT", `${algorithm}: maxIterations must be a positive integer`, {
2937
+ argument: "maxIterations",
2938
+ value: maxIterations,
2939
+ expected: "a positive integer"
2940
+ });
2941
+ }
2942
+ const dest = checkDest(options?.dest, n, algorithm);
2943
+ if (options?.signal?.aborted) {
2944
+ throw aborted(algorithm);
2945
+ }
2946
+ return { n, maxIterations, tolerance, weights: options?.weighted === false ? null : void 0, dest };
2947
+ }
2948
+ function finite(value, fallback, argument) {
2949
+ const resolved = value ?? fallback;
2950
+ if (!Number.isFinite(resolved)) {
2951
+ throw new WebGpuGraphError("E_INVALID_ARGUMENT", `katzCentrality: ${argument} must be a finite number`, {
2952
+ argument,
2953
+ value: resolved,
2954
+ expected: "a finite number"
2955
+ });
2956
+ }
2957
+ return resolved;
2958
+ }
2959
+ function normalised(raw, dest, norm) {
2960
+ let acc = 0;
2961
+ for (const v of raw) {
2962
+ acc += norm === "sum" ? Math.abs(v) : v * v;
2963
+ }
2964
+ const scale = norm === "sum" ? acc : Math.sqrt(acc);
2965
+ const out = dest ?? raw;
2966
+ for (let v = 0; v < raw.length; v++) {
2967
+ out[v] = scale > 0 ? raw[v] / scale : raw[v];
2968
+ }
2969
+ return out;
2970
+ }
2971
+ function scoresOf(run2, dest, norm) {
2972
+ return {
2973
+ scores: normalised(run2.scores, dest, norm),
2974
+ iterations: run2.iterations,
2975
+ converged: run2.converged,
2976
+ precision: "f32"
2977
+ };
2978
+ }
2979
+ function empty(dest, total, onProgress) {
2980
+ onProgress?.(total, total);
2981
+ return { scores: dest ?? new Float32Array(0), iterations: 0, converged: true, precision: "f32" };
2982
+ }
2983
+ function latestOfKind(sameSeed, otherSeed) {
2984
+ const fromSame = sameSeed.iterationsRun % 2 === 0 ? sameSeed.iterationsRun : sameSeed.iterationsRun - 1;
2985
+ const fromOther = otherSeed.iterationsRun % 2 === 1 ? otherSeed.iterationsRun : otherSeed.iterationsRun - 1;
2986
+ const chain = fromSame >= fromOther ? sameSeed : otherSeed;
2987
+ const index = Math.max(fromSame, fromOther);
2988
+ return (index === chain.iterationsRun ? chain.scores : chain.previous) ?? chain.scores;
2989
+ }
2990
+ async function eigenvectorCentrality(ctx, s, options) {
2991
+ const algorithm = "eigenvectorCentrality";
2992
+ const r = resolve(ctx, s, options, algorithm);
2993
+ if (r.n === 0) {
2994
+ return empty(r.dest, r.maxIterations, options?.onProgress);
2995
+ }
2996
+ const run2 = await runPowerIteration(ctx, r.n, {
2997
+ normMode: 2,
2998
+ adjacency: coreOf(ctx, s, algorithm),
2999
+ alternate: null,
3000
+ alpha: 1,
3001
+ beta: 0,
3002
+ uniformP: 0,
3003
+ maxIterations: r.maxIterations,
3004
+ tolerance: r.tolerance,
3005
+ weights: r.weights,
3006
+ label: algorithm,
3007
+ signal: options?.signal,
3008
+ onProgress: options?.onProgress
3009
+ });
3010
+ return scoresOf(run2, r.dest, "l2");
3011
+ }
3012
+ async function katzCentrality(ctx, s, options) {
3013
+ const algorithm = "katzCentrality";
3014
+ const alpha = finite(options?.alpha, 0.1, "alpha");
3015
+ const beta = finite(options?.beta, 1, "beta");
3016
+ const r = resolve(ctx, s, options, algorithm);
3017
+ if (r.n === 0) {
3018
+ return empty(r.dest, r.maxIterations, options?.onProgress);
3019
+ }
3020
+ const run2 = await runPowerIteration(ctx, r.n, {
3021
+ normMode: 4,
3022
+ adjacency: reverseOf(ctx, s),
3023
+ alternate: null,
3024
+ alpha,
3025
+ beta,
3026
+ uniformP: 1,
3027
+ maxIterations: r.maxIterations,
3028
+ tolerance: r.tolerance,
3029
+ weights: r.weights,
3030
+ label: algorithm,
3031
+ signal: options?.signal,
3032
+ onProgress: options?.onProgress
3033
+ });
3034
+ return scoresOf(run2, r.dest, "l2");
3035
+ }
3036
+ async function hits(ctx, s, options) {
3037
+ const algorithm = "hits";
3038
+ const r = resolve(ctx, s, options, algorithm);
3039
+ const total = 2 * r.maxIterations;
3040
+ if (r.n === 0) {
3041
+ const { scores, iterations, converged, precision } = empty(r.dest, total, options?.onProgress);
3042
+ return { hubs: scores, authorities: new Float32Array(0), iterations, converged, precision };
3043
+ }
3044
+ const onProgress = options?.onProgress;
3045
+ const shared = {
3046
+ normMode: 1,
3047
+ alpha: 1,
3048
+ beta: 0,
3049
+ uniformP: 0,
3050
+ maxIterations: r.maxIterations,
3051
+ tolerance: r.tolerance,
3052
+ weights: r.weights,
3053
+ signal: options?.signal
3054
+ };
3055
+ const forward = coreOf(ctx, s, algorithm);
3056
+ const reverse = reverseOf(ctx, s);
3057
+ const hubSeeded = await runPowerIteration(ctx, r.n, {
3058
+ ...shared,
3059
+ adjacency: reverse,
3060
+ alternate: forward,
3061
+ label: `${algorithm}/hub-seeded`,
3062
+ onProgress: onProgress === void 0 ? void 0 : (done) => {
3063
+ onProgress(done, total);
3064
+ }
3065
+ });
3066
+ const authoritySeeded = await runPowerIteration(ctx, r.n, {
3067
+ ...shared,
3068
+ adjacency: forward,
3069
+ alternate: reverse,
3070
+ label: `${algorithm}/authority-seeded`,
3071
+ onProgress: onProgress === void 0 ? void 0 : (done) => {
3072
+ onProgress(r.maxIterations + done, total);
3073
+ }
3074
+ });
3075
+ return {
3076
+ hubs: normalised(latestOfKind(hubSeeded, authoritySeeded), r.dest, "sum"),
3077
+ authorities: normalised(latestOfKind(authoritySeeded, hubSeeded), null, "sum"),
3078
+ iterations: Math.max(hubSeeded.iterations, authoritySeeded.iterations),
3079
+ converged: hubSeeded.converged && authoritySeeded.converged,
3080
+ precision: "f32"
3081
+ };
3082
+ }
1641
3083
  const LCG_M = 34359738337;
1642
3084
  const LCG_A = 185852;
1643
3085
  const LCG_C = 1;
@@ -1710,7 +3152,7 @@ function seedPositions(s, positions, seed, dim, scale, center, range) {
1710
3152
  let finiteRows = 0;
1711
3153
  let unseededRows = 0;
1712
3154
  for (let i = 0; i < n; i++) {
1713
- let finite = true;
3155
+ let finite2 = true;
1714
3156
  for (let axis = 0; axis < dim; axis++) {
1715
3157
  const v = positions[3 * i + axis];
1716
3158
  if (Number.isFinite(v)) {
@@ -1721,10 +3163,10 @@ function seedPositions(s, positions, seed, dim, scale, center, range) {
1721
3163
  hi[axis] = v;
1722
3164
  }
1723
3165
  } else {
1724
- finite = false;
3166
+ finite2 = false;
1725
3167
  }
1726
3168
  }
1727
- if (finite) {
3169
+ if (finite2) {
1728
3170
  finiteRows++;
1729
3171
  } else {
1730
3172
  unseededRows++;
@@ -1837,7 +3279,7 @@ function fieldTypeOf(block, field) {
1837
3279
  throw invalidArgument(
1838
3280
  "field",
1839
3281
  field,
1840
- block.fields.map((f2) => f2[0]),
3282
+ block.fields.map((f) => f[0]),
1841
3283
  `block ${block.name} has no field ${field}`
1842
3284
  );
1843
3285
  }
@@ -1950,7 +3392,7 @@ class HeaderWriter {
1950
3392
  throw invalidArgument(
1951
3393
  "field",
1952
3394
  field,
1953
- this.block.fields.map((f2) => f2[0]),
3395
+ this.block.fields.map((f) => f[0]),
1954
3396
  `state block has no field ${field}`
1955
3397
  );
1956
3398
  }
@@ -2002,8 +3444,8 @@ class HeaderWriter {
2002
3444
  function createPending(k2, generation) {
2003
3445
  let resolveFn = () => void 0;
2004
3446
  let rejectFn = () => void 0;
2005
- const promise = new Promise((resolve, reject) => {
2006
- resolveFn = resolve;
3447
+ const promise = new Promise((resolve2, reject) => {
3448
+ resolveFn = resolve2;
2007
3449
  rejectFn = reject;
2008
3450
  });
2009
3451
  return {
@@ -2034,7 +3476,7 @@ class ForceSimulation {
2034
3476
  * @param tuning - the resolved GPU tuning
2035
3477
  * @param resolve - how setParams merges a patch into the current record
2036
3478
  */
2037
- constructor(ctx, model, options, tuning, resolve) {
3479
+ constructor(ctx, model, options, tuning, resolve2) {
2038
3480
  this.overrideList = /* @__PURE__ */ new Map();
2039
3481
  this.pending = [];
2040
3482
  this.stateValue = "created";
@@ -2109,7 +3551,7 @@ class ForceSimulation {
2109
3551
  this.ctx = ctx;
2110
3552
  this.model = model;
2111
3553
  this.tuning = tuning;
2112
- this.resolveOptions = resolve;
3554
+ this.resolveOptions = resolve2;
2113
3555
  this.optionsValue = options;
2114
3556
  this.maxInFlight = maxInFlight;
2115
3557
  this.dimValue = dim;
@@ -3299,8 +4741,8 @@ class ForceSimulation {
3299
4741
  */
3300
4742
  startBind(resources, overrides) {
3301
4743
  const generation = this.generationValue;
3302
- const run = () => this.compileAndBind(resources, overrides, generation);
3303
- const promise = this.ready.catch(() => void 0).then(run);
4744
+ const run2 = () => this.compileAndBind(resources, overrides, generation);
4745
+ const promise = this.ready.catch(() => void 0).then(run2);
3304
4746
  void promise.catch(() => void 0);
3305
4747
  this.ready = promise;
3306
4748
  }
@@ -4416,6 +5858,79 @@ function createAccelerator(ctx, options) {
4416
5858
  ctx.assertReady();
4417
5859
  return createForceAtlas2(ctx, { ...o, ...frozen.layout });
4418
5860
  },
5861
+ /**
5862
+ * PageRank on the device (spec 8.2; contract 3.14).
5863
+ * @param gs - the snapshot
5864
+ * @param o - the CPU option record (spec 9.2 PageRankOptions)
5865
+ * @returns the f32 scores with `precision: "f32"` (spec 9.7)
5866
+ */
5867
+ async pageRank(gs, o) {
5868
+ ctx.assertReady();
5869
+ return await pageRank(ctx, gs, o);
5870
+ },
5871
+ /**
5872
+ * Personalized PageRank on the device (spec 8.2; contract 3.14). The mirror admits an f64 personalization
5873
+ * (spec 9.2 `F32 | F64`); the kernel reads f32, so an f64 vector is narrowed on the host first.
5874
+ * @param gs - the snapshot
5875
+ * @param personalization - one finite non-negative mass per node, not all zero
5876
+ * @param o - the CPU option record (spec 9.2 PageRankOptions)
5877
+ * @returns the f32 scores with `precision: "f32"` (spec 9.7)
5878
+ */
5879
+ async personalizedPageRank(gs, personalization, o) {
5880
+ ctx.assertReady();
5881
+ const mass = personalization instanceof Float32Array ? personalization : Float32Array.from(personalization);
5882
+ return await personalizedPageRank(ctx, gs, mass, o);
5883
+ },
5884
+ /**
5885
+ * HITS hubs and authorities on the device (spec 8.2; contract 3.14).
5886
+ * @param gs - the snapshot
5887
+ * @param o - the CPU option record (spec 9.2 HitsOptions)
5888
+ * @returns hubs and authorities with `precision: "f32"` (spec 9.7)
5889
+ */
5890
+ async hits(gs, o) {
5891
+ ctx.assertReady();
5892
+ return await hits(ctx, gs, o);
5893
+ },
5894
+ /**
5895
+ * Eigenvector centrality on the device (spec 8.2; contract 3.14).
5896
+ * @param gs - the snapshot
5897
+ * @param o - the CPU option record (spec 9.2 EigenvectorOptions)
5898
+ * @returns the f32 scores with `precision: "f32"` (spec 9.7)
5899
+ */
5900
+ async eigenvectorCentrality(gs, o) {
5901
+ ctx.assertReady();
5902
+ return await eigenvectorCentrality(ctx, gs, o);
5903
+ },
5904
+ /**
5905
+ * Katz centrality on the device (spec 8.2; contract 3.14).
5906
+ * @param gs - the snapshot
5907
+ * @param o - the CPU option record (spec 9.2 KatzOptions)
5908
+ * @returns the f32 scores with `precision: "f32"` (spec 9.7)
5909
+ */
5910
+ async katzCentrality(gs, o) {
5911
+ ctx.assertReady();
5912
+ return await katzCentrality(ctx, gs, o);
5913
+ },
5914
+ /**
5915
+ * Weakly connected components on the device (spec 8.3; contract 3.14): WCC semantics on directed input.
5916
+ * @param gs - the snapshot
5917
+ * @param o - `renumber` (default true); the mirror passes none
5918
+ * @returns the labels, the count and groups() (spec 9.7 LabelResultLike)
5919
+ */
5920
+ async connectedComponents(gs, o) {
5921
+ ctx.assertReady();
5922
+ return await connectedComponents(ctx, gs, o);
5923
+ },
5924
+ /**
5925
+ * The same algorithm as `connectedComponents` under the mirror's other name (spec 3.3, 9.2).
5926
+ * @param gs - the snapshot
5927
+ * @param o - `renumber` (default true); the mirror passes none
5928
+ * @returns the labels, the count and groups() (spec 9.7 LabelResultLike)
5929
+ */
5930
+ async weaklyConnectedComponents(gs, o) {
5931
+ ctx.assertReady();
5932
+ return await connectedComponents(ctx, gs, o);
5933
+ },
4419
5934
  /**
4420
5935
  * Destroys every device buffer recorded for the snapshot (spec 4.5); delegates to ctx.release.
4421
5936
  * @param s - the snapshot the app is done with
@@ -4439,16 +5954,22 @@ export {
4439
5954
  LAYOUT_TUNING_DEFAULTS,
4440
5955
  MAX_1D_ITEMS,
4441
5956
  MAX_WORKGROUPS_PER_DIM,
4442
- f as PASSTHROUGH_FORMAT_CODES,
4443
- g as STORAGE_ALIGN,
4444
- j as WORKGROUP_SIZE,
5957
+ g as PASSTHROUGH_FORMAT_CODES,
5958
+ j as STORAGE_ALIGN,
5959
+ k as WORKGROUP_SIZE,
4445
5960
  WebGpuGraphError,
5961
+ connectedComponents,
4446
5962
  createAccelerator,
4447
5963
  createForceAtlas2,
4448
5964
  degree,
5965
+ eigenvectorCentrality,
4449
5966
  hasErrorCode,
4450
- k as isSoftwareAdapter,
5967
+ hits,
5968
+ l as isSoftwareAdapter,
4451
5969
  isWebGpuGraphError,
5970
+ katzCentrality,
5971
+ pageRank,
5972
+ personalizedPageRank,
4452
5973
  seedPositions
4453
5974
  };
4454
5975
  //# sourceMappingURL=webgpu-graph-algorithms.js.map