@graphty/webgpu-graph-algorithms 0.6.26 → 0.6.28

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 (116) hide show
  1. package/README.md +56 -6
  2. package/dist/acquire.d.ts +2 -0
  3. package/dist/browser.js +18 -1
  4. package/dist/browser.js.map +1 -1
  5. package/dist/chunks/accelerator-B-FjQwaA.js +19173 -0
  6. package/dist/chunks/accelerator-B-FjQwaA.js.map +1 -0
  7. package/dist/chunks/managed-D_GdQtnu.js +98 -0
  8. package/dist/chunks/managed-D_GdQtnu.js.map +1 -0
  9. package/dist/node.js +18 -1
  10. package/dist/node.js.map +1 -1
  11. package/dist/src/accelerator.d.ts.map +1 -1
  12. package/dist/src/accelerator.js +5 -3
  13. package/dist/src/accelerator.js.map +1 -1
  14. package/dist/src/algorithms/all-pairs.d.ts.map +1 -1
  15. package/dist/src/algorithms/all-pairs.js +72 -47
  16. package/dist/src/algorithms/all-pairs.js.map +1 -1
  17. package/dist/src/algorithms/betweenness.d.ts +1 -1
  18. package/dist/src/algorithms/betweenness.js +2 -2
  19. package/dist/src/algorithms/closeness.d.ts +45 -42
  20. package/dist/src/algorithms/closeness.d.ts.map +1 -1
  21. package/dist/src/algorithms/closeness.js +295 -226
  22. package/dist/src/algorithms/closeness.js.map +1 -1
  23. package/dist/src/browser/index.d.ts +10 -0
  24. package/dist/src/browser/index.d.ts.map +1 -1
  25. package/dist/src/browser/index.js +22 -0
  26. package/dist/src/browser/index.js.map +1 -1
  27. package/dist/src/constants.d.ts +13 -3
  28. package/dist/src/constants.d.ts.map +1 -1
  29. package/dist/src/constants.js +13 -3
  30. package/dist/src/constants.js.map +1 -1
  31. package/dist/src/kernels.d.ts +14 -4
  32. package/dist/src/kernels.d.ts.map +1 -1
  33. package/dist/src/kernels.js +62 -28
  34. package/dist/src/kernels.js.map +1 -1
  35. package/dist/src/layouts/force-simulation.d.ts.map +1 -1
  36. package/dist/src/layouts/force-simulation.js +0 -1
  37. package/dist/src/layouts/force-simulation.js.map +1 -1
  38. package/dist/src/layouts/forceatlas2.d.ts.map +1 -1
  39. package/dist/src/layouts/forceatlas2.js +0 -1
  40. package/dist/src/layouts/forceatlas2.js.map +1 -1
  41. package/dist/src/layouts/fruchterman-reingold.d.ts.map +1 -1
  42. package/dist/src/layouts/fruchterman-reingold.js +0 -1
  43. package/dist/src/layouts/fruchterman-reingold.js.map +1 -1
  44. package/dist/src/layouts/repulsion-grid.d.ts +1 -3
  45. package/dist/src/layouts/repulsion-grid.d.ts.map +1 -1
  46. package/dist/src/layouts/repulsion-grid.js +1 -6
  47. package/dist/src/layouts/repulsion-grid.js.map +1 -1
  48. package/dist/src/layouts/spring-electrical.d.ts.map +1 -1
  49. package/dist/src/layouts/spring-electrical.js +0 -1
  50. package/dist/src/layouts/spring-electrical.js.map +1 -1
  51. package/dist/src/managed.d.ts +11 -0
  52. package/dist/src/managed.d.ts.map +1 -0
  53. package/dist/src/managed.js +129 -0
  54. package/dist/src/managed.js.map +1 -0
  55. package/dist/src/node/index.d.ts +11 -0
  56. package/dist/src/node/index.d.ts.map +1 -1
  57. package/dist/src/node/index.js +21 -0
  58. package/dist/src/node/index.js.map +1 -1
  59. package/dist/src/primitives/grid-pyramid.d.ts +16 -15
  60. package/dist/src/primitives/grid-pyramid.d.ts.map +1 -1
  61. package/dist/src/primitives/grid-pyramid.js +20 -28
  62. package/dist/src/primitives/grid-pyramid.js.map +1 -1
  63. package/dist/src/types/accelerator.d.ts +2 -0
  64. package/dist/src/types/accelerator.d.ts.map +1 -1
  65. package/dist/src/types/managed.d.ts +81 -0
  66. package/dist/src/types/managed.d.ts.map +1 -0
  67. package/dist/src/types/managed.js +7 -0
  68. package/dist/src/types/managed.js.map +1 -0
  69. package/dist/src/wgsl/bc-forward.wgsl.d.ts +1 -1
  70. package/dist/src/wgsl/bc-forward.wgsl.js +1 -1
  71. package/dist/src/wgsl/closeness-level.wgsl.d.ts +37 -0
  72. package/dist/src/wgsl/closeness-level.wgsl.d.ts.map +1 -0
  73. package/dist/src/wgsl/closeness-level.wgsl.js +204 -0
  74. package/dist/src/wgsl/closeness-level.wgsl.js.map +1 -0
  75. package/dist/src/wgsl/closeness-rowsum.wgsl.d.ts +11 -0
  76. package/dist/src/wgsl/closeness-rowsum.wgsl.d.ts.map +1 -0
  77. package/dist/src/wgsl/closeness-rowsum.wgsl.js +42 -0
  78. package/dist/src/wgsl/closeness-rowsum.wgsl.js.map +1 -0
  79. package/dist/src/wgsl/grid-centroid-hub.wgsl.d.ts +2 -2
  80. package/dist/src/wgsl/grid-centroid-hub.wgsl.js +2 -2
  81. package/dist/webgpu-graph-algorithms.js +142 -15586
  82. package/dist/webgpu-graph-algorithms.js.map +1 -1
  83. package/package.json +10 -4
  84. package/src/accelerator.ts +5 -3
  85. package/src/algorithms/all-pairs.ts +86 -56
  86. package/src/algorithms/betweenness.ts +2 -2
  87. package/src/algorithms/closeness.ts +353 -256
  88. package/src/browser/index.ts +37 -0
  89. package/src/constants.ts +13 -3
  90. package/src/kernels.ts +65 -36
  91. package/src/layouts/force-simulation.ts +0 -1
  92. package/src/layouts/forceatlas2.ts +0 -1
  93. package/src/layouts/fruchterman-reingold.ts +0 -1
  94. package/src/layouts/repulsion-grid.ts +2 -7
  95. package/src/layouts/spring-electrical.ts +0 -1
  96. package/src/managed.ts +172 -0
  97. package/src/node/index.ts +36 -0
  98. package/src/primitives/grid-pyramid.ts +29 -41
  99. package/src/types/accelerator.ts +2 -0
  100. package/src/types/managed.ts +86 -0
  101. package/src/wgsl/bc-forward.wgsl.ts +1 -1
  102. package/src/wgsl/closeness-level.wgsl.ts +203 -0
  103. package/src/wgsl/closeness-rowsum.wgsl.ts +41 -0
  104. package/src/wgsl/grid-centroid-hub.wgsl.ts +2 -2
  105. package/dist/chunks/context-BZY6SMsM.js +0 -3615
  106. package/dist/chunks/context-BZY6SMsM.js.map +0 -1
  107. package/dist/src/wgsl/closeness-reduce.wgsl.d.ts +0 -20
  108. package/dist/src/wgsl/closeness-reduce.wgsl.d.ts.map +0 -1
  109. package/dist/src/wgsl/closeness-reduce.wgsl.js +0 -69
  110. package/dist/src/wgsl/closeness-reduce.wgsl.js.map +0 -1
  111. package/dist/src/wgsl/closeness-sweep.wgsl.d.ts +0 -22
  112. package/dist/src/wgsl/closeness-sweep.wgsl.d.ts.map +0 -1
  113. package/dist/src/wgsl/closeness-sweep.wgsl.js +0 -106
  114. package/dist/src/wgsl/closeness-sweep.wgsl.js.map +0 -1
  115. package/src/wgsl/closeness-reduce.wgsl.ts +0 -68
  116. package/src/wgsl/closeness-sweep.wgsl.ts +0 -105
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@graphty/webgpu-graph-algorithms",
3
- "version": "0.6.26",
3
+ "version": "0.6.28",
4
4
  "description": "WebGPU-accelerated graph algorithms and layouts over the @graphty/graph-format snapshot, for Node (Dawn) and browsers",
