@graphty/webgpu-graph-algorithms 0.2.0 → 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.
- package/README.md +68 -25
- package/dist/browser.js +1 -1
- package/dist/chunks/{context-E6iKaeuJ.js → context-CRbw2Wyo.js} +178 -19
- package/dist/chunks/{context-E6iKaeuJ.js.map → context-CRbw2Wyo.js.map} +1 -1
- package/dist/node.js +1 -1
- package/dist/src/accelerator.d.ts +9 -6
- package/dist/src/accelerator.d.ts.map +1 -1
- package/dist/src/accelerator.js +85 -6
- package/dist/src/accelerator.js.map +1 -1
- package/dist/src/algorithms/components.d.ts +30 -0
- package/dist/src/algorithms/components.d.ts.map +1 -0
- package/dist/src/algorithms/components.js +300 -0
- package/dist/src/algorithms/components.js.map +1 -0
- package/dist/src/algorithms/pagerank.d.ts +39 -0
- package/dist/src/algorithms/pagerank.d.ts.map +1 -0
- package/dist/src/algorithms/pagerank.js +298 -0
- package/dist/src/algorithms/pagerank.js.map +1 -0
- package/dist/src/algorithms/power-iteration.d.ts +109 -0
- package/dist/src/algorithms/power-iteration.d.ts.map +1 -0
- package/dist/src/algorithms/power-iteration.js +206 -0
- package/dist/src/algorithms/power-iteration.js.map +1 -0
- package/dist/src/algorithms/scope.d.ts +26 -0
- package/dist/src/algorithms/scope.d.ts.map +1 -0
- package/dist/src/algorithms/scope.js +41 -0
- package/dist/src/algorithms/scope.js.map +1 -0
- package/dist/src/algorithms/spectral.d.ts +50 -0
- package/dist/src/algorithms/spectral.d.ts.map +1 -0
- package/dist/src/algorithms/spectral.js +247 -0
- package/dist/src/algorithms/spectral.js.map +1 -0
- package/dist/src/index.d.ts +4 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +4 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/kernel/dispatch.d.ts +4 -1
- package/dist/src/kernel/dispatch.d.ts.map +1 -1
- package/dist/src/kernel/dispatch.js +12 -5
- package/dist/src/kernel/dispatch.js.map +1 -1
- package/dist/src/kernels.d.ts +20 -4
- package/dist/src/kernels.d.ts.map +1 -1
- package/dist/src/kernels.js +172 -2
- package/dist/src/kernels.js.map +1 -1
- package/dist/src/memory/residency.d.ts.map +1 -1
- package/dist/src/memory/residency.js +164 -11
- package/dist/src/memory/residency.js.map +1 -1
- package/dist/src/primitives/core-shape.d.ts +41 -0
- package/dist/src/primitives/core-shape.d.ts.map +1 -0
- package/dist/src/primitives/core-shape.js +89 -0
- package/dist/src/primitives/core-shape.js.map +1 -0
- package/dist/src/primitives/segmented-reduce.d.ts.map +1 -1
- package/dist/src/primitives/segmented-reduce.js +4 -30
- package/dist/src/primitives/segmented-reduce.js.map +1 -1
- package/dist/src/primitives/spmv.d.ts +56 -0
- package/dist/src/primitives/spmv.d.ts.map +1 -0
- package/dist/src/primitives/spmv.js +101 -0
- package/dist/src/primitives/spmv.js.map +1 -0
- package/dist/src/types/accelerator.d.ts +14 -2
- package/dist/src/types/accelerator.d.ts.map +1 -1
- package/dist/src/types/algorithms.d.ts +73 -0
- package/dist/src/types/algorithms.d.ts.map +1 -0
- package/dist/src/types/algorithms.js +17 -0
- package/dist/src/types/algorithms.js.map +1 -0
- package/dist/src/wgsl/pr-finalize.wgsl.d.ts +11 -0
- package/dist/src/wgsl/pr-finalize.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/pr-finalize.wgsl.js +36 -0
- package/dist/src/wgsl/pr-finalize.wgsl.js.map +1 -0
- package/dist/src/wgsl/pr-scale.wgsl.d.ts +14 -0
- package/dist/src/wgsl/pr-scale.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/pr-scale.wgsl.js +48 -0
- package/dist/src/wgsl/pr-scale.wgsl.js.map +1 -0
- package/dist/src/wgsl/spmv-pull.wgsl.d.ts +15 -0
- package/dist/src/wgsl/spmv-pull.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/spmv-pull.wgsl.js +47 -0
- package/dist/src/wgsl/spmv-pull.wgsl.js.map +1 -0
- package/dist/src/wgsl/wcc-compress.wgsl.d.ts +9 -0
- package/dist/src/wgsl/wcc-compress.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/wcc-compress.wgsl.js +26 -0
- package/dist/src/wgsl/wcc-compress.wgsl.js.map +1 -0
- package/dist/src/wgsl/wcc-link-edges.wgsl.d.ts +13 -0
- package/dist/src/wgsl/wcc-link-edges.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/wcc-link-edges.wgsl.js +46 -0
- package/dist/src/wgsl/wcc-link-edges.wgsl.js.map +1 -0
- package/dist/src/wgsl/wcc-link-sample.wgsl.d.ts +11 -0
- package/dist/src/wgsl/wcc-link-sample.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/wcc-link-sample.wgsl.js +45 -0
- package/dist/src/wgsl/wcc-link-sample.wgsl.js.map +1 -0
- package/dist/src/wgsl/wcc-sample.wgsl.d.ts +10 -0
- package/dist/src/wgsl/wcc-sample.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/wcc-sample.wgsl.js +18 -0
- package/dist/src/wgsl/wcc-sample.wgsl.js.map +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/webgpu-graph-algorithms.js +1550 -29
- package/dist/webgpu-graph-algorithms.js.map +1 -1
- package/package.json +3 -3
- package/src/accelerator.ts +101 -7
- package/src/algorithms/components.ts +348 -0
- package/src/algorithms/pagerank.ts +343 -0
- package/src/algorithms/power-iteration.ts +278 -0
- package/src/algorithms/scope.ts +52 -0
- package/src/algorithms/spectral.ts +300 -0
- package/src/index.ts +18 -0
- package/src/kernel/dispatch.ts +12 -5
- package/src/kernels.ts +206 -5
- package/src/memory/residency.ts +200 -11
- package/src/primitives/core-shape.ts +103 -0
- package/src/primitives/segmented-reduce.ts +4 -36
- package/src/primitives/spmv.ts +155 -0
- package/src/types/accelerator.ts +28 -2
- package/src/types/algorithms.ts +82 -0
- package/src/wgsl/pr-finalize.wgsl.ts +36 -0
- package/src/wgsl/pr-scale.wgsl.ts +48 -0
- package/src/wgsl/spmv-pull.wgsl.ts +47 -0
- package/src/wgsl/wcc-compress.wgsl.ts +26 -0
- package/src/wgsl/wcc-link-edges.wgsl.ts +46 -0
- package/src/wgsl/wcc-link-sample.wgsl.ts +45 -0
- package/src/wgsl/wcc-sample.wgsl.ts +18 -0
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HITS, eigenvector centrality and Katz centrality on the device (spec 3.3 lines 794-796, 8.2 lines 2603-2605):
|
|
3
|
+
* three entry points over the ONE power-iteration driver, differing only in the adjacency the pull walks, the
|
|
4
|
+
* normaliser and the coefficients. Eigenvector is the L2-normalised iteration over the forward core; Katz is
|
|
5
|
+
* `alpha * A^T x + beta` over the reverse view with no per-iteration normaliser; HITS alternates the two pulls with
|
|
6
|
+
* sum normalisation, `a(i) = A^T norm(h(i-1))` and `h(i) = A norm(a(i-1))` (the CPU package's recurrence, which
|
|
7
|
+
* design 9.7 makes the parity target), as TWO interleaved chains of the driver -- the reverse-first chain from the
|
|
8
|
+
* hub seed and the forward-first chain from the authority seed -- so a call is still two runs and two readbacks.
|
|
9
|
+
* Every result is normalised ONCE on the host after the readback (sum for HITS, L2 for the other two), matching
|
|
10
|
+
* the f64 oracle; the per-iteration normaliser never leaves the device (PD-10).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { type F32, type GraphSnapshot } from "@graphty/graph-format";
|
|
14
|
+
|
|
15
|
+
import { type GpuContext } from "../context.js";
|
|
16
|
+
import { WebGpuGraphError } from "../errors.js";
|
|
17
|
+
import {
|
|
18
|
+
type EigenvectorOptions,
|
|
19
|
+
type GpuHitsResult,
|
|
20
|
+
type GpuScoresResult,
|
|
21
|
+
type HitsOptions,
|
|
22
|
+
type KatzOptions,
|
|
23
|
+
} from "../types/algorithms.js";
|
|
24
|
+
import { type Binding } from "../types/memory.js";
|
|
25
|
+
import { type GpuRunOptions } from "../types/run.js";
|
|
26
|
+
import { aborted, checkDest, coreOf, type PowerIterationRun, reverseOf, runPowerIteration } from "./power-iteration.js";
|
|
27
|
+
|
|
28
|
+
/** The three options every entry point shares, resolved. */
|
|
29
|
+
interface Resolved {
|
|
30
|
+
readonly n: number;
|
|
31
|
+
readonly maxIterations: number;
|
|
32
|
+
readonly tolerance: number;
|
|
33
|
+
readonly weights: Binding | null | undefined;
|
|
34
|
+
readonly dest: F32 | null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Resolves and validates the shared options and the run options' `dest` / `signal`.
|
|
39
|
+
* @param ctx - the context
|
|
40
|
+
* @param s - the snapshot
|
|
41
|
+
* @param options - maxIterations / tolerance / weighted plus dest / signal
|
|
42
|
+
* @param algorithm - the public name, for messages
|
|
43
|
+
* @returns the resolved options
|
|
44
|
+
*/
|
|
45
|
+
function resolve(
|
|
46
|
+
ctx: GpuContext,
|
|
47
|
+
s: GraphSnapshot,
|
|
48
|
+
options: (HitsOptions & GpuRunOptions) | undefined,
|
|
49
|
+
algorithm: string,
|
|
50
|
+
): Resolved {
|
|
51
|
+
ctx.assertReady();
|
|
52
|
+
const n = s.nodeCount;
|
|
53
|
+
const maxIterations = options?.maxIterations ?? 100;
|
|
54
|
+
const tolerance = options?.tolerance ?? 1e-6;
|
|
55
|
+
if (!Number.isInteger(maxIterations) || maxIterations < 1) {
|
|
56
|
+
throw new WebGpuGraphError("E_INVALID_ARGUMENT", `${algorithm}: maxIterations must be a positive integer`, {
|
|
57
|
+
argument: "maxIterations",
|
|
58
|
+
value: maxIterations,
|
|
59
|
+
expected: "a positive integer",
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
const dest = checkDest(options?.dest, n, algorithm);
|
|
63
|
+
if (options?.signal?.aborted) {
|
|
64
|
+
throw aborted(algorithm);
|
|
65
|
+
}
|
|
66
|
+
// weighted: false runs UNWEIGHTED on a weighted snapshot: the pull folds 1 per arc (prepareSpmvPull's null)
|
|
67
|
+
return { n, maxIterations, tolerance, weights: options?.weighted === false ? null : undefined, dest };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* A finite coefficient, or E_INVALID_ARGUMENT.
|
|
72
|
+
* @param value - the caller's value, or undefined for the default
|
|
73
|
+
* @param fallback - the default
|
|
74
|
+
* @param argument - the option name
|
|
75
|
+
* @returns the coefficient
|
|
76
|
+
*/
|
|
77
|
+
function finite(value: number | undefined, fallback: number, argument: string): number {
|
|
78
|
+
const resolved = value ?? fallback;
|
|
79
|
+
if (!Number.isFinite(resolved)) {
|
|
80
|
+
throw new WebGpuGraphError("E_INVALID_ARGUMENT", `katzCentrality: ${argument} must be a finite number`, {
|
|
81
|
+
argument,
|
|
82
|
+
value: resolved,
|
|
83
|
+
expected: "a finite number",
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
return resolved;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Copies the raw iterate into its destination divided by its norm (unchanged when the norm is not positive).
|
|
91
|
+
* @param raw - the driver's iterate
|
|
92
|
+
* @param dest - the caller's destination, or null for the raw array itself
|
|
93
|
+
* @param norm - "sum" (L1) or "l2"
|
|
94
|
+
* @returns the normalised scores
|
|
95
|
+
*/
|
|
96
|
+
function normalised(raw: F32, dest: F32 | null, norm: "sum" | "l2"): F32 {
|
|
97
|
+
let acc = 0;
|
|
98
|
+
for (const v of raw) {
|
|
99
|
+
acc += norm === "sum" ? Math.abs(v) : v * v;
|
|
100
|
+
}
|
|
101
|
+
const scale = norm === "sum" ? acc : Math.sqrt(acc);
|
|
102
|
+
const out = dest ?? raw;
|
|
103
|
+
for (let v = 0; v < raw.length; v++) {
|
|
104
|
+
out[v] = scale > 0 ? raw[v] / scale : raw[v];
|
|
105
|
+
}
|
|
106
|
+
return out;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* The score result of one run.
|
|
111
|
+
* @param run - the driver's run
|
|
112
|
+
* @param dest - the caller's destination, or null
|
|
113
|
+
* @param norm - the final normaliser
|
|
114
|
+
* @returns the result
|
|
115
|
+
*/
|
|
116
|
+
function scoresOf(run: PowerIterationRun, dest: F32 | null, norm: "sum" | "l2"): GpuScoresResult {
|
|
117
|
+
return {
|
|
118
|
+
scores: normalised(run.scores, dest, norm),
|
|
119
|
+
iterations: run.iterations,
|
|
120
|
+
converged: run.converged,
|
|
121
|
+
precision: "f32",
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* The n = 0 result: nothing to iterate.
|
|
127
|
+
* @param dest - the caller's destination, or null
|
|
128
|
+
* @param total - the run's progress denominator, reported to onProgress as both done and total
|
|
129
|
+
* @param onProgress - the caller's progress callback
|
|
130
|
+
* @returns the empty result
|
|
131
|
+
*/
|
|
132
|
+
function empty(
|
|
133
|
+
dest: F32 | null,
|
|
134
|
+
total: number,
|
|
135
|
+
onProgress: ((done: number, total: number) => void) | undefined,
|
|
136
|
+
): GpuScoresResult {
|
|
137
|
+
onProgress?.(total, total);
|
|
138
|
+
return { scores: dest ?? new Float32Array(0), iterations: 0, converged: true, precision: "f32" };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* The latest iterate of one kind across the two HITS chains: a chain holds its seed's kind after an even number of
|
|
143
|
+
* iterations m (in `scores`, index m) and the other kind's last iterate one back (in `previous`, index m - 1, never
|
|
144
|
+
* null on an alternating run; the fallback only satisfies the type).
|
|
145
|
+
* @param sameSeed - the chain seeded with the wanted kind
|
|
146
|
+
* @param otherSeed - the chain seeded with the other kind
|
|
147
|
+
* @returns the raw iterate with the higher index
|
|
148
|
+
*/
|
|
149
|
+
function latestOfKind(sameSeed: PowerIterationRun, otherSeed: PowerIterationRun): F32 {
|
|
150
|
+
const fromSame = sameSeed.iterationsRun % 2 === 0 ? sameSeed.iterationsRun : sameSeed.iterationsRun - 1;
|
|
151
|
+
const fromOther = otherSeed.iterationsRun % 2 === 1 ? otherSeed.iterationsRun : otherSeed.iterationsRun - 1;
|
|
152
|
+
const chain = fromSame >= fromOther ? sameSeed : otherSeed;
|
|
153
|
+
const index = Math.max(fromSame, fromOther);
|
|
154
|
+
return (index === chain.iterationsRun ? chain.scores : chain.previous) ?? chain.scores;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Eigenvector centrality on the device (spec 3.3, 8.2): the L2-normalised power iteration over the forward
|
|
159
|
+
* adjacency, converging on `delta < n * tolerance`; the scores are L2-normalised.
|
|
160
|
+
* @param ctx - the context whose device runs the kernels
|
|
161
|
+
* @param s - the snapshot (uploaded through ctx.residency, or found there)
|
|
162
|
+
* @param options - maxIterations 100 / tolerance 1e-6 / weighted true, plus dest / signal / onProgress
|
|
163
|
+
* @returns the scores, iterations, converged and precision
|
|
164
|
+
*/
|
|
165
|
+
export async function eigenvectorCentrality(
|
|
166
|
+
ctx: GpuContext,
|
|
167
|
+
s: GraphSnapshot,
|
|
168
|
+
options?: EigenvectorOptions & GpuRunOptions,
|
|
169
|
+
): Promise<GpuScoresResult> {
|
|
170
|
+
const algorithm = "eigenvectorCentrality";
|
|
171
|
+
const r = resolve(ctx, s, options, algorithm);
|
|
172
|
+
if (r.n === 0) {
|
|
173
|
+
return empty(r.dest, r.maxIterations, options?.onProgress);
|
|
174
|
+
}
|
|
175
|
+
const run = await runPowerIteration(ctx, r.n, {
|
|
176
|
+
normMode: 2,
|
|
177
|
+
adjacency: coreOf(ctx, s, algorithm),
|
|
178
|
+
alternate: null,
|
|
179
|
+
alpha: 1,
|
|
180
|
+
beta: 0,
|
|
181
|
+
uniformP: 0,
|
|
182
|
+
maxIterations: r.maxIterations,
|
|
183
|
+
tolerance: r.tolerance,
|
|
184
|
+
weights: r.weights,
|
|
185
|
+
label: algorithm,
|
|
186
|
+
signal: options?.signal,
|
|
187
|
+
onProgress: options?.onProgress,
|
|
188
|
+
});
|
|
189
|
+
return scoresOf(run, r.dest, "l2");
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Katz centrality on the device (spec 3.3, 8.2): `x = alpha * A^T x + beta` over the reverse adjacency with no
|
|
194
|
+
* per-iteration normaliser; the scores are L2-normalised once at the end.
|
|
195
|
+
* @param ctx - the context whose device runs the kernels
|
|
196
|
+
* @param s - the snapshot (uploaded through ctx.residency, or found there)
|
|
197
|
+
* @param options - alpha 0.1 / beta 1 / maxIterations 100 / tolerance 1e-6 / weighted true, plus dest / signal / onProgress
|
|
198
|
+
* @returns the scores, iterations, converged and precision
|
|
199
|
+
*/
|
|
200
|
+
export async function katzCentrality(
|
|
201
|
+
ctx: GpuContext,
|
|
202
|
+
s: GraphSnapshot,
|
|
203
|
+
options?: KatzOptions & GpuRunOptions,
|
|
204
|
+
): Promise<GpuScoresResult> {
|
|
205
|
+
const algorithm = "katzCentrality";
|
|
206
|
+
const alpha = finite(options?.alpha, 0.1, "alpha");
|
|
207
|
+
const beta = finite(options?.beta, 1, "beta");
|
|
208
|
+
const r = resolve(ctx, s, options, algorithm);
|
|
209
|
+
if (r.n === 0) {
|
|
210
|
+
return empty(r.dest, r.maxIterations, options?.onProgress);
|
|
211
|
+
}
|
|
212
|
+
const run = await runPowerIteration(ctx, r.n, {
|
|
213
|
+
normMode: 4,
|
|
214
|
+
adjacency: reverseOf(ctx, s),
|
|
215
|
+
alternate: null,
|
|
216
|
+
alpha,
|
|
217
|
+
beta,
|
|
218
|
+
uniformP: 1,
|
|
219
|
+
maxIterations: r.maxIterations,
|
|
220
|
+
tolerance: r.tolerance,
|
|
221
|
+
weights: r.weights,
|
|
222
|
+
label: algorithm,
|
|
223
|
+
signal: options?.signal,
|
|
224
|
+
onProgress: options?.onProgress,
|
|
225
|
+
});
|
|
226
|
+
return scoresOf(run, r.dest, "l2");
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* HITS on the device (spec 3.3, 8.2): `a(i) = A^T norm(h(i-1))` and `h(i) = A norm(a(i-1))` from uniform seeds,
|
|
231
|
+
* sum-normalised. Those two recurrences interleave into two independent chains, each alternating the reverse and
|
|
232
|
+
* the forward pull, so the driver runs twice: the chain seeded as hubs pulls reverse first and holds h(m) after an
|
|
233
|
+
* even number m of iterations and a(m) after an odd one; the chain seeded as authorities is the mirror image. Each
|
|
234
|
+
* chain stops at its own batch boundary, so the two can end on opposite parities holding the SAME kind; the result
|
|
235
|
+
* takes the latest iterate of each kind across both chains (a chain's last two iterates are one of each kind),
|
|
236
|
+
* which is h(m) and a(m) when both ran m iterations and never two vectors of one kind otherwise.
|
|
237
|
+
* `iterations` is the larger of the two and `converged` their conjunction. `dest`, when given, receives the hubs.
|
|
238
|
+
* Both vectors are sum-normalised.
|
|
239
|
+
* @param ctx - the context whose device runs the kernels
|
|
240
|
+
* @param s - the snapshot (uploaded through ctx.residency, or found there)
|
|
241
|
+
* @param options - maxIterations 100 / tolerance 1e-6 / weighted true, plus dest / signal / onProgress
|
|
242
|
+
* @returns hubs, authorities, iterations, converged and precision
|
|
243
|
+
*/
|
|
244
|
+
export async function hits(
|
|
245
|
+
ctx: GpuContext,
|
|
246
|
+
s: GraphSnapshot,
|
|
247
|
+
options?: HitsOptions & GpuRunOptions,
|
|
248
|
+
): Promise<GpuHitsResult> {
|
|
249
|
+
const algorithm = "hits";
|
|
250
|
+
const r = resolve(ctx, s, options, algorithm);
|
|
251
|
+
const total = 2 * r.maxIterations;
|
|
252
|
+
if (r.n === 0) {
|
|
253
|
+
const { scores, iterations, converged, precision } = empty(r.dest, total, options?.onProgress);
|
|
254
|
+
return { hubs: scores, authorities: new Float32Array(0), iterations, converged, precision };
|
|
255
|
+
}
|
|
256
|
+
const onProgress = options?.onProgress;
|
|
257
|
+
const shared = {
|
|
258
|
+
normMode: 1 as const,
|
|
259
|
+
alpha: 1,
|
|
260
|
+
beta: 0,
|
|
261
|
+
uniformP: 0,
|
|
262
|
+
maxIterations: r.maxIterations,
|
|
263
|
+
tolerance: r.tolerance,
|
|
264
|
+
weights: r.weights,
|
|
265
|
+
signal: options?.signal,
|
|
266
|
+
};
|
|
267
|
+
const forward = coreOf(ctx, s, algorithm);
|
|
268
|
+
const reverse = reverseOf(ctx, s);
|
|
269
|
+
const hubSeeded = await runPowerIteration(ctx, r.n, {
|
|
270
|
+
...shared,
|
|
271
|
+
adjacency: reverse,
|
|
272
|
+
alternate: forward,
|
|
273
|
+
label: `${algorithm}/hub-seeded`,
|
|
274
|
+
onProgress:
|
|
275
|
+
onProgress === undefined
|
|
276
|
+
? undefined
|
|
277
|
+
: (done) => {
|
|
278
|
+
onProgress(done, total);
|
|
279
|
+
},
|
|
280
|
+
});
|
|
281
|
+
const authoritySeeded = await runPowerIteration(ctx, r.n, {
|
|
282
|
+
...shared,
|
|
283
|
+
adjacency: forward,
|
|
284
|
+
alternate: reverse,
|
|
285
|
+
label: `${algorithm}/authority-seeded`,
|
|
286
|
+
onProgress:
|
|
287
|
+
onProgress === undefined
|
|
288
|
+
? undefined
|
|
289
|
+
: (done) => {
|
|
290
|
+
onProgress(r.maxIterations + done, total);
|
|
291
|
+
},
|
|
292
|
+
});
|
|
293
|
+
return {
|
|
294
|
+
hubs: normalised(latestOfKind(hubSeeded, authoritySeeded), r.dest, "sum"),
|
|
295
|
+
authorities: normalised(latestOfKind(authoritySeeded, hubSeeded), null, "sum"),
|
|
296
|
+
iterations: Math.max(hubSeeded.iterations, authoritySeeded.iterations),
|
|
297
|
+
converged: hubSeeded.converged && authoritySeeded.converged,
|
|
298
|
+
precision: "f32",
|
|
299
|
+
};
|
|
300
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -40,6 +40,11 @@ export type { PassTiming, Profiler } from "./kernel/profiler.js";
|
|
|
40
40
|
// ==================== algorithms (P1: the walking-skeleton diagnostic, spec 3.3)
|
|
41
41
|
export { degree } from "./algorithms/degree.js";
|
|
42
42
|
|
|
43
|
+
// ==================== algorithms (P7: the SpMV family and WCC, spec 8.2, 8.3)
|
|
44
|
+
export { connectedComponents } from "./algorithms/components.js";
|
|
45
|
+
export { pageRank, personalizedPageRank } from "./algorithms/pagerank.js";
|
|
46
|
+
export { eigenvectorCentrality, hits, katzCentrality } from "./algorithms/spectral.js";
|
|
47
|
+
|
|
43
48
|
// ==================== layouts and the accelerator (P3)
|
|
44
49
|
export { createAccelerator } from "./accelerator.js";
|
|
45
50
|
export { createForceAtlas2 } from "./layouts/forceatlas2.js";
|
|
@@ -67,6 +72,19 @@ export type {
|
|
|
67
72
|
SsspResultLike,
|
|
68
73
|
} from "./types/accelerator.js";
|
|
69
74
|
|
|
75
|
+
// ==================== types: the P7 algorithm results and option records (spec 3.3 lines 815-828, 9.7)
|
|
76
|
+
export type {
|
|
77
|
+
ComponentsOptions,
|
|
78
|
+
EigenvectorOptions,
|
|
79
|
+
GpuHitsResult,
|
|
80
|
+
GpuLabelResult,
|
|
81
|
+
GpuPageRankResult,
|
|
82
|
+
GpuScoresResult,
|
|
83
|
+
HitsOptions,
|
|
84
|
+
KatzOptions,
|
|
85
|
+
PageRankOptions,
|
|
86
|
+
} from "./types/algorithms.js";
|
|
87
|
+
|
|
70
88
|
// ==================== types: context and capabilities (P0 / P1)
|
|
71
89
|
export type {
|
|
72
90
|
AdapterInfoLike,
|
package/src/kernel/dispatch.ts
CHANGED
|
@@ -123,7 +123,9 @@ export function plan2d(groups: number, caps: PlanCaps): DispatchPlan {
|
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
/**
|
|
126
|
-
* P7 (spec 5.2): groups = min(
|
|
126
|
+
* P7 (spec 5.2): groups = min(ceil(items / wg), maxGroups ?? (caps.software ? 64 : 4096), the per-dimension limit) with the kernel looping by `stride = groups * wg`; items 0 -> { x: 0, stride: null }.
|
|
127
|
+
* The cap is the ONE performance default in src/ that may read `caps.software` (spec 2.4, 5.2): a grid-stride map is
|
|
128
|
+
* order-independent, so the result never depends on it and the same body serves both adapters.
|
|
127
129
|
* PLAN DECISION: `maxGroups?` is spelled `?: number` rather than the contract's `?: number | undefined` because the
|
|
128
130
|
* root ESLint rule no-duplicate-type-constituents rejects the explicit undefined on an optional parameter (the call
|
|
129
131
|
* signature is identical).
|
|
@@ -131,12 +133,17 @@ export function plan2d(groups: number, caps: PlanCaps): DispatchPlan {
|
|
|
131
133
|
* @param wg - the workgroup size
|
|
132
134
|
* @param caps - the capability table
|
|
133
135
|
* @param maxGroups - the group cap
|
|
136
|
+
* @returns the plan
|
|
134
137
|
*/
|
|
135
138
|
export function planGridStride(items: number, wg: number, caps: PlanCaps, maxGroups?: number): DispatchPlan {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
139
|
+
assertCount("items", items);
|
|
140
|
+
assertWorkgroupSize(wg);
|
|
141
|
+
if (items === 0) {
|
|
142
|
+
return { x: 0, y: 1, z: 1, items, stride: null };
|
|
143
|
+
}
|
|
144
|
+
const cap = Math.min(maxGroups ?? (caps.software ? 64 : 4096), perDimension(caps));
|
|
145
|
+
const groups = Math.min(Math.ceil(items / wg), cap);
|
|
146
|
+
return { x: groups, y: 1, z: 1, items, stride: groups * wg };
|
|
140
147
|
}
|
|
141
148
|
|
|
142
149
|
/**
|
package/src/kernels.ts
CHANGED
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
* test and PipelineCache.warm() iterate this table. P1-T4 lands degree, reduce, fill, fa2-repulsion-exact (K3)
|
|
7
7
|
* and fa2-speed-finalize (K4) together with every generated block of contract 3.10.2; P2-T2 lands
|
|
8
8
|
* segmented-reduce; P3-T2 adds fa2-stats-finalize (K1), fa2-attraction (K2), fa2-integrate (K5) and
|
|
9
|
-
* fa2-to-scene
|
|
9
|
+
* fa2-to-scene; M8b-T3 adds the seven P7 entries: spmv-pull, pr-scale, pr-finalize, wcc-link-sample,
|
|
10
|
+
* wcc-link-edges, wcc-compress and wcc-sample. This file is the only importer of src/wgsl/** (spec 3.2;
|
|
11
|
+
* test/layers.test.ts).
|
|
10
12
|
*/
|
|
11
13
|
|
|
12
14
|
import { STATE_HEADER_BYTES } from "./constants.js";
|
|
@@ -23,10 +25,17 @@ import { fa2SpeedFinalizeWgsl } from "./wgsl/fa2-speed-finalize.wgsl.js";
|
|
|
23
25
|
import { fa2StatsFinalizeWgsl } from "./wgsl/fa2-stats-finalize.wgsl.js";
|
|
24
26
|
import { fa2ToSceneWgsl } from "./wgsl/fa2-to-scene.wgsl.js";
|
|
25
27
|
import { fillWgsl } from "./wgsl/fill.wgsl.js";
|
|
28
|
+
import { prFinalizeWgsl } from "./wgsl/pr-finalize.wgsl.js";
|
|
29
|
+
import { prScaleWgsl } from "./wgsl/pr-scale.wgsl.js";
|
|
26
30
|
import { reduceWgsl } from "./wgsl/reduce.wgsl.js";
|
|
27
31
|
import { segmentedReduceWgsl } from "./wgsl/segmented-reduce.wgsl.js";
|
|
32
|
+
import { spmvPullWgsl } from "./wgsl/spmv-pull.wgsl.js";
|
|
33
|
+
import { wccCompressWgsl } from "./wgsl/wcc-compress.wgsl.js";
|
|
34
|
+
import { wccLinkEdgesWgsl } from "./wgsl/wcc-link-edges.wgsl.js";
|
|
35
|
+
import { wccLinkSampleWgsl } from "./wgsl/wcc-link-sample.wgsl.js";
|
|
36
|
+
import { wccSampleWgsl } from "./wgsl/wcc-sample.wgsl.js";
|
|
28
37
|
|
|
29
|
-
/** Every module id of P1-P3 (
|
|
38
|
+
/** Every module id of P1-P3 and P7 (later ids are appended, never renamed). */
|
|
30
39
|
export type KernelId =
|
|
31
40
|
| "degree"
|
|
32
41
|
| "reduce"
|
|
@@ -37,7 +46,14 @@ export type KernelId =
|
|
|
37
46
|
| "fa2-repulsion-exact"
|
|
38
47
|
| "fa2-speed-finalize"
|
|
39
48
|
| "fa2-integrate"
|
|
40
|
-
| "fa2-to-scene"
|
|
49
|
+
| "fa2-to-scene"
|
|
50
|
+
| "spmv-pull"
|
|
51
|
+
| "pr-scale"
|
|
52
|
+
| "pr-finalize"
|
|
53
|
+
| "wcc-link-sample"
|
|
54
|
+
| "wcc-link-edges"
|
|
55
|
+
| "wcc-compress"
|
|
56
|
+
| "wcc-sample";
|
|
41
57
|
|
|
42
58
|
/** One registry entry: everything of a WgslModuleSpec except the per-variant overrides and snippets. */
|
|
43
59
|
export interface KernelEntry {
|
|
@@ -52,7 +68,7 @@ export interface KernelEntry {
|
|
|
52
68
|
/** The snippet marker names the body carries (segmented-reduce: ["VALUE"]). */
|
|
53
69
|
readonly snippetSlots: readonly string[];
|
|
54
70
|
/** The phase the entry landed in (documentation and the compile-matrix filter). */
|
|
55
|
-
readonly phase: "P1" | "P2" | "P3";
|
|
71
|
+
readonly phase: "P1" | "P2" | "P3" | "P7";
|
|
56
72
|
}
|
|
57
73
|
|
|
58
74
|
// ---- the generated blocks (spec 5.3; contract 3.10.2): field order = byte order, offsets in the JSDoc
|
|
@@ -169,6 +185,64 @@ export const FA2_PARTIAL: UniformBlock = UniformBlock.define(
|
|
|
169
185
|
{ layout: "storage" },
|
|
170
186
|
);
|
|
171
187
|
|
|
188
|
+
/** `SpmvParams` (uniform, 32 B; spec 8.2): `n` @0 rows of the pull, the bound arc window `[arcBase, arcEnd)` @4 / @8 (0 and arcCount when not windowed), the grid-stride step `stride` @12, `alpha` @16, `beta` @20 (the `1 - alpha` term), `uniformP` @24 (the uniform personalization mass `1 / n`, 0 for a pure SpMV), `pad0` @28. */
|
|
189
|
+
export const SPMV_PARAMS: UniformBlock = UniformBlock.define("SpmvParams", [
|
|
190
|
+
["n", "u32"],
|
|
191
|
+
["arcBase", "u32"],
|
|
192
|
+
["arcEnd", "u32"],
|
|
193
|
+
["stride", "u32"],
|
|
194
|
+
["alpha", "f32"],
|
|
195
|
+
["beta", "f32"],
|
|
196
|
+
["uniformP", "f32"],
|
|
197
|
+
["pad0", "u32"],
|
|
198
|
+
]);
|
|
199
|
+
|
|
200
|
+
/** `PrParams` (uniform, 32 B; spec 8.2): `n` @0, `groups` @4 (the per-workgroup partial count the finalize folds), `iteration` @8 (1-based), `trackConvergence` @12 (1 records firstConverged), `convergeThreshold` @16 (`tolerance * n`, the design's `delta < tol * n`), `pad0` @20, `pad1` @24, `pad2` @28. */
|
|
201
|
+
export const PR_PARAMS: UniformBlock = UniformBlock.define("PrParams", [
|
|
202
|
+
["n", "u32"],
|
|
203
|
+
["groups", "u32"],
|
|
204
|
+
["iteration", "u32"],
|
|
205
|
+
["trackConvergence", "u32"],
|
|
206
|
+
["convergeThreshold", "f32"],
|
|
207
|
+
["pad0", "u32"],
|
|
208
|
+
["pad1", "u32"],
|
|
209
|
+
["pad2", "u32"],
|
|
210
|
+
]);
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* `PrPartial` (storage record, 32 B; spec 8.2). Element 0 is the HEADER, whose first 16 bytes are exactly the four
|
|
214
|
+
* fields the design names -- `danglingMass` @0, `delta` @4, `firstConvergedIteration` @8, `iteration` @12 -- plus
|
|
215
|
+
* `norm` @16, which M8b adds so HITS / eigenvector / Katz keep their normaliser on the device (PD-10). Element
|
|
216
|
+
* `1 + g` is workgroup g's partial: it uses `danglingMass`, `delta` and `norm` as three sums and leaves the two u32
|
|
217
|
+
* fields zero.
|
|
218
|
+
*/
|
|
219
|
+
export const PR_PARTIAL: UniformBlock = UniformBlock.define(
|
|
220
|
+
"PrPartial",
|
|
221
|
+
[
|
|
222
|
+
["danglingMass", "f32"],
|
|
223
|
+
["delta", "f32"],
|
|
224
|
+
["firstConverged", "u32"],
|
|
225
|
+
["iteration", "u32"],
|
|
226
|
+
["norm", "f32"],
|
|
227
|
+
["pad0", "f32"],
|
|
228
|
+
["pad1", "f32"],
|
|
229
|
+
["pad2", "f32"],
|
|
230
|
+
],
|
|
231
|
+
{ layout: "storage" },
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
/** `WccParams` (uniform, 32 B; spec 8.3): `n` @0, `items` @4 (rows for a sample round, edges for an edge round, 1024 for the sampler), `stride` @8, `r` @12 (the neighbour index of the sampled round, and the sampler's seed), `flagIndex` @16 (the changed word inside `comp`, PD-4), `giant` @20 (`U32_MAX` before the sample), `maxSteps` @24, `pad0` @28. */
|
|
235
|
+
export const WCC_PARAMS: UniformBlock = UniformBlock.define("WccParams", [
|
|
236
|
+
["n", "u32"],
|
|
237
|
+
["items", "u32"],
|
|
238
|
+
["stride", "u32"],
|
|
239
|
+
["r", "u32"],
|
|
240
|
+
["flagIndex", "u32"],
|
|
241
|
+
["giant", "u32"],
|
|
242
|
+
["maxSteps", "u32"],
|
|
243
|
+
["pad0", "u32"],
|
|
244
|
+
]);
|
|
245
|
+
|
|
172
246
|
// ---- the entries (contract 3.10.1; group 0 = graph, 1 = state, 2 = params, 3 = cold)
|
|
173
247
|
|
|
174
248
|
/**
|
|
@@ -381,13 +455,133 @@ const FA2_TO_SCENE: KernelEntry = {
|
|
|
381
455
|
phase: "P3",
|
|
382
456
|
};
|
|
383
457
|
|
|
458
|
+
/** `spmv-pull` (spec 8.10): the grid-stride pull SpMV over the reverse adjacency; HAS_PERSONALIZATION / USE_DANGLING plus the standard USE_PERM / HAS_WEIGHTS; 8 storage bindings (the design's count). */
|
|
459
|
+
const SPMV_PULL: KernelEntry = {
|
|
460
|
+
id: "spmv-pull",
|
|
461
|
+
body: spmvPullWgsl,
|
|
462
|
+
entryPoint: "spmv_pull",
|
|
463
|
+
bindings: GRAPH_SLOTS.concat(
|
|
464
|
+
decl(1, 0, "xNorm", "storage-ro", "array<f32>"),
|
|
465
|
+
decl(1, 1, "rankOut", "storage", "array<f32>"),
|
|
466
|
+
decl(1, 2, "personalization", "storage-ro", "array<f32>"),
|
|
467
|
+
decl(1, 3, "partials", "storage-ro", "array<PrPartial>"),
|
|
468
|
+
decl(2, 0, "P", "uniform", "SpmvParams"),
|
|
469
|
+
),
|
|
470
|
+
overrideDecls: [
|
|
471
|
+
{ name: "HAS_PERSONALIZATION", type: "bool", default: false },
|
|
472
|
+
{ name: "USE_DANGLING", type: "bool", default: false },
|
|
473
|
+
],
|
|
474
|
+
uniforms: [SPMV_PARAMS, PR_PARTIAL],
|
|
475
|
+
needs: [],
|
|
476
|
+
snippetSlots: [],
|
|
477
|
+
phase: "P7",
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
/** `pr-scale` (spec 8.2 dispatch (a), 8.10): the per-node normaliser and the per-workgroup partials; NORM_MODE 0 PageRank / 1 L1 pass / 2 L2 pass / 3 scale / 4 identity; 5 storage bindings; calls the reduction helpers. */
|
|
481
|
+
const PR_SCALE: KernelEntry = {
|
|
482
|
+
id: "pr-scale",
|
|
483
|
+
body: prScaleWgsl,
|
|
484
|
+
entryPoint: "pr_scale",
|
|
485
|
+
bindings: [
|
|
486
|
+
decl(1, 0, "rankIn", "storage-ro", "array<f32>"),
|
|
487
|
+
decl(1, 1, "rankPrev", "storage-ro", "array<f32>"),
|
|
488
|
+
decl(1, 2, "outWeightSum", "storage-ro", "array<f32>"),
|
|
489
|
+
decl(1, 3, "xNorm", "storage", "array<f32>"),
|
|
490
|
+
decl(1, 4, "partials", "storage", "array<PrPartial>"),
|
|
491
|
+
decl(2, 0, "P", "uniform", "PrParams"),
|
|
492
|
+
],
|
|
493
|
+
overrideDecls: [{ name: "NORM_MODE", type: "u32", default: 0 }],
|
|
494
|
+
uniforms: [PR_PARAMS, PR_PARTIAL],
|
|
495
|
+
needs: ["subgroups"],
|
|
496
|
+
snippetSlots: [],
|
|
497
|
+
phase: "P7",
|
|
498
|
+
};
|
|
499
|
+
|
|
500
|
+
/** `pr-finalize` (spec 8.2 dispatch (b), 8.10): the one-workgroup fold of the partials into the header at partials[0] and the firstConverged record; NORM_MODE 2 takes the square root; 1 storage binding; calls the reduction helpers. */
|
|
501
|
+
const PR_FINALIZE: KernelEntry = {
|
|
502
|
+
id: "pr-finalize",
|
|
503
|
+
body: prFinalizeWgsl,
|
|
504
|
+
entryPoint: "pr_finalize",
|
|
505
|
+
bindings: [decl(1, 0, "partials", "storage", "array<PrPartial>"), decl(2, 0, "P", "uniform", "PrParams")],
|
|
506
|
+
overrideDecls: [{ name: "NORM_MODE", type: "u32", default: 0 }],
|
|
507
|
+
uniforms: [PR_PARAMS, PR_PARTIAL],
|
|
508
|
+
needs: ["subgroups"],
|
|
509
|
+
snippetSlots: [],
|
|
510
|
+
phase: "P7",
|
|
511
|
+
};
|
|
512
|
+
|
|
513
|
+
/** `wcc-link-sample` (spec 8.3): Afforest's sampled link round over the CSR rows (every vertex links its r-th neighbour); the standard USE_PERM / HAS_WEIGHTS only; 5 storage bindings (the four graph slots and the atomic `comp`, whose word at P.flagIndex is the changed flag, PD-4). */
|
|
514
|
+
const WCC_LINK_SAMPLE: KernelEntry = {
|
|
515
|
+
id: "wcc-link-sample",
|
|
516
|
+
body: wccLinkSampleWgsl,
|
|
517
|
+
entryPoint: "wcc_link_sample",
|
|
518
|
+
bindings: GRAPH_SLOTS.concat(
|
|
519
|
+
decl(1, 0, "comp", "storage", "array<atomic<u32>>"),
|
|
520
|
+
decl(2, 0, "P", "uniform", "WccParams"),
|
|
521
|
+
),
|
|
522
|
+
overrideDecls: [],
|
|
523
|
+
uniforms: [WCC_PARAMS],
|
|
524
|
+
needs: [],
|
|
525
|
+
snippetSlots: [],
|
|
526
|
+
phase: "P7",
|
|
527
|
+
};
|
|
528
|
+
|
|
529
|
+
/** `wcc-link-edges` (spec 8.3, 8.10): Afforest's each-edge-once link round over the edge list; no overrides; 3 storage bindings (the design's count: edgeSrc, edgeDst and the atomic `comp` carrying the changed flag). */
|
|
530
|
+
const WCC_LINK_EDGES: KernelEntry = {
|
|
531
|
+
id: "wcc-link-edges",
|
|
532
|
+
body: wccLinkEdgesWgsl,
|
|
533
|
+
entryPoint: "wcc_link_edges",
|
|
534
|
+
bindings: [
|
|
535
|
+
decl(1, 0, "edgeSrc", "storage-ro", "array<u32>"),
|
|
536
|
+
decl(1, 1, "edgeDst", "storage-ro", "array<u32>"),
|
|
537
|
+
decl(1, 2, "comp", "storage", "array<atomic<u32>>"),
|
|
538
|
+
decl(2, 0, "P", "uniform", "WccParams"),
|
|
539
|
+
],
|
|
540
|
+
overrideDecls: [],
|
|
541
|
+
uniforms: [WCC_PARAMS],
|
|
542
|
+
needs: [],
|
|
543
|
+
snippetSlots: [],
|
|
544
|
+
phase: "P7",
|
|
545
|
+
};
|
|
546
|
+
|
|
547
|
+
/** `wcc-compress` (spec 8.3, 8.10): Afforest's bounded pointer-jumping compress; no overrides; 1 storage binding. */
|
|
548
|
+
const WCC_COMPRESS: KernelEntry = {
|
|
549
|
+
id: "wcc-compress",
|
|
550
|
+
body: wccCompressWgsl,
|
|
551
|
+
entryPoint: "wcc_compress",
|
|
552
|
+
bindings: [decl(1, 0, "comp", "storage", "array<atomic<u32>>"), decl(2, 0, "P", "uniform", "WccParams")],
|
|
553
|
+
overrideDecls: [],
|
|
554
|
+
uniforms: [WCC_PARAMS],
|
|
555
|
+
needs: [],
|
|
556
|
+
snippetSlots: [],
|
|
557
|
+
phase: "P7",
|
|
558
|
+
};
|
|
559
|
+
|
|
560
|
+
/** `wcc-sample` (spec 8.3, 8.10): the component-label sample of P.items pseudo-random vertices the host takes the mode of (PD-12); no overrides; 2 storage bindings. */
|
|
561
|
+
const WCC_SAMPLE: KernelEntry = {
|
|
562
|
+
id: "wcc-sample",
|
|
563
|
+
body: wccSampleWgsl,
|
|
564
|
+
entryPoint: "wcc_sample",
|
|
565
|
+
bindings: [
|
|
566
|
+
decl(1, 0, "comp", "storage", "array<atomic<u32>>"),
|
|
567
|
+
decl(1, 1, "hist", "storage", "array<u32>"),
|
|
568
|
+
decl(2, 0, "P", "uniform", "WccParams"),
|
|
569
|
+
],
|
|
570
|
+
overrideDecls: [],
|
|
571
|
+
uniforms: [WCC_PARAMS],
|
|
572
|
+
needs: [],
|
|
573
|
+
snippetSlots: [],
|
|
574
|
+
phase: "P7",
|
|
575
|
+
};
|
|
576
|
+
|
|
384
577
|
/**
|
|
385
578
|
* The entries by id, in dispatch order. PLAN DECISION: `KernelId` is declared in full (contract 3.10) while the
|
|
386
579
|
* entries landed phase by phase, so the table is built as a Partial record and exported below through the
|
|
387
580
|
* contract's `Readonly<Record<KernelId, KernelEntry>>` type by one assertion; the runtime membership check of
|
|
388
581
|
* `entryOf` is the E_INVALID_ARGUMENT the contract documents for a JS caller's unknown id. P1-T4 landed the five
|
|
389
582
|
* P1 entries, P2-T2 `"segmented-reduce"`, and P3-T2 `"fa2-stats-finalize"`, `"fa2-attraction"`, `"fa2-integrate"`
|
|
390
|
-
* and `"fa2-to-scene"
|
|
583
|
+
* and `"fa2-to-scene"`; M8b-T3 landed the seven P7 entries, so every member of `KernelId` is present and the
|
|
584
|
+
* assertion is exact.
|
|
391
585
|
*/
|
|
392
586
|
const REGISTRY: Readonly<Partial<Record<KernelId, KernelEntry>>> = Object.freeze({
|
|
393
587
|
degree: DEGREE,
|
|
@@ -400,6 +594,13 @@ const REGISTRY: Readonly<Partial<Record<KernelId, KernelEntry>>> = Object.freeze
|
|
|
400
594
|
"fa2-speed-finalize": FA2_SPEED_FINALIZE,
|
|
401
595
|
"fa2-integrate": FA2_INTEGRATE,
|
|
402
596
|
"fa2-to-scene": FA2_TO_SCENE,
|
|
597
|
+
"spmv-pull": SPMV_PULL,
|
|
598
|
+
"pr-scale": PR_SCALE,
|
|
599
|
+
"pr-finalize": PR_FINALIZE,
|
|
600
|
+
"wcc-link-sample": WCC_LINK_SAMPLE,
|
|
601
|
+
"wcc-link-edges": WCC_LINK_EDGES,
|
|
602
|
+
"wcc-compress": WCC_COMPRESS,
|
|
603
|
+
"wcc-sample": WCC_SAMPLE,
|
|
403
604
|
});
|
|
404
605
|
|
|
405
606
|
/** THE registry (spec 3.5): every entry, keyed by id. */
|