@graphty/webgpu-graph-algorithms 0.2.1 → 0.4.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 (131) hide show
  1. package/README.md +50 -39
  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 +12 -7
  7. package/dist/src/accelerator.d.ts.map +1 -1
  8. package/dist/src/accelerator.js +88 -7
  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/layouts/seed.d.ts +3 -1
  43. package/dist/src/layouts/seed.d.ts.map +1 -1
  44. package/dist/src/layouts/seed.js +3 -1
  45. package/dist/src/layouts/seed.js.map +1 -1
  46. package/dist/src/memory/residency.d.ts.map +1 -1
  47. package/dist/src/memory/residency.js +164 -11
  48. package/dist/src/memory/residency.js.map +1 -1
  49. package/dist/src/primitives/core-shape.d.ts +41 -0
  50. package/dist/src/primitives/core-shape.d.ts.map +1 -0
  51. package/dist/src/primitives/core-shape.js +89 -0
  52. package/dist/src/primitives/core-shape.js.map +1 -0
  53. package/dist/src/primitives/segmented-reduce.d.ts.map +1 -1
  54. package/dist/src/primitives/segmented-reduce.js +4 -30
  55. package/dist/src/primitives/segmented-reduce.js.map +1 -1
  56. package/dist/src/primitives/spmv.d.ts +56 -0
  57. package/dist/src/primitives/spmv.d.ts.map +1 -0
  58. package/dist/src/primitives/spmv.js +101 -0
  59. package/dist/src/primitives/spmv.js.map +1 -0
  60. package/dist/src/types/accelerator.d.ts +24 -31
  61. package/dist/src/types/accelerator.d.ts.map +1 -1
  62. package/dist/src/types/accelerator.js +5 -4
  63. package/dist/src/types/accelerator.js.map +1 -1
  64. package/dist/src/types/algorithms.d.ts +74 -0
  65. package/dist/src/types/algorithms.d.ts.map +1 -0
  66. package/dist/src/types/algorithms.js +18 -0
  67. package/dist/src/types/algorithms.js.map +1 -0
  68. package/dist/src/types/layout.d.ts +2 -2
  69. package/dist/src/types/layout.d.ts.map +1 -1
  70. package/dist/src/types/layout.js +1 -1
  71. package/dist/src/types/options.d.ts +8 -49
  72. package/dist/src/types/options.d.ts.map +1 -1
  73. package/dist/src/types/options.js +5 -3
  74. package/dist/src/types/options.js.map +1 -1
  75. package/dist/src/wgsl/pr-finalize.wgsl.d.ts +11 -0
  76. package/dist/src/wgsl/pr-finalize.wgsl.d.ts.map +1 -0
  77. package/dist/src/wgsl/pr-finalize.wgsl.js +36 -0
  78. package/dist/src/wgsl/pr-finalize.wgsl.js.map +1 -0
  79. package/dist/src/wgsl/pr-scale.wgsl.d.ts +14 -0
  80. package/dist/src/wgsl/pr-scale.wgsl.d.ts.map +1 -0
  81. package/dist/src/wgsl/pr-scale.wgsl.js +48 -0
  82. package/dist/src/wgsl/pr-scale.wgsl.js.map +1 -0
  83. package/dist/src/wgsl/spmv-pull.wgsl.d.ts +15 -0
  84. package/dist/src/wgsl/spmv-pull.wgsl.d.ts.map +1 -0
  85. package/dist/src/wgsl/spmv-pull.wgsl.js +47 -0
  86. package/dist/src/wgsl/spmv-pull.wgsl.js.map +1 -0
  87. package/dist/src/wgsl/wcc-compress.wgsl.d.ts +9 -0
  88. package/dist/src/wgsl/wcc-compress.wgsl.d.ts.map +1 -0
  89. package/dist/src/wgsl/wcc-compress.wgsl.js +26 -0
  90. package/dist/src/wgsl/wcc-compress.wgsl.js.map +1 -0
  91. package/dist/src/wgsl/wcc-link-edges.wgsl.d.ts +13 -0
  92. package/dist/src/wgsl/wcc-link-edges.wgsl.d.ts.map +1 -0
  93. package/dist/src/wgsl/wcc-link-edges.wgsl.js +46 -0
  94. package/dist/src/wgsl/wcc-link-edges.wgsl.js.map +1 -0
  95. package/dist/src/wgsl/wcc-link-sample.wgsl.d.ts +11 -0
  96. package/dist/src/wgsl/wcc-link-sample.wgsl.d.ts.map +1 -0
  97. package/dist/src/wgsl/wcc-link-sample.wgsl.js +45 -0
  98. package/dist/src/wgsl/wcc-link-sample.wgsl.js.map +1 -0
  99. package/dist/src/wgsl/wcc-sample.wgsl.d.ts +10 -0
  100. package/dist/src/wgsl/wcc-sample.wgsl.d.ts.map +1 -0
  101. package/dist/src/wgsl/wcc-sample.wgsl.js +18 -0
  102. package/dist/src/wgsl/wcc-sample.wgsl.js.map +1 -0
  103. package/dist/tsconfig.build.tsbuildinfo +1 -1
  104. package/dist/webgpu-graph-algorithms.js +1550 -29
  105. package/dist/webgpu-graph-algorithms.js.map +1 -1
  106. package/package.json +5 -4
  107. package/src/accelerator.ts +104 -8
  108. package/src/algorithms/components.ts +348 -0
  109. package/src/algorithms/pagerank.ts +343 -0
  110. package/src/algorithms/power-iteration.ts +278 -0
  111. package/src/algorithms/scope.ts +52 -0
  112. package/src/algorithms/spectral.ts +300 -0
  113. package/src/index.ts +20 -1
  114. package/src/kernel/dispatch.ts +12 -5
  115. package/src/kernels.ts +206 -5
  116. package/src/layouts/seed.ts +3 -1
  117. package/src/memory/residency.ts +200 -11
  118. package/src/primitives/core-shape.ts +103 -0
  119. package/src/primitives/segmented-reduce.ts +4 -36
  120. package/src/primitives/spmv.ts +155 -0
  121. package/src/types/accelerator.ts +40 -32
  122. package/src/types/algorithms.ts +83 -0
  123. package/src/types/layout.ts +2 -2
  124. package/src/types/options.ts +22 -53
  125. package/src/wgsl/pr-finalize.wgsl.ts +36 -0
  126. package/src/wgsl/pr-scale.wgsl.ts +48 -0
  127. package/src/wgsl/spmv-pull.wgsl.ts +47 -0
  128. package/src/wgsl/wcc-compress.wgsl.ts +26 -0
  129. package/src/wgsl/wcc-link-edges.wgsl.ts +46 -0
  130. package/src/wgsl/wcc-link-sample.wgsl.ts +45 -0
  131. package/src/wgsl/wcc-sample.wgsl.ts +18 -0