5
5
  "author": "Adam Powers <apowers@ato.ms>",
6
6
  "type": "module",
@@ -21,6 +21,12 @@
21
21
  "types": "./dist/node.d.ts",
22
22
  "import": "./dist/node.js",
23
23
  "default": "./dist/node.js"
24
+ },
25
+ "./acquire": {
26
+ "types": "./dist/acquire.d.ts",
27
+ "browser": "./dist/browser.js",
28
+ "node": "./dist/node.js",
29
+ "default": "./dist/browser.js"
24
30
  }
25
31
  },
26
32
  "sideEffects": false,
@@ -61,7 +67,7 @@
61
67
  "homepage": "https://github.com/graphty-org/graphty-monorepo/tree/master/webgpu-graph-algorithms#readme",
62
68
  "dependencies": {
63
69
  "@webgpu/types": "^0.1.72",
64
- "@graphty/graph-format": "^1.2.6"
70
+ "@graphty/graph-format": "^1.3.0"
65
71
  },
66
72
  "peerDependencies": {
67
73
  "@graphty/algorithms": "^1.0.0 || ^2.0.0 || ^3.0.0",
@@ -95,8 +101,8 @@
95
101
  "vite": "^7.0.5",
96
102
  "vitest": "4.1.11",
97
103
  "webgpu": "0.4.0",
98
- "@graphty/layout": "^2.0.9",
99
- "@graphty/algorithms": "^3.1.8"
104
+ "@graphty/layout": "^2.2.0",
105
+ "@graphty/algorithms": "^3.3.0"
100
106
  },