@@ -1,41 +1,34 @@
1
1
  /**
2
- * The structural mirrors of @graphty/layout's LayoutSimulation / LayoutAccelerator (spec 9.3) and of
3
- * the @graphty/algorithms AlgorithmAccelerator (spec 9.2), plus the package's own accelerator surface (spec 3.3).
4
- * Until W1 these ARE the mirrors (D27): from W1 the mirrors become `import type` of the real packages and
5
- * test/types/conformance.test-d.ts asserts mutual assignability. Types only.
2
+ * The layout half of spec 9.3 and the @graphty/algorithms AlgorithmAccelerator mirror (spec 9.2), plus the
3
+ * package's own accelerator surface (spec 3.3). D27's two halves are now on different footings: at W1b the LAYOUT
4
+ * mirrors became `import type` of the real `@graphty/layout` interfaces, re-exported here so this package's public
5
+ * surface is unchanged; the ALGORITHMS mirrors stay structural until A2/M8a gives them something real to point at.
6
+ * test/types/conformance.test-d.ts is the cross-compile that holds the layout half honest. Types only.
6
7
  */
7
8
 
8
- import type { F32, F64, GraphSnapshot, NodeMask, NumericVector, U32 } from "@graphty/graph-format";
9
+ import type { F32, F64, GraphSnapshot, NumericVector, U32 } from "@graphty/graph-format";
10
+ import type { LayoutAccelerator, LayoutSimulation } from "@graphty/layout";
9
11
 
10
12
  import type { GpuContext } from "../context.js";
13
+ import type {
14
+ ComponentsOptions,
15
+ EigenvectorOptions,
16
+ GpuHitsResult,
17
+ GpuLabelResult,
18
+ GpuPageRankResult,
19
+ GpuScoresResult,
20
+ HitsOptions,
21
+ KatzOptions,
22
+ PageRankOptions,
23
+ } from "./algorithms.js";
11
24
  import type { ForceAtlas2Stats, GpuLayoutSimulation, GpuLayoutTuning } from "./layout.js";
12
- import type { ForceAtlas2Options, FruchtermanReingoldOptions, SpringElectricalOptions } from "./options.js";
25
+ import type { ForceAtlas2Options } from "./options.js";
13
26
 
14
- // ---- mirrors of @graphty/layout (spec 9.3)
27
+ // ---- the real @graphty/layout interfaces (spec 9.3, D27): imported at W1b, re-exported so the package's public
28
+ // surface is unchanged and src/types/layout.ts keeps resolving them from here. `export type`, never a bare
29
+ // `export { ... }`: isolatedModules makes the bare form TS1205.
15
30
 