101
107
  "scripts": {
102
108
  "build": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.build.json",
@@ -175,6 +175,7 @@ export function createAccelerator(ctx: GpuContext, options?: AcceleratorOptions)
175
175
  const frozen = freezeOptions(options);
176
176
  return {
177
177
  kind: "webgpu",
178
+ harmonicCloseness: true,
178
179
  ctx,
179
180
  options: frozen,
180
181
  /**
@@ -357,10 +358,11 @@ export function createAccelerator(ctx: GpuContext, options?: AcceleratorOptions)
357
358
  );
358
359
  },
359
360
  /**
360
- * Closeness centrality on the device (spec 8.4; P8-T13): the bit-parallel multi-source sweep, or one `sssp`
361
- * per source when `weighted`. `maxIterations` / `tolerance` are refused when defined (P8 PD-25).
361
+ * Closeness centrality on the device (spec 8.4): the all-pairs sweep with a row sum on small and
362
+ * weighted graphs, the bit-parallel multi-source search otherwise, one `sssp` per source for a weighted graph
363
+ * above the all-pairs ceiling. `maxIterations` / `tolerance` are refused when defined (P8 PD-25).
362
364
  * @param gs - the snapshot
363
- * @param o - `weighted`, and a sampled run's `sources` (undirected snapshots only)
365
+ * @param o - `weighted`, `harmonic` (exact runs), and a sampled run's `sources` (undirected snapshots only)
364
366
  * @returns the f32 scores with `precision: "f32"` (spec 9.7) and `sourcesUsed`
365
367
  */
366
368
  async closenessCentrality(gs: GraphSnapshot, o?: ClosenessAcceleratorOptions): Promise<GpuClosenessResult> {
@@ -34,14 +34,18 @@ import { APSP_PARAMS, FILL_PARAMS, graphBindings, graphOverrides, kernelSpec } f
34
34
  import { assertWholeCore } from "../primitives/core-shape.js";
35
35
  import { assertDeviceComputes } from "../primitives/verify.js";
36
36
  import { type ApspOptions, type GpuApspResult } from "../types/all-pairs.js";
37
+ import { type Binding } from "../types/memory.js";
37
38
  import { type GpuRunOptions } from "../types/run.js";
38
- import { algorithmScope } from "./scope.js";
39
+ import { type AlgorithmScope, algorithmScope } from "./scope.js";
39
40
  import { aborted, bindingOf, checkDest } from "./sssp.js";
40
41
 
41
42
  const ALGORITHM = "allPairsShortestPath";
42
43
 
43
- /** The rounds one submit holds by default: three dispatches per round. */
44
- const DEFAULT_ROUNDS_PER_SUBMIT = Math.floor(APSP_MAX_DISPATCHES_PER_SUBMIT / 3);
44
+ /**
45
+ * The rounds one submit holds by default: three dispatches per round.
46
+ * @internal
47
+ */
48
+ export const DEFAULT_ROUNDS_PER_SUBMIT = Math.floor(APSP_MAX_DISPATCHES_PER_SUBMIT / 3);
45
49
 
46
50
  /**
47
51
  * The knobs the tests need and nothing public offers.
@@ -79,6 +83,83 @@ export function allPairsCeiling(limits: Pick<GPUSupportedLimits, "maxStorageBuff
79
83
  return { maxNodes, limit, limitName };
80
84
  }
81
85
 
86
+ /**
87
+ * The blocked sweep itself, shared with closeness's all-pairs route: the `n x n` matrix leased from `scope`, filled,
88
+ * initialised from the arcs and swept to the shortest distances, every submit awaited; the matrix stays on the device.
89
+ * The caller has checked the ceiling and the weights.
90
+ * @internal
91
+ * @param ctx - the context
92
+ * @param s - the snapshot (n > 0)
93
+ * @param scope - the scope that leases the matrix and the params records (`min(roundsPerSubmit, blocks) + 2` slots)
94
+ * @param weighted - true sums the snapshot's weights, false counts hops
95
+ * @param roundsPerSubmit - the rounds recorded per submit
96
+ * @param options - signal / onProgress (rounds done of `ceil(n / 32)`)
97
+ * @param algorithm - the name errors carry
98
+ * @returns the matrix binding (`4 n^2` bytes, row-major, `dist[i * n + j]` from `i` to `j`)
99
+ */
100
+ export async function sweepAllPairs(
101
+ ctx: GpuContext,
102
+ s: GraphSnapshot,
103
+ scope: AlgorithmScope,
104
+ weighted: boolean,
105
+ roundsPerSubmit: number,
106
+ options: GpuRunOptions | undefined,
107
+ algorithm: string,
108
+ ): Promise<Binding> {
109
+ const n = s.nodeCount;
110
+ const core = ctx.residency.core(s);
111
+ assertWholeCore(core, s.arcCount, ctx.caps.limits.maxStorageBufferBindingSize, algorithm);
112
+ const blocks = Math.ceil(n / APSP_TILE);
113
+ const wg = ctx.workgroupSize;
114
+ const bytes = 4 * n * n;
115
+ const matrix = bindingOf(scope.scratch(bytes, "dist"), bytes);
116
+ await ctx.allocator.check();
117
+ const weightsBinding = weighted ? undefined : null;
118
+ const fill = await ctx.pipelines.kernel(kernelSpec("fill"));
119
+ const init = await ctx.pipelines.kernel(kernelSpec("apsp-init", graphOverrides(core, null, weightsBinding)));
120
+ const phases = await Promise.all(
121
+ [0, 1, 2].map((phase) => ctx.pipelines.kernel(kernelSpec("apsp-fw", { PHASE: phase }))),
122
+ );
123
+ const graph = graphBindings(core, null, weightsBinding);
124
+ const others = blocks - 1;
125
+ const phasePlans = [plan2d(1, ctx.caps), plan2d(2 * others, ctx.caps), plan2d(others * others, ctx.caps)];
126
+
127
+ for (let first = 0; first < blocks; first += roundsPerSubmit) {
128
+ const batch = new CommandBatch(ctx, `${algorithm}/sweep`);
129
+ const pass = batch.pass("apsp");
130
+ if (first === 0) {
131
+ const fillParams = scope.params(FILL_PARAMS, { count: n * n, value: F32_INF_BITS, mode: 0, pad0: 0 });
132
+ fill.dispatch(pass, fill.bind({ dst: matrix, P: fillParams.binding }), plan1d(n * n, wg, ctx.caps), [
133
+ fillParams.offset,
134
+ ]);
135
+ const initParams = scope.params(APSP_PARAMS, { n, round: 0, blocks, infBits: F32_INF_BITS });
136
+ init.dispatch(pass, init.bind({ ...graph, dist: matrix, P: initParams.binding }), plan1d(n, wg, ctx.caps), [
137
+ initParams.offset,
138
+ ]);
139
+ }
140
+ const last = Math.min(first + roundsPerSubmit, blocks);
141
+ for (let round = first; round < last; round++) {
142
+ const params = scope.params(APSP_PARAMS, { n, round, blocks, infBits: F32_INF_BITS });
143
+ for (let phase = 0; phase < 3; phase++) {
144
+ const kernel = phases[phase];
145
+ kernel.dispatch(pass, kernel.bind({ dist: matrix, P: params.binding }), phasePlans[phase], [
146
+ params.offset,
147
+ ]);
148
+ }
149
+ }
150
+ batch.endPass();
151
+ scope.flush();
152
+ const submitted = batch.submit();
153
+ await submitted.readback;
154
+ ctx.assertReady();
155
+ options?.onProgress?.(last, blocks);
156
+ if (options?.signal?.aborted) {
157
+ throw aborted(algorithm, submitted.id);
158
+ }
159
+ }
160
+ return matrix;
161
+ }
162
+
82
163
  /**
83
164
  * All-pairs shortest paths with the test knobs; `allPairsShortestPath` is this with an empty tuning.
84
165
  * @internal
@@ -145,61 +226,10 @@ export async function allPairsWithTuning(
145
226
  return { dist, n };
146
227
  }
147
228
  await assertDeviceComputes(ctx);
148
- const core = ctx.residency.core(s);
149
- assertWholeCore(core, s.arcCount, ctx.caps.limits.maxStorageBufferBindingSize, ALGORITHM);
150
- const blocks = Math.ceil(n / APSP_TILE);
151
- const scope = algorithmScope(ctx, ALGORITHM, Math.min(roundsPerSubmit, blocks) + 2);
229
+ const scope = algorithmScope(ctx, ALGORITHM, Math.min(roundsPerSubmit, Math.ceil(n / APSP_TILE)) + 2);
152
230
  try {
153
- const wg = ctx.workgroupSize;
231
+ const matrix = await sweepAllPairs(ctx, s, scope, weighted, roundsPerSubmit, options, ALGORITHM);
154
232
  const bytes = 4 * n * n;
155
- const matrix = bindingOf(scope.scratch(bytes, "dist"), bytes);
156
- await ctx.allocator.check();
157
- const weightsBinding = weighted ? undefined : null;
158
- const fill = await ctx.pipelines.kernel(kernelSpec("fill"));
159
- const init = await ctx.pipelines.kernel(kernelSpec("apsp-init", graphOverrides(core, null, weightsBinding)));
160
- const phases = await Promise.all(
161
- [0, 1, 2].map((phase) => ctx.pipelines.kernel(kernelSpec("apsp-fw", { PHASE: phase }))),
162
- );
163
- const graph = graphBindings(core, null, weightsBinding);
164
- const others = blocks - 1;
165
- const phasePlans = [plan2d(1, ctx.caps), plan2d(2 * others, ctx.caps), plan2d(others * others, ctx.caps)];
166
-
167
- for (let first = 0; first < blocks; first += roundsPerSubmit) {
168
- const batch = new CommandBatch(ctx, `${ALGORITHM}/sweep`);
169
- const pass = batch.pass("apsp");
170
- if (first === 0) {
171
- const fillParams = scope.params(FILL_PARAMS, { count: n * n, value: F32_INF_BITS, mode: 0, pad0: 0 });
172
- fill.dispatch(pass, fill.bind({ dst: matrix, P: fillParams.binding }), plan1d(n * n, wg, ctx.caps), [
173
- fillParams.offset,
174
- ]);
175
- const initParams = scope.params(APSP_PARAMS, { n, round: 0, blocks, infBits: F32_INF_BITS });
176
- init.dispatch(
177
- pass,
178
- init.bind({ ...graph, dist: matrix, P: initParams.binding }),
179
- plan1d(n, wg, ctx.caps),
180
- [initParams.offset],
181
- );
182
- }
183
- const last = Math.min(first + roundsPerSubmit, blocks);
184
- for (let round = first; round < last; round++) {
185
- const params = scope.params(APSP_PARAMS, { n, round, blocks, infBits: F32_INF_BITS });
186
- for (let phase = 0; phase < 3; phase++) {
187
- const kernel = phases[phase];
188
- kernel.dispatch(pass, kernel.bind({ dist: matrix, P: params.binding }), phasePlans[phase], [
189
- params.offset,
190
- ]);
191
- }
192
- }
193
- batch.endPass();
194
- scope.flush();
195
- const submitted = batch.submit();
196
- await submitted.readback;
197
- ctx.assertReady();
198
- options?.onProgress?.(last, blocks);
199
- if (options?.signal?.aborted) {
200
- throw aborted(ALGORITHM, submitted.id);
201
- }
202
- }
203
233
  await ctx.readback.read(matrix.buffer, bytes, dist);
204
234
  ctx.assertReady();
205
235
  return { dist, n };
@@ -30,7 +30,7 @@
30
30
  *
31
31
  * The batch size k is planned from the device limits at 16 bytes per (node, source) -- the three arrays design 4.7
32
32
  * counts plus the 4-byte log entry it omits -- as `min(floor(maxStorageBufferBindingSize / 4n), floor(0.25 x
33
- * maxBufferSize / 16n), 64)`, at least 1 (`planBatchSize`); a graph whose single source does not fit one binding is
33
+ * maxBufferSize / 16n), BC_MAX_BATCH)` (256), at least 1 (`planBatchSize`); a graph whose single source does not fit one binding is
34
34
  * E_TOO_LARGE. The forward form is chosen per batch: the first batch runs `bc-forward`; a later one runs the
35
35
  * edge-parallel form when the previous batch's level count -- the MAXIMUM depth over its sources, the only depth
36
36
  * figure the host has, which over-estimates the median of design 8.4's rule and so errs toward the frontier form --
@@ -153,7 +153,7 @@ interface RawBetweenness {
153
153
 
154
154
  /**
155
155
  * The source batch size (design 8.4, 10.1): `min(floor(maxStorageBufferBindingSize / 4n), floor(0.25 x maxBufferSize /
156
- * 16n), 64, remaining)`, at least 1. Each of the four `n x k` arrays is one binding of `4 n k` bytes, and the batch's
156
+ * 16n), BC_MAX_BATCH, remaining)`, at least 1. Each of the four `n x k` arrays is one binding of `4 n k` bytes, and the batch's
157
157
  * four together stay inside a quarter of the largest buffer.
158
158
  * @internal
159
159
  * @param n - the vertex count (>= 1)