16
- /** Design 14.3 LayoutSimulation, verbatim. */
17
- export interface LayoutSimulation {
18
- load(snapshot: GraphSnapshot, positions: F32): void;
19
- step(iterations?: number): void | Promise<void>;
20
- readonly settled: boolean;
21
- setFixed(mask: NodeMask): void;
22
- setPosition(index: number, x: number, y: number, z: number): void;
23
- dispose(): void;
24
- }
25
-
26
- /**
27
- * Spec 9.3 LayoutAccelerator, verbatim.
28
- * Exported: published mirror (spec 3.3, D27); re-exported from src/index.ts at P3-T3.
29
- * @public
30
- */
31
- export interface LayoutAccelerator {
32
- readonly kind: string;
33
- forceAtlas2?(options?: ForceAtlas2Options): LayoutSimulation;
34
- fruchtermanReingold?(options?: FruchtermanReingoldOptions): LayoutSimulation;
35
- springElectrical?(options?: SpringElectricalOptions): LayoutSimulation;
36
- release?(s: GraphSnapshot): void;
37
- dispose?(): void;
38
- }
31
+ export type { LayoutAccelerator, LayoutSimulation };
39
32
 
40
33
  // ---- mirrors of @graphty/algorithms (spec 9.2); the option types named there do not exist before A2, so they are
41
34
  // mirrored as empty-extensible records
@@ -221,8 +214,12 @@ export interface AcceleratorOptions {
221
214
  }
222
215
 
223
216
  /**
224
- * The injectable object (spec 3.3); at P3 it carries forceAtlas2, release and dispose -- the algorithm members
225
- * arrive with P7+.
217
+ * The injectable object (spec 3.3): P3's forceAtlas2, release and dispose, plus P7's seven algorithm members
218
+ * (spec 8.2, 8.3; M8b-T8), non-optional here and returning the `Gpu*Result` shapes, which satisfy the `*ResultLike`
219
+ * mirrors (spec 9.7: `precision` is an extra field, `F32` is a `NumericVector`). `connectedComponents` and
220
+ * `weaklyConnectedComponents` are the same algorithm (spec 3.3: WCC semantics on directed input) under both names
221
+ * the mirror declares; their options parameter stays OPTIONAL, because the mirror declares none and an extra
222
+ * REQUIRED parameter would stop the member satisfying it. Later phases add one member per shipped algorithm.
226
223
  * Exported: implemented by src/accelerator.ts (P3-T3); re-exported from src/index.ts at P3-T3.
227
224
  * @public
228
225
  */
@@ -231,6 +228,17 @@ export interface GpuAccelerator extends AlgorithmAccelerator, LayoutAccelerator
231
228
  readonly ctx: GpuContext;
232
229
  readonly options: Readonly<AcceleratorOptions>;
233
230
  forceAtlas2(options?: ForceAtlas2Options): GpuLayoutSimulation<ForceAtlas2Options, ForceAtlas2Stats>;
231
+ pageRank(s: GraphSnapshot, options?: PageRankOptions): Promise<GpuPageRankResult>;
232
+ personalizedPageRank(
233
+ s: GraphSnapshot,
234
+ personalization: F32 | F64,
235
+ options?: PageRankOptions,
236
+ ): Promise<GpuPageRankResult>;
237
+ hits(s: GraphSnapshot, options?: HitsOptions): Promise<GpuHitsResult>;
238
+ eigenvectorCentrality(s: GraphSnapshot, options?: EigenvectorOptions): Promise<GpuScoresResult>;
239
+ katzCentrality(s: GraphSnapshot, options?: KatzOptions): Promise<GpuScoresResult>;
240
+ connectedComponents(s: GraphSnapshot, options?: ComponentsOptions): Promise<GpuLabelResult>;
241
+ weaklyConnectedComponents(s: GraphSnapshot, options?: ComponentsOptions): Promise<GpuLabelResult>;
234
242
  release(s: GraphSnapshot): void;
235
243
  dispose(): void;
236
244
  }
@@ -0,0 +1,83 @@
1
+ /**
2
+ * The result and option records of the P7 algorithms (spec 3.3 lines 815-828, 9.7). The `Gpu*Result` shapes are the
3
+ * design's verbatim; the option records are this package's own, spelled MEMBER FOR MEMBER as the CPU seam spells
4
+ * them so one object literal satisfies both sides (the D27 mirror rule src/types/options.ts followed for the
5
+ * layout options until W1b, when those became `import type` re-exports of `@graphty/layout`'s declarations).
6
+ * Types only: this file imports nothing at runtime.
7
+ *
8
+ * The CPU counterparts, when phase M8a lands them (plan 2026-09-19-webgpu-m8a-algorithms-seam, Task M8a-T8):
9
+ * `PageRankOptions` here is `IndexedPageRankOptions` there (`{ dampingFactor?, maxIterations?, tolerance?,
10
+ * weighted? }`); `HitsOptions`, `EigenvectorOptions` and `KatzOptions` here all correspond to the ONE
11
+ * `HitsOptionsLike` there (`{ maxIterations?, tolerance?, weighted? }`), which is why every one of them carries
12
+ * those three members and `KatzOptions` adds `alpha` / `beta` on top; `ComponentsOptions` has no CPU counterpart at
13
+ * all, because `AlgorithmAccelerator.connectedComponents?(s: GraphSnapshot): Promise<LabelResultLike>` declares no
14
+ * options parameter. `weighted`, never `weight`: that is the member name graph-format design 14.2 fixes at
15
+ * `design/graph-format/graph-format-design.md:3892` and the one M8a ports.
16
+ */
17
+
18
+ import type { F32, U32 } from "@graphty/graph-format";
19
+
20
+ /** Spec 3.3 line 815: every score result carries `precision` so a consumer can label GPU scores (Q-24). */
21
+ export interface GpuScoresResult {
22
+ readonly scores: F32;
23
+ readonly iterations: number;
24
+ readonly converged: boolean;
25
+ readonly precision: "f32";
26
+ }
27
+
28
+ /** Spec 3.3 line 816: `iterations` is the first iteration whose L1 delta fell below the tolerance (8.2), not the batch boundary. */
29
+ export interface GpuPageRankResult extends GpuScoresResult {
30
+ readonly danglingMass: number;
31
+ }
32
+
33
+ /** Spec 3.3 line 817. */
34
+ export interface GpuHitsResult {
35
+ readonly hubs: F32;
36
+ readonly authorities: F32;
37
+ readonly iterations: number;
38
+ readonly converged: boolean;
39
+ readonly precision: "f32";
40
+ }
41
+
42
+ /** Spec 3.3 line 818: labels dense 0..count-1 in first-seen order (renumberPartition); groups() is index-aligned. */
43
+ export interface GpuLabelResult {
44
+ readonly labels: U32;
45
+ readonly count: number;
46
+ groups(): U32[];
47
+ }
48
+
49
+ /** The CPU seam's IndexedPageRankOptions, member for member (M8a Task M8a-T8; graph-format design 14.2 :3892). */
50
+ export interface PageRankOptions {
51
+ readonly dampingFactor?: number | undefined;
52
+ readonly maxIterations?: number | undefined;
53
+ readonly tolerance?: number | undefined;
54
+ readonly weighted?: boolean | undefined;
55
+ }
56
+
57
+ /** The CPU seam's HitsOptionsLike, member for member (M8a Task M8a-T8). */
58
+ export interface HitsOptions {
59
+ readonly maxIterations?: number | undefined;
60
+ readonly tolerance?: number | undefined;
61
+ readonly weighted?: boolean | undefined;
62
+ }
63
+
64
+ /** The CPU seam's HitsOptionsLike again: `eigenvectorCentrality` takes that same shape on the CPU side. */
65
+ export interface EigenvectorOptions {
66
+ readonly maxIterations?: number | undefined;
67
+ readonly tolerance?: number | undefined;
68
+ readonly weighted?: boolean | undefined;
69
+ }
70
+
71
+ /** HitsOptionsLike plus Katz's own two: `alpha` is the attenuation and `beta` the constant term. */
72
+ export interface KatzOptions {
73
+ readonly alpha?: number | undefined;
74
+ readonly beta?: number | undefined;
75
+ readonly maxIterations?: number | undefined;
76
+ readonly tolerance?: number | undefined;
77
+ readonly weighted?: boolean | undefined;
78
+ }
79
+
80
+ /** GPU-only (spec 3.3 line 797: `renumber: true` by default, Q-12); the CPU seam's connectedComponents takes none. */
81
+ export interface ComponentsOptions {
82
+ readonly renumber?: boolean | undefined;
83
+ }
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * The layout-facing public types (spec 3.3, 7.19): the stats records, the GPU simulation interface that extends the
3
- * design-14.3 LayoutSimulation mirror, the run options and the GPU-only tuning knobs. Types only.
3
+ * real `@graphty/layout` LayoutSimulation (design 14.3), the run options and the GPU-only tuning knobs. Types only.
4
4
  */
5
5
 
6
6
  import type { F32, GraphSnapshot, NodeMask } from "@graphty/graph-format";
@@ -50,7 +50,7 @@ export interface RunOptions {
50
50
  readonly signal?: AbortSignal | undefined;
51
51
  }
52
52
 
53
- /** Spec 3.3 GpuLayoutSimulation, verbatim (LayoutSimulation is the design-14.3 mirror of accelerator.ts). */
53
+ /** Spec 3.3 GpuLayoutSimulation, verbatim (LayoutSimulation is `@graphty/layout`'s, via accelerator.ts). */
54
54
  export interface GpuLayoutSimulation<Options, Stats extends LayoutStatsBase> extends LayoutSimulation {
55
55
  load(snapshot: GraphSnapshot, positions: F32): void;
56
56
  /**
@@ -1,60 +1,29 @@
1
1
  /**
2
- * The option records of the layouts (spec 9.3, 7.14): structural mirrors of @graphty/layout's option types (D27),
3
- * copied field for field so a `ForceAtlas2Options` object the element parses is accepted here without a cast. Types
4
- * only: this file imports nothing at runtime.
2
+ * The option records of the layouts (spec 9.3, 7.14). The five layout-owned records come from `@graphty/layout` by
3
+ * `import type` and are re-exported here, so a `ForceAtlas2Options` object the element parses is not merely
4
+ * shaped like the one this package takes -- it IS the same declaration (W1b; the D27 mirrors are gone).
5
+ * `ResolvedForceAtlas2Options` below is this package's own and stays local. Types only: nothing here is a runtime
6
+ * import.
5
7
  */
6
8
 
7
- import type { F32, NodeId, NodeMask } from "@graphty/graph-format";
9
+ import type { F32, NodeId } from "@graphty/graph-format";
10
+ import type {
11
+ CommonLayoutOptions,
12
+ ForceAtlas2Options,
13
+ FruchtermanReingoldOptions,
14
+ SimulationOptions,
15
+ SpringElectricalOptions,
16
+ } from "@graphty/layout";
8
17
 
9
- /** Design 14.3 CommonLayoutOptions, mirrored verbatim. */
10
- export interface CommonLayoutOptions {
11
- readonly dim?: 2 | 3 | undefined;
12
- readonly scale?: number | undefined;
13
- readonly center?: ArrayLike<number> | undefined;
14
- readonly seed?: number | null | undefined;
15
- }
16
-
17
- /** Spec 9.3 SimulationOptions, mirrored verbatim (layout-owned; the CPU simulations ignore maxInFlight). */
18
- export interface SimulationOptions {
19
- readonly settleThreshold?: number | undefined;
20
- readonly settleWindow?: number | undefined;
21
- readonly iterationsPerStep?: number | undefined;
22
- readonly maxInFlight?: number | undefined;
23
- }
24
-
25
- /**
26
- * Spec 9.3 ForceAtlas2Options, mirrored verbatim (same names and defaults as
27
- * layout/src/layouts/force-directed/forceatlas2.ts lines 26-42).
28
- */
29
- export interface ForceAtlas2Options extends CommonLayoutOptions, SimulationOptions {
30
- readonly maxIter?: number | undefined;
31
- readonly jitterTolerance?: number | undefined;
32
- readonly scalingRatio?: number | undefined;
33
- readonly gravity?: number | undefined;
34
- readonly strongGravity?: boolean | undefined;
35
- readonly distributedAction?: boolean | undefined;
36
- readonly linlog?: boolean | undefined;
37
- readonly nodeMass?: F32 | string | Readonly<Record<NodeId, number>> | null | undefined;
38
- readonly nodeSize?: F32 | string | Readonly<Record<NodeId, number>> | null | undefined;
39
- readonly weight?: boolean | string | null | undefined;
40
- readonly dissuadeHubs?: boolean | undefined;
41
- }
42
-
43
- /** Spec 9.3 FruchtermanReingoldOptions, mirrored for the LayoutAccelerator mirror's method signature (P5 implements it). */
44
- export interface FruchtermanReingoldOptions extends CommonLayoutOptions, SimulationOptions {
45
- readonly k?: number | null | undefined;
46
- readonly iterations?: number | undefined;
47
- readonly fixed?: NodeMask | string | null | undefined;
48
- }
49
-
50
- /** Spec 9.3 SpringElectricalOptions, mirrored for the LayoutAccelerator mirror's method signature (P5 implements it). */
51
- export interface SpringElectricalOptions extends CommonLayoutOptions, SimulationOptions {
52
- readonly springLength?: number | undefined;
53
- readonly springCoefficient?: number | undefined;
54
- readonly gravity?: number | undefined;
55
- readonly dragCoefficient?: number | undefined;
56
- readonly timeStep?: number | undefined;
57
- }
18
+ // The five layout-owned option records are @graphty/layout's declarations, re-exported so src/index.ts's barrel
19
+ // and the option type tests keep resolving them from here (W1b, Task M5b-T2).
20
+ export type {
21
+ CommonLayoutOptions,
22
+ ForceAtlas2Options,
23
+ FruchtermanReingoldOptions,
24
+ SimulationOptions,
25
+ SpringElectricalOptions,
26
+ };
58
27
 
59
28
  /**
60
29
  * The resolved (defaults applied) ForceAtlas2 option record the simulation keeps; every field present.
@@ -0,0 +1,36 @@
1
+ /**
2
+ * The `pr-finalize` kernel body (spec 8.2 dispatch (b)): ONE workgroup folds the `P.groups` per-workgroup partials
3
+ * into the header at `partials[0]` -- a STORAGE region, never a uniform, read by the next dispatch of the same
4
+ * pass -- and records `firstConverged` the first time the delta falls below `P.convergeThreshold`. NORM_MODE 2
5
+ * stores the square root of the folded norm (the L2 case). The recorded iteration is `P.iteration - 1u` because
6
+ * the delta a scale pass produces at iteration i is `|x(i-1) - x(i-2)|`, the error of iteration i - 1 (PD-9).
7
+ * The body is normative: a sabotage mutation is a textual edit of it, so it is not restyled.
8
+ */
9
+
10
+ /** Entry point `pr_finalize`; override NORM_MODE (2 takes the square root of the folded norm, every other value stores it as folded). */
11
+ export const prFinalizeWgsl = /* wgsl */ `
12
+ @compute @workgroup_size(WG)
13
+ fn pr_finalize(@builtin(local_invocation_id) lid: vec3<u32>) {
14
+ var d = 0.0;
15
+ var e = 0.0;
16
+ var m = 0.0;
17
+ for (var g = lid.x; g < P.groups; g = g + WG) {
18
+ d = d + partials[1u + g].danglingMass;
19
+ e = e + partials[1u + g].delta;
20
+ m = m + partials[1u + g].norm;
21
+ }
22
+ let folded = wg_reduce_vec4(vec4f(d, e, m, 0.0), lid.x, 0u);
23
+ if (lid.x == 0u) {
24
+ partials[0].danglingMass = folded.x;
25
+ partials[0].delta = folded.y;
26
+ var norm = folded.z;
27
+ if (NORM_MODE == 2u) { norm = sqrt(max(0.0, folded.z)); }
28
+ partials[0].norm = norm;
29
+ partials[0].iteration = P.iteration;
30
+ let unset = partials[0].firstConverged == U32_MAX;
31
+ if (P.trackConvergence == 1u && P.iteration >= 2u && folded.y < P.convergeThreshold && unset) {
32
+ partials[0].firstConverged = P.iteration - 1u;
33
+ }
34
+ }
35
+ }
36
+ `;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * The `pr-scale` kernel body (spec 8.2 dispatch (a)): one invocation per node writes `xNorm[u]` and contributes a
3
+ * per-workgroup partial of the dangling mass, the L1 delta `|rankIn - rankPrev|` and, for the spectral modes, the
4
+ * norm term. NORM_MODE selects the divisor: 0 PageRank (`rankIn[u] / outWeightSum[u]`, 0 and a dangling
5
+ * contribution when the sum is not positive); 1 and 2 are NORM PASSES that write no xNorm and only accumulate
6
+ * `abs(x)` (L1) or `x * x` (L2); 3 divides by the scalar `partials[0].norm` the previous dispatch folded; 4 is the
7
+ * identity (Katz). The body is normative: a sabotage mutation is a textual edit of it, so it is not restyled.
8
+ *
9
+ * The guard is named `inRange`, never `active`: `active` is a WGSL reserved word (spec 16.2) and the composer
10
+ * rejects it before a device is touched.
11
+ */
12
+
13
+ /** Entry point `pr_scale`; override NORM_MODE (0 PageRank, 1 L1 norm pass, 2 L2 norm pass, 3 scale by partials[0].norm, 4 identity). */
14
+ export const prScaleWgsl = /* wgsl */ `
15
+ @compute @workgroup_size(WG)
16
+ fn pr_scale(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
17
+ let u = linear_id(wid, lid.x);
18
+ let inRange = u < P.n;
19
+ var x = 0.0;
20
+ var prev = 0.0;
21
+ if (inRange) { x = rankIn[u]; prev = rankPrev[u]; }
22
+ var dangling = 0.0;
23
+ var delta = 0.0;
24
+ var normTerm = 0.0;
25
+ if (inRange) {
26
+ delta = abs(x - prev);
27
+ if (NORM_MODE == 0u) {
28
+ let divisor = outWeightSum[u];
29
+ if (divisor <= 0.0) { dangling = x; xNorm[u] = 0.0; } else { xNorm[u] = x / divisor; }
30
+ }
31
+ if (NORM_MODE == 1u) { normTerm = abs(x); }
32
+ if (NORM_MODE == 2u) { normTerm = x * x; }
33
+ if (NORM_MODE == 3u) {
34
+ var scale = partials[0].norm;
35
+ if (scale <= 0.0) { scale = 1.0; }
36
+ xNorm[u] = x / scale;
37
+ }
38
+ if (NORM_MODE == 4u) { xNorm[u] = x; }
39
+ }
40
+ let folded = wg_reduce_vec4(vec4f(dangling, delta, normTerm, 0.0), lid.x, 0u);
41
+ if (lid.x == 0u) {
42
+ let slot = 1u + group_id(wid);
43
+ partials[slot].danglingMass = folded.x;
44
+ partials[slot].delta = folded.y;
45
+ partials[slot].norm = folded.z;
46
+ }
47
+ }
48
+ `;
@@ -0,0 +1,47 @@
1
+ /**
2
+ * The `spmv-pull` kernel body (spec 6 row 9, 8.2; PD-1 of the M8b plan): one invocation per row of the REVERSE
3
+ * adjacency, grid-stride over `[0, P.n)`, folding `weight * xNorm[nbr]` over the row's in-arcs in chunks of 64
4
+ * terms (a two-level f32 sum: the chunk absorbs the rounding of 64 terms, the row total the rounding of the chunk
5
+ * count, so a 10,000-arc hub row loses about 200 rounding steps instead of 10,000; Kahan compensation is not used
6
+ * because Metal's shader compiler folds `((acc + term) - acc) - term` to zero whatever hides it) and writing
7
+ * `rankOut[v] = beta * pv + alpha * (sum + danglingMass * pv)`, where `pv` is `personalization[v]` when
8
+ * HAS_PERSONALIZATION and the uniform `P.uniformP` otherwise. PageRank sets alpha to the
9
+ * damping factor, beta to `1 - alpha` and USE_DANGLING; HITS and eigenvector set alpha 1, beta 0, uniformP 0; Katz
10
+ * sets alpha to the attenuation, beta to its constant and uniformP 1. The body is normative: a sabotage mutation
11
+ * (test/helpers/sabotage.ts) is a textual edit of it, so it is not restyled.
12
+ */
13
+
14
+ /** Entry point `spmv_pull`; overrides HAS_PERSONALIZATION and USE_DANGLING plus the standard USE_PERM / HAS_WEIGHTS. */
15
+ export const spmvPullWgsl = /* wgsl */ `
16
+ @compute @workgroup_size(WG)
17
+ fn spmv_pull(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
18
+ var dangling = 0.0;
19
+ if (USE_DANGLING) { dangling = partials[0].danglingMass; }
20
+ let first = linear_id(wid, lid.x);
21
+ for (var row = first; row < P.n; row = row + P.stride) {
22
+ let v = select(row, perm[row], USE_PERM);
23
+ let a0 = max(rowPtr[v], P.arcBase);
24
+ let a1 = min(rowPtr[v + 1u], P.arcEnd);
25
+ var acc = 0.0;
26
+ var chunk = 0.0;
27
+ var inChunk = 0u;
28
+ for (var arc = a0; arc < a1; arc = arc + 1u) {
29
+ let nbr = colIdx[arc - P.arcBase]; // \`target\` is a WGSL reserved word (spec 16.2)
30
+ var weight = 1.0;
31
+ if (HAS_WEIGHTS) { weight = weights[arc - P.arcBase]; }
32
+ // two-level sum: 64 terms into chunk, chunk into acc (see the header; no compensation, no select)
33
+ chunk = chunk + (weight * xNorm[nbr]);
34
+ inChunk = inChunk + 1u;
35
+ if (inChunk == 64u) {
36
+ acc = acc + chunk;
37
+ chunk = 0.0;
38
+ inChunk = 0u;
39
+ }
40
+ }
41
+ acc = acc + chunk;
42
+ var pv = P.uniformP;
43
+ if (HAS_PERSONALIZATION) { pv = personalization[v]; }
44
+ rankOut[v] = (P.beta * pv) + (P.alpha * (acc + (dangling * pv)));
45
+ }
46
+ }
47
+ `;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * The `wcc-compress` kernel body (spec 8.3): pointer jumping to the root, reading through `atomicLoad` on the same
3
+ * `array<atomic<u32>>` because WGSL forbids mixing atomic and plain access to one element. The walk is bounded by
4
+ * `P.maxSteps`; a walk that runs out leaves a shorter path, which the next round finishes. The body is normative:
5
+ * a sabotage mutation is a textual edit of it, so it is not restyled.
6
+ */
7
+
8
+ /** Entry point `wcc_compress`; no overrides. */
9
+ export const wccCompressWgsl = /* wgsl */ `
10
+ @compute @workgroup_size(WG)
11
+ fn wcc_compress(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
12
+ let first = linear_id(wid, lid.x);
13
+ for (var v = first; v < P.items; v = v + P.stride) {
14
+ var root = atomicLoad(&comp[v]);
15
+ var steps = 0u;
16
+ loop {
17
+ let parent = atomicLoad(&comp[root]);
18
+ if (parent == root) { break; }
19
+ if (steps >= P.maxSteps) { break; }
20
+ steps = steps + 1u;
21
+ root = parent;
22
+ }
23
+ atomicStore(&comp[v], root);
24
+ }
25
+ }
26
+ `;
@@ -0,0 +1,46 @@
1
+ /**
2
+ * The `wcc-link-edges` kernel body (spec 8.3): the each-edge-once link round of Afforest, correct for directed and
3
+ * undirected input alike because `edgeList()` yields every logical edge once in declared orientation (design 10.1).
4
+ * `link_pair` is the same GAP `Link` transcription as wcc-link-sample (each module is composed alone, so the helper
5
+ * is copied, not shared): all-u32 CAS on `comp`, an `array<atomic<u32>>` because WGSL forbids mixing atomic and
6
+ * plain access to one element, with a bounded retry loop (PD-5) and the changed flag at `P.flagIndex` inside the
7
+ * same array (PD-4). The `P.giant` guard is GAP's "skip the vertices already in the giant component" and is a pure
8
+ * optimisation -- linking two vertices already in one component is a no-op. The body is normative: a sabotage
9
+ * mutation is a textual edit of it, so it is not restyled.
10
+ */
11
+
12
+ /** Entry point `wcc_link_edges`; no overrides. */
13
+ export const wccLinkEdgesWgsl = /* wgsl */ `
14
+ fn link_pair(a: u32, b: u32) {
15
+ var p1 = atomicLoad(&comp[a]);
16
+ var p2 = atomicLoad(&comp[b]);
17
+ var steps = 0u;
18
+ loop {
19
+ if (p1 == p2) { break; }
20
+ if (steps >= P.maxSteps) { atomicStore(&comp[P.flagIndex], 1u); break; }
21
+ steps = steps + 1u;
22
+ let hi = max(p1, p2);
23
+ let lo = min(p1, p2);
24
+ let pHigh = atomicLoad(&comp[hi]);
25
+ if (pHigh == lo) { break; }
26
+ if (pHigh == hi) {
27
+ let swapped = atomicCompareExchangeWeak(&comp[hi], hi, lo);
28
+ if (swapped.exchanged) { atomicStore(&comp[P.flagIndex], 1u); break; }
29
+ }
30
+ p1 = atomicLoad(&comp[atomicLoad(&comp[hi])]);
31
+ p2 = atomicLoad(&comp[lo]);
32
+ }
33
+ }
34
+
35
+ @compute @workgroup_size(WG)
36
+ fn wcc_link_edges(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
37
+ let first = linear_id(wid, lid.x);
38
+ for (var e = first; e < P.items; e = e + P.stride) {
39
+ let u = edgeSrc[e];
40
+ let v = edgeDst[e];
41
+ if (u == v) { continue; }
42
+ if (atomicLoad(&comp[u]) == P.giant && atomicLoad(&comp[v]) == P.giant) { continue; }
43
+ link_pair(u, v);
44
+ }
45
+ }
46
+ `;
@@ -0,0 +1,45 @@
1
+ /**
2
+ * The `wcc-link-sample` kernel body (spec 8.3): one of Afforest's sampled link rounds -- every vertex links its
3
+ * r-th neighbour, `colIdx[rowPtr[v] + P.r]`, when it has one. `link_pair` is GAP's `Link` (gapbs/cc.cc lines
4
+ * 40-150) transcribed for WGSL: all-u32 CAS on `comp`, which is `array<atomic<u32>>` because WGSL forbids mixing
5
+ * atomic and plain access to one element, with a bounded retry loop (PD-5). The changed flag is the word at
6
+ * `P.flagIndex` inside the same array (PD-4). The body is normative: a sabotage mutation is a textual edit of it,
7
+ * so it is not restyled.
8
+ */
9
+
10
+ /** Entry point `wcc_link_sample`; standard USE_PERM / HAS_WEIGHTS only (the body reads neither weights nor a permutation beyond the row select). */
11
+ export const wccLinkSampleWgsl = /* wgsl */ `
12
+ fn link_pair(a: u32, b: u32) {
13
+ var p1 = atomicLoad(&comp[a]);
14
+ var p2 = atomicLoad(&comp[b]);
15
+ var steps = 0u;
16
+ loop {
17
+ if (p1 == p2) { break; }
18
+ if (steps >= P.maxSteps) { atomicStore(&comp[P.flagIndex], 1u); break; }
19
+ steps = steps + 1u;
20
+ let hi = max(p1, p2);
21
+ let lo = min(p1, p2);
22
+ let pHigh = atomicLoad(&comp[hi]);
23
+ if (pHigh == lo) { break; }
24
+ if (pHigh == hi) {
25
+ let swapped = atomicCompareExchangeWeak(&comp[hi], hi, lo);
26
+ if (swapped.exchanged) { atomicStore(&comp[P.flagIndex], 1u); break; }
27
+ }
28
+ p1 = atomicLoad(&comp[atomicLoad(&comp[hi])]);
29
+ p2 = atomicLoad(&comp[lo]);
30
+ }
31
+ }
32
+
33
+ @compute @workgroup_size(WG)
34
+ fn wcc_link_sample(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
35
+ let first = linear_id(wid, lid.x);
36
+ for (var row = first; row < P.items; row = row + P.stride) {
37
+ let v = select(row, perm[row], USE_PERM);
38
+ let a0 = rowPtr[v];
39
+ let a1 = rowPtr[v + 1u];
40
+ if (a0 + P.r < a1) {
41
+ link_pair(v, colIdx[a0 + P.r]);
42
+ }
43
+ }
44
+ }
45
+ `;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * The `wcc-sample` kernel body (spec 8.3 "a 1,024-entry histogram readback to find the giant component"): writes
3
+ * the component label of `P.items` pseudo-randomly chosen vertices into `hist`, which the host reads back and takes
4
+ * the mode of (PD-12: GAP's SampleFrequentElement counts on the host too, and a device histogram over component
5
+ * ids would return a bucket, not an id). The sampler uses the prelude's `lowbias32` and `%`, never a bitwise
6
+ * operator on an index.
7
+ */
8
+
9
+ /** Entry point `wcc_sample`; no overrides. */
10
+ export const wccSampleWgsl = /* wgsl */ `
11
+ @compute @workgroup_size(WG)
12
+ fn wcc_sample(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
13
+ let i = linear_id(wid, lid.x);
14
+ if (i >= P.items) { return; }
15
+ let v = lowbias32(i + P.r) % P.n;
16
+ hist[i] = atomicLoad(&comp[v]);
17
+ }
18
+ `;