@graphty/webgpu-graph-algorithms 0.6.2 → 0.6.4
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 +62 -32
- package/dist/browser.js +1 -1
- package/dist/chunks/{context-BXqgCifx.js → context-Dvq-Cc6v.js} +71 -25
- package/dist/chunks/context-Dvq-Cc6v.js.map +1 -0
- package/dist/node.js +1 -1
- package/dist/src/accelerator.d.ts +8 -6
- package/dist/src/accelerator.d.ts.map +1 -1
- package/dist/src/accelerator.js +57 -6
- package/dist/src/accelerator.js.map +1 -1
- package/dist/src/algorithms/bellman-ford.d.ts +60 -0
- package/dist/src/algorithms/bellman-ford.d.ts.map +1 -0
- package/dist/src/algorithms/bellman-ford.js +301 -0
- package/dist/src/algorithms/bellman-ford.js.map +1 -0
- package/dist/src/algorithms/bfs.d.ts +67 -0
- package/dist/src/algorithms/bfs.d.ts.map +1 -0
- package/dist/src/algorithms/bfs.js +534 -0
- package/dist/src/algorithms/bfs.js.map +1 -0
- package/dist/src/algorithms/closeness.d.ts +53 -0
- package/dist/src/algorithms/closeness.d.ts.map +1 -0
- package/dist/src/algorithms/closeness.js +323 -0
- package/dist/src/algorithms/closeness.js.map +1 -0
- package/dist/src/algorithms/scope.d.ts +5 -3
- package/dist/src/algorithms/scope.d.ts.map +1 -1
- package/dist/src/algorithms/scope.js +3 -0
- package/dist/src/algorithms/scope.js.map +1 -1
- package/dist/src/algorithms/sssp.d.ts +71 -0
- package/dist/src/algorithms/sssp.d.ts.map +1 -0
- package/dist/src/algorithms/sssp.js +585 -0
- package/dist/src/algorithms/sssp.js.map +1 -0
- package/dist/src/constants.d.ts +12 -0
- package/dist/src/constants.d.ts.map +1 -1
- package/dist/src/constants.js +12 -0
- package/dist/src/constants.js.map +1 -1
- package/dist/src/index.d.ts +8 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +7 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/kernel/prelude.d.ts +4 -4
- package/dist/src/kernel/prelude.d.ts.map +1 -1
- package/dist/src/kernel/prelude.js +39 -5
- package/dist/src/kernel/prelude.js.map +1 -1
- package/dist/src/kernel/uniform-ring.d.ts +8 -0
- package/dist/src/kernel/uniform-ring.d.ts.map +1 -1
- package/dist/src/kernel/uniform-ring.js +13 -0
- package/dist/src/kernel/uniform-ring.js.map +1 -1
- package/dist/src/kernels.d.ts +44 -4
- package/dist/src/kernels.d.ts.map +1 -1
- package/dist/src/kernels.js +371 -3
- package/dist/src/kernels.js.map +1 -1
- package/dist/src/primitives/advance.d.ts +62 -0
- package/dist/src/primitives/advance.d.ts.map +1 -0
- package/dist/src/primitives/advance.js +95 -0
- package/dist/src/primitives/advance.js.map +1 -0
- package/dist/src/primitives/compact.d.ts +89 -0
- package/dist/src/primitives/compact.d.ts.map +1 -0
- package/dist/src/primitives/compact.js +233 -0
- package/dist/src/primitives/compact.js.map +1 -0
- package/dist/src/primitives/core-shape.d.ts +22 -1
- package/dist/src/primitives/core-shape.d.ts.map +1 -1
- package/dist/src/primitives/core-shape.js +33 -3
- package/dist/src/primitives/core-shape.js.map +1 -1
- package/dist/src/primitives/frontier.d.ts +156 -0
- package/dist/src/primitives/frontier.d.ts.map +1 -0
- package/dist/src/primitives/frontier.js +259 -0
- package/dist/src/primitives/frontier.js.map +1 -0
- package/dist/src/types/accelerator.d.ts +16 -7
- package/dist/src/types/accelerator.d.ts.map +1 -1
- package/dist/src/types/traversal.d.ts +53 -0
- package/dist/src/types/traversal.d.ts.map +1 -0
- package/dist/src/types/traversal.js +10 -0
- package/dist/src/types/traversal.js.map +1 -0
- package/dist/src/wgsl/advance-expand.wgsl.d.ts +19 -0
- package/dist/src/wgsl/advance-expand.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/advance-expand.wgsl.js +69 -0
- package/dist/src/wgsl/advance-expand.wgsl.js.map +1 -0
- package/dist/src/wgsl/bf-relax.wgsl.d.ts +22 -0
- package/dist/src/wgsl/bf-relax.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/bf-relax.wgsl.js +58 -0
- package/dist/src/wgsl/bf-relax.wgsl.js.map +1 -0
- package/dist/src/wgsl/bfs-bitset-build.wgsl.d.ts +15 -0
- package/dist/src/wgsl/bfs-bitset-build.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/bfs-bitset-build.wgsl.js +24 -0
- package/dist/src/wgsl/bfs-bitset-build.wgsl.js.map +1 -0
- package/dist/src/wgsl/bfs-bottom-up.wgsl.d.ts +20 -0
- package/dist/src/wgsl/bfs-bottom-up.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/bfs-bottom-up.wgsl.js +67 -0
- package/dist/src/wgsl/bfs-bottom-up.wgsl.js.map +1 -0
- package/dist/src/wgsl/bfs-contract.wgsl.d.ts +20 -0
- package/dist/src/wgsl/bfs-contract.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/bfs-contract.wgsl.js +55 -0
- package/dist/src/wgsl/bfs-contract.wgsl.js.map +1 -0
- package/dist/src/wgsl/bfs-fused.wgsl.d.ts +25 -0
- package/dist/src/wgsl/bfs-fused.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/bfs-fused.wgsl.js +78 -0
- package/dist/src/wgsl/bfs-fused.wgsl.js.map +1 -0
- package/dist/src/wgsl/bfs-unvisited-flags.wgsl.d.ts +18 -0
- package/dist/src/wgsl/bfs-unvisited-flags.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/bfs-unvisited-flags.wgsl.js +42 -0
- package/dist/src/wgsl/bfs-unvisited-flags.wgsl.js.map +1 -0
- package/dist/src/wgsl/closeness-reduce.wgsl.d.ts +17 -0
- package/dist/src/wgsl/closeness-reduce.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/closeness-reduce.wgsl.js +65 -0
- package/dist/src/wgsl/closeness-reduce.wgsl.js.map +1 -0
- package/dist/src/wgsl/closeness-sweep.wgsl.d.ts +20 -0
- package/dist/src/wgsl/closeness-sweep.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/closeness-sweep.wgsl.js +96 -0
- package/dist/src/wgsl/closeness-sweep.wgsl.js.map +1 -0
- package/dist/src/wgsl/compact-scatter.wgsl.d.ts +9 -0
- package/dist/src/wgsl/compact-scatter.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/compact-scatter.wgsl.js +17 -0
- package/dist/src/wgsl/compact-scatter.wgsl.js.map +1 -0
- package/dist/src/wgsl/dedupe-claim.wgsl.d.ts +10 -0
- package/dist/src/wgsl/dedupe-claim.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/dedupe-claim.wgsl.js +19 -0
- package/dist/src/wgsl/dedupe-claim.wgsl.js.map +1 -0
- package/dist/src/wgsl/dedupe-filter.wgsl.d.ts +12 -0
- package/dist/src/wgsl/dedupe-filter.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/dedupe-filter.wgsl.js +46 -0
- package/dist/src/wgsl/dedupe-filter.wgsl.js.map +1 -0
- package/dist/src/wgsl/frontier-finalize.wgsl.d.ts +59 -0
- package/dist/src/wgsl/frontier-finalize.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/frontier-finalize.wgsl.js +210 -0
- package/dist/src/wgsl/frontier-finalize.wgsl.js.map +1 -0
- package/dist/src/wgsl/sssp-pred.wgsl.d.ts +28 -0
- package/dist/src/wgsl/sssp-pred.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/sssp-pred.wgsl.js +80 -0
- package/dist/src/wgsl/sssp-pred.wgsl.js.map +1 -0
- package/dist/src/wgsl/sssp-relax.wgsl.d.ts +30 -0
- package/dist/src/wgsl/sssp-relax.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/sssp-relax.wgsl.js +72 -0
- package/dist/src/wgsl/sssp-relax.wgsl.js.map +1 -0
- package/dist/webgpu-graph-algorithms.js +3207 -377
- package/dist/webgpu-graph-algorithms.js.map +1 -1
- package/package.json +5 -4
- package/src/accelerator.ts +65 -7
- package/src/algorithms/bellman-ford.ts +387 -0
- package/src/algorithms/bfs.ts +626 -0
- package/src/algorithms/closeness.ts +395 -0
- package/src/algorithms/scope.ts +13 -3
- package/src/algorithms/sssp.ts +767 -0
- package/src/constants.ts +12 -0
- package/src/index.ts +14 -1
- package/src/kernel/prelude.ts +39 -4
- package/src/kernel/uniform-ring.ts +14 -0
- package/src/kernels.ts +450 -6
- package/src/primitives/advance.ts +130 -0
- package/src/primitives/compact.ts +323 -0
- package/src/primitives/core-shape.ts +41 -3
- package/src/primitives/frontier.ts +388 -0
- package/src/types/accelerator.ts +18 -5
- package/src/types/traversal.ts +56 -0
- package/src/wgsl/advance-expand.wgsl.ts +68 -0
- package/src/wgsl/bf-relax.wgsl.ts +57 -0
- package/src/wgsl/bfs-bitset-build.wgsl.ts +23 -0
- package/src/wgsl/bfs-bottom-up.wgsl.ts +66 -0
- package/src/wgsl/bfs-contract.wgsl.ts +54 -0
- package/src/wgsl/bfs-fused.wgsl.ts +77 -0
- package/src/wgsl/bfs-unvisited-flags.wgsl.ts +41 -0
- package/src/wgsl/closeness-reduce.wgsl.ts +64 -0
- package/src/wgsl/closeness-sweep.wgsl.ts +95 -0
- package/src/wgsl/compact-scatter.wgsl.ts +16 -0
- package/src/wgsl/dedupe-claim.wgsl.ts +18 -0
- package/src/wgsl/dedupe-filter.wgsl.ts +45 -0
- package/src/wgsl/frontier-finalize.wgsl.ts +209 -0
- package/src/wgsl/sssp-pred.wgsl.ts +79 -0
- package/src/wgsl/sssp-relax.wgsl.ts +71 -0
- package/dist/chunks/context-BXqgCifx.js.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { W as WebGpuGraphError, U as UNIFORM_SLOT_BYTES, B as BufferUsage, M as MAX_WORKGROUPS_PER_DIM, a as WGSL_RESERVED_WORDS, S as STATE_HEADER_BYTES, d as deviceLostError, i as isWebGpuGraphError, b as U32_MAX$2, R as RADIX_BINS,
|
|
2
|
-
import {
|
|
3
|
-
import { renumberPartition, makeMask, maskTest, expandEdges, fromEdgeArrays } from "@graphty/graph-format";
|
|
1
|
+
import { W as WebGpuGraphError, U as UNIFORM_SLOT_BYTES, B as BufferUsage, M as MAX_WORKGROUPS_PER_DIM, a as WGSL_RESERVED_WORDS, S as STATE_HEADER_BYTES, d as deviceLostError, i as isWebGpuGraphError, b as U32_MAX$2, c as MAX_LEVELS_PER_SUBMIT, F as FRONTIER_CANDIDATES, I as INDIRECT_ARGS_STRIDE, R as RADIX_BINS, e as FUSED_FRONTIER_MAX, f as BEAMER_BETA, g as SSSP_DELTA_FACTOR, h as F32_INF_BITS, j as GRID_COARSEST_SIDE, k as GRID_MIN_SIDE, l as GRID_SORT_BITS, m as FA2_DEFAULTS, n as MAX_ITERATIONS_PER_STEP, o as MAX_1D_ITEMS, p as hasErrorCode, q as FA2_FLAG_FIRST, P as PARTIAL_BYTES, r as GRID_HUB_CELL, L as LAYOUT_TUNING_DEFAULTS, E as EXACT_MAX_NODES, T as TRACE_RECORD_BYTES, s as GRID_BBOX_MARGIN, t as GRID_EXTENT_FLOOR, u as FR_ADAPTIVE_MAX_ITERATIONS, v as FR_START_TEMPERATURE, w as FA2_FLAG_ADAPTIVE, x as FR_REHEAT_FRACTION, y as FR_DEFAULTS, z as SE_DEFAULTS, A as SE_SCALE_REFERENCE_NODES } from "./chunks/context-Dvq-Cc6v.js";
|
|
2
|
+
import { C, G, D, H, J, K } from "./chunks/context-Dvq-Cc6v.js";
|
|
3
|
+
import { renumberPartition, INVALID_INDEX, makeMask, maskTest, expandEdges, fromEdgeArrays } from "@graphty/graph-format";
|
|
4
4
|
class UniformRing {
|
|
5
5
|
/**
|
|
6
6
|
* Creates the ring buffer (`slots x UNIFORM_SLOT_BYTES` bytes, UNIFORM | COPY_DST) through the allocator.
|
|
@@ -10,6 +10,7 @@ class UniformRing {
|
|
|
10
10
|
* @param label - the buffer label
|
|
11
11
|
*/
|
|
12
12
|
constructor(device, allocator, slots, label) {
|
|
13
|
+
this.overruns = 0;
|
|
13
14
|
this.next = 0;
|
|
14
15
|
this.dirtyLo = -1;
|
|
15
16
|
this.dirtyHi = -1;
|
|
@@ -31,6 +32,7 @@ class UniformRing {
|
|
|
31
32
|
this.label = label;
|
|
32
33
|
this.shadow = new ArrayBuffer(slots * UNIFORM_SLOT_BYTES);
|
|
33
34
|
this.view = new DataView(this.shadow);
|
|
35
|
+
this.dirty = new Uint8Array(slots);
|
|
34
36
|
this.buffer = allocator.createBuffer({
|
|
35
37
|
label,
|
|
36
38
|
size: slots * UNIFORM_SLOT_BYTES,
|
|
@@ -79,6 +81,9 @@ class UniformRing {
|
|
|
79
81
|
}
|
|
80
82
|
const first = this.next;
|
|
81
83
|
this.next += count;
|
|
84
|
+
if (this.dirty.subarray(first, first + count).includes(1)) {
|
|
85
|
+
this.overruns += 1;
|
|
86
|
+
}
|
|
82
87
|
return first;
|
|
83
88
|
}
|
|
84
89
|
/**
|
|
@@ -91,6 +96,7 @@ class UniformRing {
|
|
|
91
96
|
this.checkSlot(slot);
|
|
92
97
|
this.assertFits(block);
|
|
93
98
|
block.write(this.view, values, slot * UNIFORM_SLOT_BYTES);
|
|
99
|
+
this.dirty[slot] = 1;
|
|
94
100
|
if (this.dirtyLo < 0 || slot < this.dirtyLo) {
|
|
95
101
|
this.dirtyLo = slot;
|
|
96
102
|
}
|
|
@@ -110,6 +116,7 @@ class UniformRing {
|
|
|
110
116
|
const begin = this.dirtyLo * UNIFORM_SLOT_BYTES;
|
|
111
117
|
const end = (this.dirtyHi + 1) * UNIFORM_SLOT_BYTES;
|
|
112
118
|
this.device.queue.writeBuffer(this.buffer, begin, this.shadow, begin, end - begin);
|
|
119
|
+
this.dirty.fill(0);
|
|
113
120
|
this.dirtyLo = -1;
|
|
114
121
|
this.dirtyHi = -1;
|
|
115
122
|
}
|
|
@@ -153,7 +160,7 @@ class UniformRing {
|
|
|
153
160
|
function perDimension(caps) {
|
|
154
161
|
return Math.min(MAX_WORKGROUPS_PER_DIM, caps.limits.maxComputeWorkgroupsPerDimension);
|
|
155
162
|
}
|
|
156
|
-
function assertCount(argument, value) {
|
|
163
|
+
function assertCount$1(argument, value) {
|
|
157
164
|
if (!Number.isInteger(value) || value < 0) {
|
|
158
165
|
throw new WebGpuGraphError("E_INVALID_ARGUMENT", `${argument} must be a non-negative integer, got ${value}`, {
|
|
159
166
|
argument,
|
|
@@ -183,8 +190,8 @@ function grid(groups, items, caps) {
|
|
|
183
190
|
if (groups <= limit) {
|
|
184
191
|
return { x: groups, y: 1, z: 1, items, stride: null };
|
|
185
192
|
}
|
|
186
|
-
const
|
|
187
|
-
if (
|
|
193
|
+
const y = Math.ceil(groups / limit);
|
|
194
|
+
if (y > limit) {
|
|
188
195
|
throw new WebGpuGraphError(
|
|
189
196
|
"E_TOO_LARGE",
|
|
190
197
|
`${groups} workgroups exceed the 2D dispatch limit of ${limit * limit}`,
|
|
@@ -197,15 +204,15 @@ function grid(groups, items, caps) {
|
|
|
197
204
|
}
|
|
198
205
|
);
|
|
199
206
|
}
|
|
200
|
-
return { x: limit, y
|
|
207
|
+
return { x: limit, y, z: 1, items, stride: null };
|
|
201
208
|
}
|
|
202
209
|
function plan1d(items, wg, caps) {
|
|
203
|
-
assertCount("items", items);
|
|
210
|
+
assertCount$1("items", items);
|
|
204
211
|
assertWorkgroupSize(wg);
|
|
205
212
|
return grid(Math.ceil(items / wg), items, caps);
|
|
206
213
|
}
|
|
207
214
|
function planGridStride(items, wg, caps, maxGroups) {
|
|
208
|
-
assertCount("items", items);
|
|
215
|
+
assertCount$1("items", items);
|
|
209
216
|
assertWorkgroupSize(wg);
|
|
210
217
|
if (items === 0) {
|
|
211
218
|
return { x: 0, y: 1, z: 1, items, stride: null };
|
|
@@ -544,6 +551,432 @@ ${lines.join("\n")}
|
|
|
544
551
|
return lanes;
|
|
545
552
|
}
|
|
546
553
|
}
|
|
554
|
+
const advanceExpandWgsl = (
|
|
555
|
+
/* wgsl */
|
|
556
|
+
`
|
|
557
|
+
var<workgroup> sh: array<u32, WG>; // the block's degrees, then their inclusive scan
|
|
558
|
+
var<workgroup> rowStart: array<u32, WG>; // the first bound arc of each entry's row
|
|
559
|
+
var<workgroup> base: u32; // the block's reserved span in the edge queue
|
|
560
|
+
var<workgroup> wcount: u32; // the frontier's length on a two-phase level, 0 on any other
|
|
561
|
+
|
|
562
|
+
@compute @workgroup_size(WG)
|
|
563
|
+
fn advance_expand(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
|
|
564
|
+
if (lid.x == 0u) { wcount = select(0u, atomicLoad(&counters[0]), atomicLoad(&counters[24]) == 1u); } // frontierCount, on the two-phase path only (the path word)
|
|
565
|
+
let count = workgroupUniformLoad(&wcount); // uniform: the block loop below holds barriers
|
|
566
|
+
for (var b0 = group_id(wid) * WG; b0 < count; b0 = b0 + P.stride) { // grid-stride over blocks of WG entries (a direct dispatch of the plan's groups)
|
|
567
|
+
let i = b0 + lid.x; // this lane's frontier entry
|
|
568
|
+
var deg = 0u;
|
|
569
|
+
var start = 0u;
|
|
570
|
+
if (i < count) { // guarded loads into locals (3.5 rule 1)
|
|
571
|
+
let v = frontierIn[i];
|
|
572
|
+
let lo = max(rowPtr[v], P.arcBase); // the row clipped to the bound window (P8-T12)
|
|
573
|
+
let hi = min(rowPtr[v + 1u], P.arcEnd);
|
|
574
|
+
start = lo;
|
|
575
|
+
deg = select(0u, hi - lo, hi > lo);
|
|
576
|
+
}
|
|
577
|
+
let inclusive = wg_scan_u32(deg, lid.x); // the prelude's inclusive scan in LANE order (P8-T1 Step 5); the twin
|
|
578
|
+
sh[lid.x] = inclusive; // sh is monotone in lid.x, the index the binary search walks
|
|
579
|
+
rowStart[lid.x] = start;
|
|
580
|
+
workgroupBarrier();
|
|
581
|
+
let aggregate = workgroupUniformLoad(&sh[WG - 1u]); // uniform; includes a barrier
|
|
582
|
+
if (lid.x == 0u) {
|
|
583
|
+
base = atomicAdd(&counters[8], aggregate); // edgeCount: ONE reservation per workgroup, not one per arc
|
|
584
|
+
atomicAdd(&counters[9], aggregate); // edgeCountUnclamped: the overflow detector (PD-23)
|
|
585
|
+
atomicAdd(&counters[2], aggregate); // frontierDegreeSum: Beamer's m_f (P8-T8)
|
|
586
|
+
}
|
|
587
|
+
workgroupBarrier();
|
|
588
|
+
for (var p = lid.x; p < aggregate; p = p + WG) { // strip [0, aggregate): lane j takes j, j + WG, ...
|
|
589
|
+
var lo = 0u; // upper_bound: the first k with sh[k] > p owns arc p
|
|
590
|
+
var hi = WG;
|
|
591
|
+
loop {
|
|
592
|
+
if (lo >= hi) { break; }
|
|
593
|
+
let mid = (lo + hi) / 2u;
|
|
594
|
+
if (sh[mid] > p) { hi = mid; } else { lo = mid + 1u; }
|
|
595
|
+
}
|
|
596
|
+
let k = lo;
|
|
597
|
+
var exclusive = 0u;
|
|
598
|
+
if (k > 0u) { exclusive = sh[k - 1u]; }
|
|
599
|
+
let arc = rowStart[k] + (p - exclusive);
|
|
600
|
+
let q = base + p;
|
|
601
|
+
if (q < P.edgeCapacity) { edgeQueue[q] = colIdx[arc - P.arcBase]; } // the clamp of PD-23; the queue holds the target vertex only (PD-24)
|
|
602
|
+
}
|
|
603
|
+
workgroupBarrier(); // sh, rowStart and base are reused by the next block
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
`
|
|
607
|
+
);
|
|
608
|
+
const bfRelaxWgsl = (
|
|
609
|
+
/* wgsl */
|
|
610
|
+
`
|
|
611
|
+
fn relax(v: u32, nd: f32) {
|
|
612
|
+
var cur = atomicLoad(&dist[v]);
|
|
613
|
+
var tries = 0u;
|
|
614
|
+
loop {
|
|
615
|
+
if (!(nd < bitcast<f32>(cur))) { break; } // no improvement; +Inf is greater than every finite nd
|
|
616
|
+
let r = atomicCompareExchangeWeak(&dist[v], cur, bitcast<u32>(nd));
|
|
617
|
+
if (r.exchanged) { atomicStore(&flags[0], 1u); break; } // changed
|
|
618
|
+
cur = r.old_value;
|
|
619
|
+
tries = tries + 1u;
|
|
620
|
+
if (tries >= P.maxRetries) { atomicStore(&flags[1], 1u); break; } // retryExhausted (PD-12)
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
@compute @workgroup_size(WG)
|
|
625
|
+
fn bf_relax(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
|
|
626
|
+
let first = linear_id(wid, lid.x);
|
|
627
|
+
let cutoff = bitcast<f32>(P.cutoffBits);
|
|
628
|
+
for (var e = first; e < P.edgeCount; e = e + P.stride) { // each logical edge once (edgeList)
|
|
629
|
+
let u = edgeSrc[e];
|
|
630
|
+
let v = edgeDst[e];
|
|
631
|
+
let w = weights[edgeToArc[e]]; // the edge's weight through its forward arc
|
|
632
|
+
let du = atomicLoad(&dist[u]);
|
|
633
|
+
if (du != F32_INF_BITS) { // unreached is tested as the bit pattern, as sssp-pred does
|
|
634
|
+
let nd = bitcast<f32>(du) + w;
|
|
635
|
+
if (nd <= cutoff) { relax(v, nd); }
|
|
636
|
+
}
|
|
637
|
+
if (UNDIRECTED) { // the other direction of an undirected edge
|
|
638
|
+
let dv = atomicLoad(&dist[v]);
|
|
639
|
+
if (dv != F32_INF_BITS) {
|
|
640
|
+
let nd = bitcast<f32>(dv) + w;
|
|
641
|
+
if (nd <= cutoff) { relax(u, nd); }
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
`
|
|
647
|
+
);
|
|
648
|
+
const bfsBitsetBuildWgsl = (
|
|
649
|
+
/* wgsl */
|
|
650
|
+
`
|
|
651
|
+
@compute @workgroup_size(WG)
|
|
652
|
+
fn bfs_bitset_build(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
|
|
653
|
+
let count = select(0u, atomicLoad(&counters[0]), atomicLoad(&counters[24]) == 3u); // frontierCount, on the bottom-up path only (the path word)
|
|
654
|
+
for (var i = linear_id(wid, lid.x); i < count; i = i + P.stride) { // grid-stride; no barrier anywhere
|
|
655
|
+
let v = frontierIn[i];
|
|
656
|
+
atomicOr(&bits[P.bitsBase + (v >> 5u)], 1u << (v & 31u));
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
`
|
|
660
|
+
);
|
|
661
|
+
const bfsBottomUpWgsl = (
|
|
662
|
+
/* wgsl */
|
|
663
|
+
`
|
|
664
|
+
var<workgroup> sh: array<u32, WG>;
|
|
665
|
+
var<workgroup> base: u32;
|
|
666
|
+
var<workgroup> wcount: u32; // the unvisited list's length on a bottom-up level, 0 on any other
|
|
667
|
+
|
|
668
|
+
@compute @workgroup_size(WG)
|
|
669
|
+
fn bfs_bottom_up(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
|
|
670
|
+
let claim = atomicLoad(&counters[11]) + 1u;
|
|
671
|
+
if (lid.x == 0u) { wcount = select(0u, atomicLoad(&counters[7]), atomicLoad(&counters[24]) == 3u); } // unvisitedListLen, on the bottom-up path only (the path word)
|
|
672
|
+
let len = workgroupUniformLoad(&wcount); // uniform: the block loop below holds barriers
|
|
673
|
+
for (var b0 = group_id(wid) * WG; b0 < len; b0 = b0 + P.stride) { // grid-stride over blocks of WG entries
|
|
674
|
+
let i = b0 + lid.x;
|
|
675
|
+
var won = 0u;
|
|
676
|
+
var v = 0u;
|
|
677
|
+
var reads = 0u;
|
|
678
|
+
if (i < len) { // guarded work into locals
|
|
679
|
+
v = sweepIn[i];
|
|
680
|
+
if (atomicLoad(&depth[v]) == INVALID_INDEX) { // a stale entry, claimed since the rebuild, is skipped
|
|
681
|
+
let end = min(rowPtr[v + 1u], P.arcEnd);
|
|
682
|
+
for (var a = max(rowPtr[v], P.arcBase); a < end; a = a + 1u) { // in-neighbours through the reverse core
|
|
683
|
+
reads = reads + 1u;
|
|
684
|
+
let u = colIdx[a - P.arcBase];
|
|
685
|
+
if (mask_bit(sweepIn[P.bitsBase + (u >> 5u)], u)) { won = 1u; break; } // the early exit: a real break, never a flag
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
sh[lid.x] = won;
|
|
690
|
+
workgroupBarrier();
|
|
691
|
+
for (var s = 1u; s < WG; s = s * 2u) { // Hillis-Steele inclusive scan of won (bfs-contract's)
|
|
692
|
+
var t = 0u;
|
|
693
|
+
if (lid.x >= s) { t = sh[lid.x - s]; }
|
|
694
|
+
workgroupBarrier();
|
|
695
|
+
sh[lid.x] = sh[lid.x] + t;
|
|
696
|
+
workgroupBarrier();
|
|
697
|
+
}
|
|
698
|
+
let inclusive = sh[lid.x];
|
|
699
|
+
let readsTotal = wg_reduce_u32(reads, lid.x, 0u); // arcsScanned, one atomic per workgroup (the sabotage witness, Step 6)
|
|
700
|
+
if (lid.x == WG - 1u) { base = atomicAdd(&counters[1], inclusive); }
|
|
701
|
+
if (lid.x == 0u) { atomicAdd(&counters[16], readsTotal); }
|
|
702
|
+
workgroupBarrier();
|
|
703
|
+
if (won == 1u) {
|
|
704
|
+
atomicStore(&depth[v], claim); // no claim race: the list holds v once and the sweep is vertex-parallel
|
|
705
|
+
frontierOut[base + inclusive - 1u] = v;
|
|
706
|
+
}
|
|
707
|
+
workgroupBarrier(); // sh and base are reused by the next block
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
`
|
|
711
|
+
);
|
|
712
|
+
const bfsContractWgsl = (
|
|
713
|
+
/* wgsl */
|
|
714
|
+
`
|
|
715
|
+
var<workgroup> sh: array<u32, WG>;
|
|
716
|
+
var<workgroup> base: u32;
|
|
717
|
+
var<workgroup> wcount: u32; // the clamped edge count on a two-phase level, 0 on any other
|
|
718
|
+
|
|
719
|
+
@compute @workgroup_size(WG)
|
|
720
|
+
fn bfs_contract(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
|
|
721
|
+
if (lid.x == 0u) { wcount = select(0u, atomicLoad(&counters[8]), atomicLoad(&counters[24]) == 1u); } // edgeCount, clamped by role 1; the two-phase path only (the path word)
|
|
722
|
+
let count = workgroupUniformLoad(&wcount); // uniform: the block loop below holds barriers
|
|
723
|
+
let claim = atomicLoad(&counters[11]) + 1u; // the depth this level assigns
|
|
724
|
+
for (var b0 = group_id(wid) * WG; b0 < count; b0 = b0 + P.stride) { // grid-stride over blocks of WG entries
|
|
725
|
+
let i = b0 + lid.x;
|
|
726
|
+
var won = 0u;
|
|
727
|
+
var v = 0u;
|
|
728
|
+
if (i < count) { // guarded work into locals
|
|
729
|
+
v = edgeQueue[i];
|
|
730
|
+
let old = atomicMin(&depth[v], claim);
|
|
731
|
+
won = select(0u, 1u, old == INVALID_INDEX); // PD-6: the unique winner
|
|
732
|
+
}
|
|
733
|
+
sh[lid.x] = won;
|
|
734
|
+
workgroupBarrier();
|
|
735
|
+
for (var s = 1u; s < WG; s = s * 2u) { // Hillis-Steele inclusive scan of won
|
|
736
|
+
var t = 0u;
|
|
737
|
+
if (lid.x >= s) { t = sh[lid.x - s]; }
|
|
738
|
+
workgroupBarrier();
|
|
739
|
+
sh[lid.x] = sh[lid.x] + t;
|
|
740
|
+
workgroupBarrier();
|
|
741
|
+
}
|
|
742
|
+
let inclusive = sh[lid.x];
|
|
743
|
+
if (lid.x == WG - 1u) { base = atomicAdd(&counters[1], inclusive); } // nextFrontierCount: one reservation per workgroup
|
|
744
|
+
workgroupBarrier();
|
|
745
|
+
if (won == 1u) { frontierOut[base + inclusive - 1u] = v; }
|
|
746
|
+
workgroupBarrier(); // sh and base are reused by the next block
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
`
|
|
750
|
+
);
|
|
751
|
+
const bfsFusedWgsl = (
|
|
752
|
+
/* wgsl */
|
|
753
|
+
`
|
|
754
|
+
var<workgroup> sh: array<u32, WG>;
|
|
755
|
+
var<workgroup> wdeg: u32;
|
|
756
|
+
var<workgroup> wstart: u32;
|
|
757
|
+
var<workgroup> base: u32;
|
|
758
|
+
var<workgroup> wcount: u32; // the frontier's length on a fused or retry level, 0 on any other
|
|
759
|
+
|
|
760
|
+
@compute @workgroup_size(WG)
|
|
761
|
+
fn bfs_fused(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
|
|
762
|
+
let claim = atomicLoad(&counters[11]) + 1u;
|
|
763
|
+
if (lid.x == 0u) {
|
|
764
|
+
let path = atomicLoad(&counters[24]); // 2 the fused level, 4 the overflow retry (PD-23)
|
|
765
|
+
wcount = select(0u, atomicLoad(&counters[0]), path == 2u || path == 4u);
|
|
766
|
+
}
|
|
767
|
+
let count = workgroupUniformLoad(&wcount); // uniform: the entry loop below holds barriers
|
|
768
|
+
for (var g = group_id(wid); g < count; g = g + P.stride) { // one workgroup per frontier entry; P.stride is the dispatch's GROUP count
|
|
769
|
+
if (lid.x == 0u) {
|
|
770
|
+
let u = frontierIn[g];
|
|
771
|
+
let a0 = max(rowPtr[u], P.arcBase);
|
|
772
|
+
let a1 = min(rowPtr[u + 1u], P.arcEnd);
|
|
773
|
+
let d = select(0u, a1 - a0, a1 > a0);
|
|
774
|
+
wdeg = d;
|
|
775
|
+
wstart = a0;
|
|
776
|
+
atomicAdd(&counters[2], d); // frontierDegreeSum, so Beamer's test (P8-T8) sees fused levels too
|
|
777
|
+
}
|
|
778
|
+
let deg = workgroupUniformLoad(&wdeg); // uniform: the loop below may hold barriers
|
|
779
|
+
let start = workgroupUniformLoad(&wstart);
|
|
780
|
+
for (var p0 = 0u; p0 < deg; p0 = p0 + WG) { // strip the row WG arcs at a time
|
|
781
|
+
let p = p0 + lid.x;
|
|
782
|
+
var won = 0u;
|
|
783
|
+
var v = 0u;
|
|
784
|
+
if (p < deg) { // guarded claim into locals
|
|
785
|
+
v = colIdx[start + p - P.arcBase];
|
|
786
|
+
let old = atomicMin(&depth[v], claim);
|
|
787
|
+
won = select(0u, 1u, old == INVALID_INDEX);
|
|
788
|
+
}
|
|
789
|
+
sh[lid.x] = won;
|
|
790
|
+
workgroupBarrier();
|
|
791
|
+
for (var s = 1u; s < WG; s = s * 2u) { // Hillis-Steele inclusive scan of won (bfs-contract's, verbatim)
|
|
792
|
+
var t = 0u;
|
|
793
|
+
if (lid.x >= s) { t = sh[lid.x - s]; }
|
|
794
|
+
workgroupBarrier();
|
|
795
|
+
sh[lid.x] = sh[lid.x] + t;
|
|
796
|
+
workgroupBarrier();
|
|
797
|
+
}
|
|
798
|
+
let inclusive = sh[lid.x];
|
|
799
|
+
if (lid.x == WG - 1u) { base = atomicAdd(&counters[1], inclusive); }
|
|
800
|
+
workgroupBarrier();
|
|
801
|
+
if (won == 1u) { frontierOut[base + inclusive - 1u] = v; }
|
|
802
|
+
workgroupBarrier(); // sh and base are reused by the next strip
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
`
|
|
807
|
+
);
|
|
808
|
+
const bfsUnvisitedFlagsWgsl = (
|
|
809
|
+
/* wgsl */
|
|
810
|
+
`
|
|
811
|
+
@compute @workgroup_size(WG)
|
|
812
|
+
fn bfs_unvisited_flags(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
|
|
813
|
+
let first = linear_id(wid, lid.x);
|
|
814
|
+
var cnt = 0u;
|
|
815
|
+
var degSum = 0u;
|
|
816
|
+
var len = 0u;
|
|
817
|
+
for (var v = first; v < P.n; v = v + P.stride) { // no barrier inside: the trip count is per lane
|
|
818
|
+
let unv = depth[v] == INVALID_INDEX;
|
|
819
|
+
let listed = unv && (inDegree[v] != 0u);
|
|
820
|
+
flags[v] = select(0u, 1u, listed);
|
|
821
|
+
cnt = cnt + select(0u, 1u, unv);
|
|
822
|
+
degSum = degSum + select(0u, outDegree[v], unv); // the OUT-degree: Beamer's m_u counts the edges top-down would examine
|
|
823
|
+
len = len + select(0u, 1u, listed);
|
|
824
|
+
}
|
|
825
|
+
let c = wg_reduce_u32(cnt, lid.x, 0u); // the prelude's workgroup sum (combine_u's sum code); uniform: after the loop
|
|
826
|
+
let d = wg_reduce_u32(degSum, lid.x, 0u);
|
|
827
|
+
let l = wg_reduce_u32(len, lid.x, 0u);
|
|
828
|
+
if (lid.x == 0u) { // ONE atomic per word per workgroup
|
|
829
|
+
atomicAdd(&counters[5], c);
|
|
830
|
+
atomicAdd(&counters[6], d);
|
|
831
|
+
atomicAdd(&counters[7], l);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
`
|
|
835
|
+
);
|
|
836
|
+
const closenessReduceWgsl = (
|
|
837
|
+
/* wgsl */
|
|
838
|
+
`
|
|
839
|
+
@compute @workgroup_size(WG)
|
|
840
|
+
fn closeness_reduce(@builtin(local_invocation_id) lid: vec3<u32>) {
|
|
841
|
+
if (lid.x != 0u) { return; } // one lane; no barrier follows (3.5 rule 1)
|
|
842
|
+
if (P.role == 1u) { // the seed of a batch: P.source is its first source
|
|
843
|
+
let k = min(32u, P.n - P.source);
|
|
844
|
+
for (var s = 0u; s < k; s = s + 1u) {
|
|
845
|
+
let v = P.source + s;
|
|
846
|
+
let bit = 1u << s;
|
|
847
|
+
bits[v] = bit; // visited
|
|
848
|
+
bits[P.bitsBase + v] = bit; // the frontier level 0 reads (region 1: level 0's parity is 0)
|
|
849
|
+
bits[3u * P.bitsBase + v] = 1u; // flags: level 0's compact turns them into the list
|
|
850
|
+
}
|
|
851
|
+
atomicStore(&counters[0], k); // not done
|
|
852
|
+
atomicStore(&counters[11], U32_MAX); // level: the first boundary brings it to 0
|
|
853
|
+
atomicStore(&counters[15], 0u); // done
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
// role 0: the level boundary -- done from the previous level's compacted count, then the accumulation
|
|
857
|
+
let count = atomicLoad(&counters[0]);
|
|
858
|
+
atomicStore(&counters[15], select(0u, 1u, count == 0u));
|
|
859
|
+
let level = atomicLoad(&counters[11]);
|
|
860
|
+
let d = level + 1u; // the distance of the claims the level just run made
|
|
861
|
+
for (var s = 0u; s < 32u; s = s + 1u) {
|
|
862
|
+
let c = atomicLoad(&perSource[s]); // newCount[s]
|
|
863
|
+
atomicStore(&perSource[32u + s], atomicLoad(&perSource[32u + s]) + c); // reached[s]
|
|
864
|
+
// sum[s] += c x d in 64 bits: the 16-bit split product (pLo, pHi), then the add with its carry
|
|
865
|
+
let cLo = c & 0xFFFFu;
|
|
866
|
+
let cHi = c >> 16u;
|
|
867
|
+
let dLo = d & 0xFFFFu;
|
|
868
|
+
let dHi = d >> 16u;
|
|
869
|
+
let ll = cLo * dLo;
|
|
870
|
+
let lh = cLo * dHi;
|
|
871
|
+
let hl = cHi * dLo;
|
|
872
|
+
let mid = (ll >> 16u) + (lh & 0xFFFFu) + (hl & 0xFFFFu);
|
|
873
|
+
let pLo = (ll & 0xFFFFu) | ((mid & 0xFFFFu) << 16u);
|
|
874
|
+
let pHi = (cHi * dHi) + (lh >> 16u) + (hl >> 16u) + (mid >> 16u);
|
|
875
|
+
var lo = atomicLoad(&perSource[64u + s]); // sumLo[s]
|
|
876
|
+
var hi = atomicLoad(&perSource[96u + s]); // sumHi[s]
|
|
877
|
+
let before = lo;
|
|
878
|
+
lo = lo + pLo;
|
|
879
|
+
hi = hi + pHi + select(0u, 1u, lo < before);
|
|
880
|
+
atomicStore(&perSource[64u + s], lo);
|
|
881
|
+
atomicStore(&perSource[96u + s], hi);
|
|
882
|
+
atomicStore(&perSource[s], 0u);
|
|
883
|
+
}
|
|
884
|
+
atomicStore(&counters[11], level + 1u);
|
|
885
|
+
}
|
|
886
|
+
`
|
|
887
|
+
);
|
|
888
|
+
const closenessSweepWgsl = (
|
|
889
|
+
/* wgsl */
|
|
890
|
+
`
|
|
891
|
+
var<workgroup> sh: array<u32, WG>; // the block's degrees, then their inclusive scan
|
|
892
|
+
var<workgroup> rowStart: array<u32, WG>; // the first bound arc of each entry's row
|
|
893
|
+
var<workgroup> rowOf: array<u32, WG>; // the frontier vertex of each entry (the source end of its arcs)
|
|
894
|
+
var<workgroup> local: array<atomic<u32>, 32>; // this workgroup's fresh claims per source
|
|
895
|
+
var<workgroup> wcount: u32; // the frontier list's length
|
|
896
|
+
|
|
897
|
+
@compute @workgroup_size(WG)
|
|
898
|
+
fn closeness_sweep(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
|
|
899
|
+
if (lid.x == 0u) { wcount = atomicLoad(&counters[0]); } // the frontier list's length (compact's total)
|
|
900
|
+
let count = workgroupUniformLoad(&wcount); // uniform: the block loop below holds barriers
|
|
901
|
+
let nextBase = select(2u * P.bitsBase, P.bitsBase, P.mode == 1u); // the region that is next this level
|
|
902
|
+
let frontierBase = 3u * P.bitsBase - nextBase; // the other one: the region that is the frontier
|
|
903
|
+
for (var b0 = group_id(wid) * WG; b0 < count; b0 = b0 + P.stride) { // grid-stride over blocks of WG entries
|
|
904
|
+
let i = b0 + lid.x; // this lane's frontier entry
|
|
905
|
+
var deg = 0u;
|
|
906
|
+
var start = 0u;
|
|
907
|
+
var u = 0u;
|
|
908
|
+
if (i < count) { // guarded loads into locals (3.5 rule 1)
|
|
909
|
+
u = frontierList[i];
|
|
910
|
+
let lo = max(rowPtr[u], P.arcBase);
|
|
911
|
+
let hi = min(rowPtr[u + 1u], P.arcEnd);
|
|
912
|
+
start = lo;
|
|
913
|
+
deg = select(0u, hi - lo, hi > lo);
|
|
914
|
+
}
|
|
915
|
+
if (lid.x < 32u) { atomicStore(&local[lid.x], 0u); } // zeroed before the strip loop (WebGPU zero-initialises workgroup memory; said anyway)
|
|
916
|
+
sh[lid.x] = deg;
|
|
917
|
+
rowStart[lid.x] = start;
|
|
918
|
+
rowOf[lid.x] = u;
|
|
919
|
+
workgroupBarrier();
|
|
920
|
+
for (var s = 1u; s < WG; s = s * 2u) { // Hillis-Steele inclusive scan of the degrees (bfs-contract's, inlined)
|
|
921
|
+
var t = 0u;
|
|
922
|
+
if (lid.x >= s) { t = sh[lid.x - s]; }
|
|
923
|
+
workgroupBarrier();
|
|
924
|
+
sh[lid.x] = sh[lid.x] + t;
|
|
925
|
+
workgroupBarrier();
|
|
926
|
+
}
|
|
927
|
+
let aggregate = workgroupUniformLoad(&sh[WG - 1u]); // uniform; includes a barrier
|
|
928
|
+
for (var p = lid.x; p < aggregate; p = p + WG) { // strip [0, aggregate): lane j takes j, j + WG, ...
|
|
929
|
+
var lo = 0u; // upper_bound: the first k with sh[k] > p owns arc p
|
|
930
|
+
var hi = WG;
|
|
931
|
+
loop {
|
|
932
|
+
if (lo >= hi) { break; }
|
|
933
|
+
let mid = (lo + hi) / 2u;
|
|
934
|
+
if (sh[mid] > p) { hi = mid; } else { lo = mid + 1u; }
|
|
935
|
+
}
|
|
936
|
+
let k = lo;
|
|
937
|
+
var exclusive = 0u;
|
|
938
|
+
if (k > 0u) { exclusive = sh[k - 1u]; }
|
|
939
|
+
let arc = rowStart[k] + (p - exclusive);
|
|
940
|
+
let x = colIdx[arc - P.arcBase];
|
|
941
|
+
let mask = atomicLoad(&bits[frontierBase + rowOf[k]]) & ~atomicLoad(&bits[x]); // the sources at u that have not reached x
|
|
942
|
+
if (mask != 0u) {
|
|
943
|
+
let old = atomicOr(&bits[x], mask); // visited: the claim, one read-modify-write
|
|
944
|
+
let fresh = mask & ~old; // the sources whose claim this lane won
|
|
945
|
+
if (fresh != 0u) {
|
|
946
|
+
atomicOr(&bits[nextBase + x], fresh);
|
|
947
|
+
atomicStore(&bits[3u * P.bitsBase + x], 1u); // flags: x is in the next frontier list (compact reads it)
|
|
948
|
+
var b = fresh;
|
|
949
|
+
loop { // one tally per set bit of fresh
|
|
950
|
+
if (b == 0u) { break; }
|
|
951
|
+
let s = firstTrailingBit(b);
|
|
952
|
+
atomicAdd(&local[s], 1u);
|
|
953
|
+
b = b & (b - 1u);
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
workgroupBarrier(); // uniform: the loop's bound is the uniform aggregate
|
|
959
|
+
if (lid.x < 32u) { // ONE global atomic per source per workgroup
|
|
960
|
+
let c = atomicLoad(&local[lid.x]);
|
|
961
|
+
if (c != 0u) { atomicAdd(&perSource[lid.x], c); } // newCount[s]
|
|
962
|
+
}
|
|
963
|
+
workgroupBarrier(); // sh, rowStart, rowOf and local are reused by the next block
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
`
|
|
967
|
+
);
|
|
968
|
+
const compactScatterWgsl = (
|
|
969
|
+
/* wgsl */
|
|
970
|
+
`
|
|
971
|
+
@compute @workgroup_size(WG)
|
|
972
|
+
fn compact_scatter(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
|
|
973
|
+
let i = linear_id(wid, lid.x);
|
|
974
|
+
if (i == 0u) { outCount[P.outIndex] = offsets[P.count - 1u] + flags[P.count - 1u]; } // the exclusive scan's total; the planner never dispatches for count 0
|
|
975
|
+
if (i >= P.count) { return; } // no barrier follows
|
|
976
|
+
if (flags[i] != 0u) { out[offsets[i]] = queue[i]; }
|
|
977
|
+
}
|
|
978
|
+
`
|
|
979
|
+
);
|
|
547
980
|
const countingScatterWgsl = (
|
|
548
981
|
/* wgsl */
|
|
549
982
|
`
|
|
@@ -557,6 +990,57 @@ fn counting_scatter(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invoca
|
|
|
557
990
|
}
|
|
558
991
|
`
|
|
559
992
|
);
|
|
993
|
+
const dedupeClaimWgsl = (
|
|
994
|
+
/* wgsl */
|
|
995
|
+
`
|
|
996
|
+
@compute @workgroup_size(WG)
|
|
997
|
+
fn dedupe_claim(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
|
|
998
|
+
var count = P.count;
|
|
999
|
+
if (P.countIndex != U32_MAX) { count = min(atomicLoad(&counters[P.countIndex]), P.count); } // a device-side count, clamped to the capacity
|
|
1000
|
+
for (var i = linear_id(wid, lid.x); i < count; i = i + P.stride) { // grid-stride; no barrier anywhere
|
|
1001
|
+
atomicStore(&owner[queue[i]], i);
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
`
|
|
1005
|
+
);
|
|
1006
|
+
const dedupeFilterWgsl = (
|
|
1007
|
+
/* wgsl */
|
|
1008
|
+
`
|
|
1009
|
+
var<workgroup> sh: array<u32, WG>;
|
|
1010
|
+
var<workgroup> base: u32;
|
|
1011
|
+
var<workgroup> wcount: u32;
|
|
1012
|
+
|
|
1013
|
+
@compute @workgroup_size(WG)
|
|
1014
|
+
fn dedupe_filter(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
|
|
1015
|
+
if (lid.x == 0u) {
|
|
1016
|
+
var count = P.count;
|
|
1017
|
+
if (P.countIndex != U32_MAX) { count = min(atomicLoad(&outCount[P.countIndex]), P.count); } // the same count source as the claim
|
|
1018
|
+
wcount = count;
|
|
1019
|
+
}
|
|
1020
|
+
let count = workgroupUniformLoad(&wcount); // uniform: the block loop below holds barriers
|
|
1021
|
+
for (var b0 = group_id(wid) * WG; b0 < count; b0 = b0 + P.stride) { // grid-stride over blocks of WG entries
|
|
1022
|
+
let i = b0 + lid.x;
|
|
1023
|
+
var keep = 0u;
|
|
1024
|
+
var v = 0u;
|
|
1025
|
+
if (i < count) { v = queue[i]; keep = select(0u, 1u, atomicLoad(&owner[v]) == i); } // guarded work into locals
|
|
1026
|
+
sh[lid.x] = keep;
|
|
1027
|
+
workgroupBarrier(); // every lane, unconditionally (3.5 rule 1)
|
|
1028
|
+
for (var s = 1u; s < WG; s = s * 2u) { // Hillis-Steele inclusive scan of keep
|
|
1029
|
+
var t = 0u;
|
|
1030
|
+
if (lid.x >= s) { t = sh[lid.x - s]; }
|
|
1031
|
+
workgroupBarrier();
|
|
1032
|
+
sh[lid.x] = sh[lid.x] + t;
|
|
1033
|
+
workgroupBarrier();
|
|
1034
|
+
}
|
|
1035
|
+
let inclusive = sh[lid.x];
|
|
1036
|
+
if (lid.x == WG - 1u) { base = atomicAdd(&outCount[P.outIndex], inclusive); } // ONE atomic per workgroup: the block's aggregate
|
|
1037
|
+
workgroupBarrier();
|
|
1038
|
+
if (keep == 1u) { out[base + inclusive - 1u] = v; }
|
|
1039
|
+
workgroupBarrier(); // sh and base are reused by the next block
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
`
|
|
1043
|
+
);
|
|
560
1044
|
const degreeWgsl = (
|
|
561
1045
|
/* wgsl */
|
|
562
1046
|
`
|
|
@@ -988,6 +1472,161 @@ fn fill(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid
|
|
|
988
1472
|
}
|
|
989
1473
|
`
|
|
990
1474
|
);
|
|
1475
|
+
const frontierFinalizeWgsl = (
|
|
1476
|
+
/* wgsl */
|
|
1477
|
+
`
|
|
1478
|
+
fn write_slot_groups(slot: u32, groups: u32, count: u32) { // groups workgroups, split in 2D above the per-dim limit
|
|
1479
|
+
var x = groups;
|
|
1480
|
+
var y = 1u;
|
|
1481
|
+
if (groups > MAX_WORKGROUPS_PER_DIM) {
|
|
1482
|
+
x = MAX_WORKGROUPS_PER_DIM;
|
|
1483
|
+
y = (groups + MAX_WORKGROUPS_PER_DIM - 1u) / MAX_WORKGROUPS_PER_DIM;
|
|
1484
|
+
}
|
|
1485
|
+
let base = 4u * (P.slotBase + slot); // 16-byte slots: (x, y, 1, count)
|
|
1486
|
+
args[base] = x; args[base + 1u] = y; args[base + 2u] = 1u; args[base + 3u] = count;
|
|
1487
|
+
}
|
|
1488
|
+
fn write_slot(slot: u32, count: u32) { // one INVOCATION per entry: ceil(count / wg) workgroups
|
|
1489
|
+
let groups = count / P.wg + select(0u, 1u, count % P.wg != 0u); // ceil(count / wg) without the u32 wrap (indirect-finalize's rule)
|
|
1490
|
+
write_slot_groups(slot, groups, count);
|
|
1491
|
+
}
|
|
1492
|
+
fn zero_slot(slot: u32) {
|
|
1493
|
+
let base = 4u * (P.slotBase + slot);
|
|
1494
|
+
args[base] = 0u; args[base + 1u] = 0u; args[base + 2u] = 1u; args[base + 3u] = 0u;
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
@compute @workgroup_size(WG)
|
|
1498
|
+
fn frontier_finalize(@builtin(local_invocation_id) lid: vec3<u32>) {
|
|
1499
|
+
if (lid.x != 0u) { return; } // one lane; no barrier follows (3.5 rule 1)
|
|
1500
|
+
if (P.role == 0u) { // the level boundary
|
|
1501
|
+
if (atomicLoad(&counters[15]) != 0u) { // done already: a no-op level the host recorded past the end
|
|
1502
|
+
for (var s = 0u; s < 7u; s = s + 1u) { zero_slot(s); } // this slotBase holds the previous submit's args
|
|
1503
|
+
return; // no counter word moves (P8-T6's levels formula reads them)
|
|
1504
|
+
}
|
|
1505
|
+
let finished = atomicLoad(&counters[0]);
|
|
1506
|
+
let next = atomicLoad(&counters[1]);
|
|
1507
|
+
let degSum = atomicLoad(&counters[2]);
|
|
1508
|
+
atomicStore(&counters[3], finished); // prevFrontierCount
|
|
1509
|
+
atomicStore(&counters[4], degSum); // prevDegreeSum
|
|
1510
|
+
atomicStore(&counters[0], next); // the rotation
|
|
1511
|
+
atomicStore(&counters[1], 0u);
|
|
1512
|
+
atomicStore(&counters[2], 0u);
|
|
1513
|
+
atomicStore(&counters[8], 0u); // edgeCount
|
|
1514
|
+
atomicStore(&counters[9], 0u); // edgeCountUnclamped
|
|
1515
|
+
atomicStore(&counters[12], atomicLoad(&counters[12]) + next); // visitedCount
|
|
1516
|
+
if (P.firstOfSubmit >= 1u) { // b = the boundary's index inside its submit, clamped to 2 (P8-T8, PD-18)
|
|
1517
|
+
atomicStore(&counters[5], atomicLoad(&counters[5]) - next); // unvisitedCount (exact): F_b was inside the submit's rebuilt sum iff b >= 1
|
|
1518
|
+
}
|
|
1519
|
+
if (P.firstOfSubmit >= 2u) {
|
|
1520
|
+
atomicStore(&counters[6], atomicLoad(&counters[6]) - degSum); // unvisitedDegreeSum (one level stale): F_{b-1} was inside it iff b >= 2
|
|
1521
|
+
}
|
|
1522
|
+
let level = atomicLoad(&counters[11]) + 1u; // the seed is U32_MAX, so the first boundary lands on 0
|
|
1523
|
+
atomicStore(&counters[11], level);
|
|
1524
|
+
let done = (next == 0u) || (level >= P.maxDepth);
|
|
1525
|
+
atomicStore(&counters[15], select(0u, 1u, done));
|
|
1526
|
+
var direction = atomicLoad(&counters[14]);
|
|
1527
|
+
if (P.mode == 1u) {
|
|
1528
|
+
direction = 0u; // top-down only (the test seam)
|
|
1529
|
+
} else if (direction == 0u) {
|
|
1530
|
+
if (degSum > atomicLoad(&counters[6]) / P.alpha && next > finished) { direction = 1u; } // m_f > m_u / alpha and growing
|
|
1531
|
+
} else {
|
|
1532
|
+
if (next * P.beta < atomicLoad(&counters[5]) && next < finished) { direction = 0u; } // next * beta < unvisited and shrinking
|
|
1533
|
+
}
|
|
1534
|
+
if (direction != atomicLoad(&counters[14])) { atomicStore(&counters[13], atomicLoad(&counters[13]) + 1u); } // switches
|
|
1535
|
+
var path = 0u; // word 24: what the level's kernels run (0 nothing, 1 two-phase, 2 fused, 3 bottom-up; role 1 writes 4 for the retry)
|
|
1536
|
+
if (done) {
|
|
1537
|
+
for (var s = 0u; s < 7u; s = s + 1u) { zero_slot(s); }
|
|
1538
|
+
} else if (direction == 1u) { // the bottom-up level (P8-T8): the bits fill, the bitset build, the sweep
|
|
1539
|
+
zero_slot(0u); zero_slot(1u); zero_slot(2u); zero_slot(6u);
|
|
1540
|
+
write_slot(3u, (P.n + 31u) / 32u); write_slot(4u, next); write_slot(5u, atomicLoad(&counters[7]));
|
|
1541
|
+
path = 3u;
|
|
1542
|
+
atomicStore(&counters[19], atomicLoad(&counters[19]) + 1u);
|
|
1543
|
+
} else if (next < P.fusedMax) { // P8-T7 makes this branch reachable (fusedMax is 0 until then)
|
|
1544
|
+
zero_slot(0u); zero_slot(1u); zero_slot(3u); zero_slot(4u); zero_slot(5u); zero_slot(6u);
|
|
1545
|
+
write_slot_groups(2u, next, next); // bfs-fused is one WORKGROUP per frontier entry
|
|
1546
|
+
path = 2u;
|
|
1547
|
+
atomicStore(&counters[17], atomicLoad(&counters[17]) + 1u);
|
|
1548
|
+
} else {
|
|
1549
|
+
zero_slot(2u); zero_slot(3u); zero_slot(4u); zero_slot(5u);
|
|
1550
|
+
write_slot(0u, next); // slots 1 and 6 are role 1's
|
|
1551
|
+
path = 1u;
|
|
1552
|
+
}
|
|
1553
|
+
atomicStore(&counters[14], direction);
|
|
1554
|
+
atomicStore(&counters[24], path);
|
|
1555
|
+
} else if (P.role == 1u) { // the edge queue is filled
|
|
1556
|
+
if (args[4u * P.slotBase] == 0u) { // role 0 did not choose the two-phase path (done, fused or bottom-up): nothing to size, nothing to count
|
|
1557
|
+
zero_slot(1u); zero_slot(6u);
|
|
1558
|
+
return;
|
|
1559
|
+
}
|
|
1560
|
+
let clamped = min(atomicLoad(&counters[8]), P.edgeCapacity);
|
|
1561
|
+
atomicStore(&counters[8], clamped);
|
|
1562
|
+
if (atomicLoad(&counters[9]) > P.edgeCapacity) { // PD-23: the fused retry
|
|
1563
|
+
let entries = atomicLoad(&counters[0]);
|
|
1564
|
+
zero_slot(1u); write_slot_groups(6u, entries, entries); // one workgroup per frontier entry, as slot 2
|
|
1565
|
+
atomicStore(&counters[24], 4u); // the path word: bfs-fused runs the retry, bfs-contract nothing
|
|
1566
|
+
atomicStore(&counters[10], atomicLoad(&counters[10]) + 1u);
|
|
1567
|
+
atomicStore(&counters[17], atomicLoad(&counters[17]) + 1u);
|
|
1568
|
+
} else {
|
|
1569
|
+
write_slot(1u, clamped); zero_slot(6u);
|
|
1570
|
+
atomicStore(&counters[18], atomicLoad(&counters[18]) + 1u); // twoPhaseLevels counts the CHOICE role 0 made, even for zero edges (P8-T7 Step 4's invariant)
|
|
1571
|
+
}
|
|
1572
|
+
} else if (P.role == 2u) { // the SSSP round boundary (P8-T9, PD-20): which pile this round relaxes
|
|
1573
|
+
atomicStore(&counters[8], 0u); // the dedupe counts (words 8 and 9, the SSSP sense) and the path word: nothing unless a pile is chosen below
|
|
1574
|
+
atomicStore(&counters[9], 0u);
|
|
1575
|
+
atomicStore(&counters[24], 0u);
|
|
1576
|
+
if (atomicLoad(&counters[15]) != 0u) { // done already: a no-op round the host recorded past the end (rule 1)
|
|
1577
|
+
for (var s = 0u; s < 7u; s = s + 1u) { zero_slot(s); }
|
|
1578
|
+
return;
|
|
1579
|
+
}
|
|
1580
|
+
let nearRaw = atomicLoad(&counters[1]); // the raw near half's appends, unclamped
|
|
1581
|
+
let farRaw = atomicLoad(&counters[21]); // the raw far half's appends, unclamped
|
|
1582
|
+
if (nearRaw > P.edgeCapacity || farRaw > P.edgeCapacity) { // a pile overflowed its half: the host raises E_TOO_LARGE
|
|
1583
|
+
atomicStore(&counters[15], 2u);
|
|
1584
|
+
for (var s = 0u; s < 7u; s = s + 1u) { zero_slot(s); }
|
|
1585
|
+
return;
|
|
1586
|
+
}
|
|
1587
|
+
zero_slot(2u); zero_slot(5u); zero_slot(6u); // role 3 sizes the relax slots once the piles are deduped
|
|
1588
|
+
if (nearRaw != 0u) { // a near round: dedupe the near half into nearIn
|
|
1589
|
+
atomicStore(&counters[0], 0u); // the deduped near count, accumulated by dedupe-filter
|
|
1590
|
+
atomicStore(&counters[14], 0u); // mode 0
|
|
1591
|
+
write_slot(0u, nearRaw); write_slot(1u, nearRaw); // dedupe-claim, dedupe-filter over the near half
|
|
1592
|
+
atomicStore(&counters[8], nearRaw); // the near dedupe's count word
|
|
1593
|
+
atomicStore(&counters[24], 5u); // the path word: sssp-relax role 0 runs, role 1 nothing
|
|
1594
|
+
zero_slot(3u); zero_slot(4u);
|
|
1595
|
+
atomicStore(&counters[11], atomicLoad(&counters[11]) + 1u); // rounds dispatched (the done boundary is not counted)
|
|
1596
|
+
} else if (farRaw != 0u) { // the near pile is empty: raise the threshold and re-bucket the far pile
|
|
1597
|
+
let threshold = bitcast<f32>(atomicLoad(&counters[22]));
|
|
1598
|
+
let raised = threshold + bitcast<f32>(atomicLoad(&counters[23])); // ONE f32 add on the bit patterns (PD-9)
|
|
1599
|
+
if (raised == threshold) { // the delta is below the threshold's ulp: the host raises E_UNSUPPORTED
|
|
1600
|
+
atomicStore(&counters[15], 3u);
|
|
1601
|
+
for (var s = 0u; s < 7u; s = s + 1u) { zero_slot(s); }
|
|
1602
|
+
return;
|
|
1603
|
+
}
|
|
1604
|
+
atomicStore(&counters[4], atomicLoad(&counters[22])); // prevThresholdBits: what the pass-through drops below
|
|
1605
|
+
atomicStore(&counters[22], bitcast<u32>(raised));
|
|
1606
|
+
atomicStore(&counters[20], 0u); // the deduped far count, accumulated by dedupe-filter
|
|
1607
|
+
atomicStore(&counters[14], 1u); // mode 1
|
|
1608
|
+
zero_slot(0u); zero_slot(1u);
|
|
1609
|
+
write_slot(3u, farRaw); write_slot(4u, farRaw); // dedupe-claim, dedupe-filter over the far half
|
|
1610
|
+
atomicStore(&counters[9], farRaw); // the far dedupe's count word
|
|
1611
|
+
atomicStore(&counters[24], 6u); // the path word: sssp-relax role 1 runs, role 0 nothing
|
|
1612
|
+
atomicStore(&counters[11], atomicLoad(&counters[11]) + 1u);
|
|
1613
|
+
} else { // both piles empty: finished
|
|
1614
|
+
atomicStore(&counters[15], 1u);
|
|
1615
|
+
for (var s = 0u; s < 7u; s = s + 1u) { zero_slot(s); }
|
|
1616
|
+
}
|
|
1617
|
+
} else if (P.role == 3u) { // the piles are deduped: size the relax
|
|
1618
|
+
if (atomicLoad(&counters[15]) != 0u) { return; } // role 2 zeroed every slot of the round
|
|
1619
|
+
if (atomicLoad(&counters[14]) == 0u) {
|
|
1620
|
+
write_slot(2u, atomicLoad(&counters[0])); // the near round over nearIn
|
|
1621
|
+
atomicStore(&counters[1], 0u); // the raw near half restarts
|
|
1622
|
+
} else {
|
|
1623
|
+
write_slot(5u, atomicLoad(&counters[20])); // the pass-through over farIn
|
|
1624
|
+
atomicStore(&counters[21], 0u); // the raw far half restarts (the pass-through re-appends what stays far)
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
`
|
|
1629
|
+
);
|
|
991
1630
|
const gridCellKeyWgsl = (
|
|
992
1631
|
/* wgsl */
|
|
993
1632
|
`
|
|
@@ -1729,6 +2368,108 @@ fn spmv_pull(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id
|
|
|
1729
2368
|
}
|
|
1730
2369
|
`
|
|
1731
2370
|
);
|
|
2371
|
+
const ssspPredWgsl = (
|
|
2372
|
+
/* wgsl */
|
|
2373
|
+
`
|
|
2374
|
+
@compute @workgroup_size(WG)
|
|
2375
|
+
fn sssp_pred(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
|
|
2376
|
+
let first = linear_id(wid, lid.x);
|
|
2377
|
+
let hb = ((P.n + 63u) / 64u) * 64u; // MODE 0 only: hops[v] is pred[hb + v]; pred[2 hb] is the changed word, pred[2 hb + 1] the orphan word (PD-27)
|
|
2378
|
+
if (P.role == 1u && P.iteration != 0u && atomicLoad(&pred[2u * hb]) < P.iteration) { return; } // the previous hop pass changed nothing: converged (no barrier anywhere, so the early return is legal)
|
|
2379
|
+
for (var u = first; u < P.n; u = u + P.stride) { // grid-stride over the rows (planGridStride(n)); no barrier anywhere
|
|
2380
|
+
let du = dist[u];
|
|
2381
|
+
var unreached = false;
|
|
2382
|
+
if (MODE == 1u) { unreached = du == INVALID_INDEX; } else { unreached = du == F32_INF_BITS; }
|
|
2383
|
+
if (unreached) { continue; }
|
|
2384
|
+
var hu = 0u; // u's hop count (MODE 0, roles 1 and 2)
|
|
2385
|
+
if (MODE == 0u && P.role != 0u) {
|
|
2386
|
+
hu = atomicLoad(&pred[hb + u]);
|
|
2387
|
+
if (hu == INVALID_INDEX) { // the key has not reached u yet
|
|
2388
|
+
if (P.role == 2u && u != P.source) { atomicAdd(&pred[2u * hb + 1u], 1u); } // an orphan: only bellmanFord can make one (P8-T10 Step 3)
|
|
2389
|
+
continue;
|
|
2390
|
+
}
|
|
2391
|
+
}
|
|
2392
|
+
let end = min(rowPtr[u + 1u], P.arcEnd);
|
|
2393
|
+
for (var a = max(rowPtr[u], P.arcBase); a < end; a = a + 1u) {
|
|
2394
|
+
let v = colIdx[a - P.arcBase];
|
|
2395
|
+
if (v == P.source) { continue; } // the source keeps INVALID_INDEX whatever attains it (a zero-weight arc could)
|
|
2396
|
+
let dv = dist[v];
|
|
2397
|
+
var tight = false; // the arc explains dist[v]
|
|
2398
|
+
var below = false; // and its source sits at a strictly smaller distance
|
|
2399
|
+
if (MODE == 1u) {
|
|
2400
|
+
tight = (du + 1u) == dv; // depth mode: BFS parent, one depth down
|
|
2401
|
+
} else {
|
|
2402
|
+
let w = select(1.0, weights[a - P.arcBase], HAS_WEIGHTS);
|
|
2403
|
+
tight = (dv != F32_INF_BITS) && (bitcast<u32>(bitcast<f32>(du) + w) == dv); // one f32 add, compared as the bit pattern PD-9 stores; never into an unreached v (an overflowed sum is +Inf too)
|
|
2404
|
+
below = bitcast<f32>(du) < bitcast<f32>(dv);
|
|
2405
|
+
}
|
|
2406
|
+
if (!tight) { continue; }
|
|
2407
|
+
var admit = false; // this arc is one key step below v
|
|
2408
|
+
if (MODE == 1u) {
|
|
2409
|
+
admit = true;
|
|
2410
|
+
} else if (P.role == 0u) { // the roots pass (the plateau rule only; bellmanFord seeds the source alone)
|
|
2411
|
+
if (P.mode == 0u && below) { atomicMin(&pred[hb + v], 0u); }
|
|
2412
|
+
} else if (P.role == 1u) { // a hop pass: one hop along a plateau arc (mode 0) or along any tight arc (mode 1)
|
|
2413
|
+
if (P.mode == 1u || du == dv) {
|
|
2414
|
+
let old = atomicMin(&pred[hb + v], hu + 1u);
|
|
2415
|
+
if (hu + 1u < old) { atomicMax(&pred[2u * hb], P.iteration + 1u); } // this pass changed something
|
|
2416
|
+
}
|
|
2417
|
+
} else { // the predecessor pass: the smallest tight arc one key step below v
|
|
2418
|
+
let hv = atomicLoad(&pred[hb + v]);
|
|
2419
|
+
if (P.mode == 1u) { admit = hu + 1u == hv; } else if (hv == 0u) { admit = below; } else { admit = (du == dv) && (hu + 1u == hv); }
|
|
2420
|
+
}
|
|
2421
|
+
if (admit) { atomicMin(&pred[v], select(a, u, P.predKind == 1u)); }
|
|
2422
|
+
}
|
|
2423
|
+
}
|
|
2424
|
+
}
|
|
2425
|
+
`
|
|
2426
|
+
);
|
|
2427
|
+
const ssspRelaxWgsl = (
|
|
2428
|
+
/* wgsl */
|
|
2429
|
+
`
|
|
2430
|
+
@compute @workgroup_size(WG)
|
|
2431
|
+
fn sssp_relax(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
|
|
2432
|
+
let first = linear_id(wid, lid.x);
|
|
2433
|
+
let mine = select(5u, 6u, P.role == 1u); // the path word role 2 wrote: 5 a near round, 6 a far pass-through
|
|
2434
|
+
let chosen = atomicLoad(&counters[24]) == mine; // the other role's dispatch of the round is a no-op
|
|
2435
|
+
let count = select(0u, min(atomicLoad(&counters[select(0u, 20u, P.role == 1u)]), P.n), chosen); // the deduped near or far pile
|
|
2436
|
+
let threshold = bitcast<f32>(atomicLoad(&counters[22]));
|
|
2437
|
+
let cutoff = bitcast<f32>(P.cutoffBits);
|
|
2438
|
+
for (var i = first; i < count; i = i + P.stride) { // no barrier anywhere: the loops may be per lane
|
|
2439
|
+
let u = queueIn[i];
|
|
2440
|
+
let du = bitcast<f32>(atomicLoad(&dist[u]));
|
|
2441
|
+
if (P.role == 1u) { // the pass-through (PD-20): re-bucket a far entry
|
|
2442
|
+
if (du < bitcast<f32>(atomicLoad(&counters[4]))) { continue; } // below the previous threshold: relaxed in an earlier bucket
|
|
2443
|
+
if (du < threshold) {
|
|
2444
|
+
let q = atomicAdd(&counters[1], 1u);
|
|
2445
|
+
if (q < P.edgeCapacity) { queueOut[q] = u; }
|
|
2446
|
+
} else {
|
|
2447
|
+
let q = atomicAdd(&counters[21], 1u);
|
|
2448
|
+
if (q < P.edgeCapacity) { queueOut[P.edgeCapacity + q] = u; }
|
|
2449
|
+
}
|
|
2450
|
+
continue;
|
|
2451
|
+
}
|
|
2452
|
+
let end = rowPtr[u + 1u];
|
|
2453
|
+
for (var a = rowPtr[u]; a < end; a = a + 1u) { // the whole row: never windowed (DEP-P8-E)
|
|
2454
|
+
let v = colIdx[a];
|
|
2455
|
+
let nd = du + select(1.0, weights[a], HAS_WEIGHTS); // ONE f32 add (PD-9)
|
|
2456
|
+
if (nd > cutoff) { continue; } // SsspOptions.cutoff: the CPU port's dv <= cutoff
|
|
2457
|
+
let bits = bitcast<u32>(nd);
|
|
2458
|
+
let old = atomicMin(&dist[v], bits); // exact on non-negative floats
|
|
2459
|
+
if (bits < old) { // this lane improved v, so it owns the append
|
|
2460
|
+
if (nd < threshold) {
|
|
2461
|
+
let q = atomicAdd(&counters[1], 1u);
|
|
2462
|
+
if (q < P.edgeCapacity) { queueOut[q] = v; }
|
|
2463
|
+
} else {
|
|
2464
|
+
let q = atomicAdd(&counters[21], 1u);
|
|
2465
|
+
if (q < P.edgeCapacity) { queueOut[P.edgeCapacity + q] = v; }
|
|
2466
|
+
}
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2469
|
+
}
|
|
2470
|
+
}
|
|
2471
|
+
`
|
|
2472
|
+
);
|
|
1732
2473
|
const wccCompressWgsl = (
|
|
1733
2474
|
/* wgsl */
|
|
1734
2475
|
`
|
|
@@ -2029,6 +2770,81 @@ const RADIX_PARAMS = UniformBlock.define("RadixParams", [
|
|
|
2029
2770
|
["groups", "u32"],
|
|
2030
2771
|
["pad0", "u32"]
|
|
2031
2772
|
]);
|
|
2773
|
+
const COMPACT_PARAMS = UniformBlock.define("CompactParams", [
|
|
2774
|
+
["count", "u32"],
|
|
2775
|
+
["outIndex", "u32"],
|
|
2776
|
+
["countIndex", "u32"],
|
|
2777
|
+
["stride", "u32"]
|
|
2778
|
+
]);
|
|
2779
|
+
const FRONTIER_COUNTERS = UniformBlock.define(
|
|
2780
|
+
"FrontierCounters",
|
|
2781
|
+
[
|
|
2782
|
+
["frontierCount", "u32"],
|
|
2783
|
+
["nextFrontierCount", "u32"],
|
|
2784
|
+
["frontierDegreeSum", "u32"],
|
|
2785
|
+
["prevFrontierCount", "u32"],
|
|
2786
|
+
["prevDegreeSum", "u32"],
|
|
2787
|
+
["unvisitedCount", "u32"],
|
|
2788
|
+
["unvisitedDegreeSum", "u32"],
|
|
2789
|
+
["unvisitedListLen", "u32"],
|
|
2790
|
+
["edgeCount", "u32"],
|
|
2791
|
+
["edgeCountUnclamped", "u32"],
|
|
2792
|
+
["overflowLevels", "u32"],
|
|
2793
|
+
["level", "u32"],
|
|
2794
|
+
["visitedCount", "u32"],
|
|
2795
|
+
["switches", "u32"],
|
|
2796
|
+
["direction", "u32"],
|
|
2797
|
+
["done", "u32"],
|
|
2798
|
+
["arcsScanned", "u32"],
|
|
2799
|
+
["fusedLevels", "u32"],
|
|
2800
|
+
["twoPhaseLevels", "u32"],
|
|
2801
|
+
["bottomUpLevels", "u32"],
|
|
2802
|
+
["farCount", "u32"],
|
|
2803
|
+
["nextFarCount", "u32"],
|
|
2804
|
+
["thresholdBits", "u32"],
|
|
2805
|
+
["deltaBits", "u32"],
|
|
2806
|
+
["path", "u32"]
|
|
2807
|
+
],
|
|
2808
|
+
{ layout: "storage" }
|
|
2809
|
+
);
|
|
2810
|
+
const FRONTIER_PARAMS = UniformBlock.define("FrontierParams", [
|
|
2811
|
+
["role", "u32"],
|
|
2812
|
+
["slotBase", "u32"],
|
|
2813
|
+
["wg", "u32"],
|
|
2814
|
+
["alpha", "u32"],
|
|
2815
|
+
["beta", "u32"],
|
|
2816
|
+
["fusedMax", "u32"],
|
|
2817
|
+
["edgeCapacity", "u32"],
|
|
2818
|
+
["maxDepth", "u32"],
|
|
2819
|
+
["n", "u32"],
|
|
2820
|
+
["mode", "u32"],
|
|
2821
|
+
["cutoffBits", "u32"],
|
|
2822
|
+
["arcBase", "u32"],
|
|
2823
|
+
["arcEnd", "u32"],
|
|
2824
|
+
["predKind", "u32"],
|
|
2825
|
+
["bitsBase", "u32"],
|
|
2826
|
+
["source", "u32"],
|
|
2827
|
+
["stride", "u32"],
|
|
2828
|
+
["firstOfSubmit", "u32"],
|
|
2829
|
+
["iteration", "u32"],
|
|
2830
|
+
["pad1", "u32"]
|
|
2831
|
+
]);
|
|
2832
|
+
const BF_PARAMS = UniformBlock.define("BfParams", [
|
|
2833
|
+
["edgeCount", "u32"],
|
|
2834
|
+
["stride", "u32"],
|
|
2835
|
+
["maxRetries", "u32"],
|
|
2836
|
+
["cutoffBits", "u32"]
|
|
2837
|
+
]);
|
|
2838
|
+
const BF_FLAGS = UniformBlock.define(
|
|
2839
|
+
"BfFlags",
|
|
2840
|
+
[
|
|
2841
|
+
["changed", "u32"],
|
|
2842
|
+
["retryExhausted", "u32"],
|
|
2843
|
+
["pad0", "u32"],
|
|
2844
|
+
["pad1", "u32"]
|
|
2845
|
+
],
|
|
2846
|
+
{ layout: "storage" }
|
|
2847
|
+
);
|
|
2032
2848
|
function decl(group, binding, name, kind, wgslType) {
|
|
2033
2849
|
return { group, binding, name, kind, wgslType };
|
|
2034
2850
|
}
|
|
@@ -2535,26 +3351,277 @@ const GRID_NEAR_FIELD = {
|
|
|
2535
3351
|
snippetSlots: [],
|
|
2536
3352
|
phase: "P4"
|
|
2537
3353
|
};
|
|
2538
|
-
const
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
"
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
"
|
|
3354
|
+
const COMPACT_SCATTER = {
|
|
3355
|
+
id: "compact-scatter",
|
|
3356
|
+
body: compactScatterWgsl,
|
|
3357
|
+
entryPoint: "compact_scatter",
|
|
3358
|
+
bindings: [
|
|
3359
|
+
decl(1, 0, "queue", "storage-ro", "array<u32>"),
|
|
3360
|
+
decl(1, 1, "flags", "storage-ro", "array<u32>"),
|
|
3361
|
+
decl(1, 2, "offsets", "storage-ro", "array<u32>"),
|
|
3362
|
+
decl(1, 3, "out", "storage", "array<u32>"),
|
|
3363
|
+
decl(1, 4, "outCount", "storage", "array<u32>"),
|
|
3364
|
+
decl(2, 0, "P", "uniform", "CompactParams")
|
|
3365
|
+
],
|
|
3366
|
+
overrideDecls: [],
|
|
3367
|
+
uniforms: [COMPACT_PARAMS],
|
|
3368
|
+
needs: [],
|
|
3369
|
+
snippetSlots: [],
|
|
3370
|
+
phase: "P8"
|
|
3371
|
+
};
|
|
3372
|
+
const DEDUPE_CLAIM = {
|
|
3373
|
+
id: "dedupe-claim",
|
|
3374
|
+
body: dedupeClaimWgsl,
|
|
3375
|
+
entryPoint: "dedupe_claim",
|
|
3376
|
+
bindings: [
|
|
3377
|
+
decl(1, 0, "queue", "storage-ro", "array<u32>"),
|
|
3378
|
+
decl(1, 1, "owner", "storage", "array<atomic<u32>>"),
|
|
3379
|
+
decl(1, 2, "counters", "storage", "array<atomic<u32>>"),
|
|
3380
|
+
decl(2, 0, "P", "uniform", "CompactParams")
|
|
3381
|
+
],
|
|
3382
|
+
overrideDecls: [],
|
|
3383
|
+
uniforms: [COMPACT_PARAMS],
|
|
3384
|
+
needs: [],
|
|
3385
|
+
snippetSlots: [],
|
|
3386
|
+
phase: "P8"
|
|
3387
|
+
};
|
|
3388
|
+
const DEDUPE_FILTER = {
|
|
3389
|
+
id: "dedupe-filter",
|
|
3390
|
+
body: dedupeFilterWgsl,
|
|
3391
|
+
entryPoint: "dedupe_filter",
|
|
3392
|
+
bindings: [
|
|
3393
|
+
decl(1, 0, "queue", "storage-ro", "array<u32>"),
|
|
3394
|
+
decl(1, 1, "owner", "storage", "array<atomic<u32>>"),
|
|
3395
|
+
decl(1, 2, "out", "storage", "array<u32>"),
|
|
3396
|
+
decl(1, 3, "outCount", "storage", "array<atomic<u32>>"),
|
|
3397
|
+
decl(2, 0, "P", "uniform", "CompactParams")
|
|
3398
|
+
],
|
|
3399
|
+
overrideDecls: [],
|
|
3400
|
+
uniforms: [COMPACT_PARAMS],
|
|
3401
|
+
needs: [],
|
|
3402
|
+
snippetSlots: [],
|
|
3403
|
+
phase: "P8"
|
|
3404
|
+
};
|
|
3405
|
+
const FRONTIER_FINALIZE = {
|
|
3406
|
+
id: "frontier-finalize",
|
|
3407
|
+
body: frontierFinalizeWgsl,
|
|
3408
|
+
entryPoint: "frontier_finalize",
|
|
3409
|
+
bindings: [
|
|
3410
|
+
decl(1, 0, "counters", "storage", "array<atomic<u32>>"),
|
|
3411
|
+
decl(1, 1, "args", "storage", "array<u32>"),
|
|
3412
|
+
decl(2, 0, "P", "uniform", "FrontierParams")
|
|
3413
|
+
],
|
|
3414
|
+
overrideDecls: [],
|
|
3415
|
+
uniforms: [FRONTIER_PARAMS],
|
|
3416
|
+
needs: [],
|
|
3417
|
+
snippetSlots: [],
|
|
3418
|
+
phase: "P8"
|
|
3419
|
+
};
|
|
3420
|
+
const ADVANCE_EXPAND = {
|
|
3421
|
+
id: "advance-expand",
|
|
3422
|
+
body: advanceExpandWgsl,
|
|
3423
|
+
entryPoint: "advance_expand",
|
|
3424
|
+
bindings: GRAPH_SLOTS.concat(
|
|
3425
|
+
decl(1, 0, "frontierIn", "storage-ro", "array<u32>"),
|
|
3426
|
+
decl(1, 1, "counters", "storage", "array<atomic<u32>>"),
|
|
3427
|
+
decl(1, 2, "edgeQueue", "storage", "array<u32>"),
|
|
3428
|
+
decl(2, 0, "P", "uniform", "FrontierParams")
|
|
3429
|
+
),
|
|
3430
|
+
overrideDecls: [],
|
|
3431
|
+
uniforms: [FRONTIER_PARAMS],
|
|
3432
|
+
needs: ["subgroups"],
|
|
3433
|
+
snippetSlots: [],
|
|
3434
|
+
phase: "P8"
|
|
3435
|
+
};
|
|
3436
|
+
const BFS_CONTRACT = {
|
|
3437
|
+
id: "bfs-contract",
|
|
3438
|
+
body: bfsContractWgsl,
|
|
3439
|
+
entryPoint: "bfs_contract",
|
|
3440
|
+
bindings: [
|
|
3441
|
+
decl(1, 0, "edgeQueue", "storage-ro", "array<u32>"),
|
|
3442
|
+
decl(1, 1, "counters", "storage", "array<atomic<u32>>"),
|
|
3443
|
+
decl(1, 2, "depth", "storage", "array<atomic<u32>>"),
|
|
3444
|
+
decl(1, 3, "frontierOut", "storage", "array<u32>"),
|
|
3445
|
+
decl(2, 0, "P", "uniform", "FrontierParams")
|
|
3446
|
+
],
|
|
3447
|
+
overrideDecls: [],
|
|
3448
|
+
uniforms: [FRONTIER_PARAMS],
|
|
3449
|
+
needs: [],
|
|
3450
|
+
snippetSlots: [],
|
|
3451
|
+
phase: "P8"
|
|
3452
|
+
};
|
|
3453
|
+
const SSSP_PRED = {
|
|
3454
|
+
id: "sssp-pred",
|
|
3455
|
+
body: ssspPredWgsl,
|
|
3456
|
+
entryPoint: "sssp_pred",
|
|
3457
|
+
bindings: GRAPH_SLOTS.concat(
|
|
3458
|
+
decl(1, 0, "dist", "storage-ro", "array<u32>"),
|
|
3459
|
+
decl(1, 1, "pred", "storage", "array<atomic<u32>>"),
|
|
3460
|
+
decl(2, 0, "P", "uniform", "FrontierParams")
|
|
3461
|
+
),
|
|
3462
|
+
overrideDecls: [{ name: "MODE", type: "u32", default: 0 }],
|
|
3463
|
+
uniforms: [FRONTIER_PARAMS],
|
|
3464
|
+
needs: [],
|
|
3465
|
+
snippetSlots: [],
|
|
3466
|
+
phase: "P8"
|
|
3467
|
+
};
|
|
3468
|
+
const BFS_FUSED = {
|
|
3469
|
+
id: "bfs-fused",
|
|
3470
|
+
body: bfsFusedWgsl,
|
|
3471
|
+
entryPoint: "bfs_fused",
|
|
3472
|
+
bindings: GRAPH_SLOTS.concat(
|
|
3473
|
+
decl(1, 0, "frontierIn", "storage-ro", "array<u32>"),
|
|
3474
|
+
decl(1, 1, "counters", "storage", "array<atomic<u32>>"),
|
|
3475
|
+
decl(1, 2, "depth", "storage", "array<atomic<u32>>"),
|
|
3476
|
+
decl(1, 3, "frontierOut", "storage", "array<u32>"),
|
|
3477
|
+
decl(2, 0, "P", "uniform", "FrontierParams")
|
|
3478
|
+
),
|
|
3479
|
+
overrideDecls: [],
|
|
3480
|
+
uniforms: [FRONTIER_PARAMS],
|
|
3481
|
+
needs: [],
|
|
3482
|
+
snippetSlots: [],
|
|
3483
|
+
phase: "P8"
|
|
3484
|
+
};
|
|
3485
|
+
const BFS_BOTTOM_UP = {
|
|
3486
|
+
id: "bfs-bottom-up",
|
|
3487
|
+
body: bfsBottomUpWgsl,
|
|
3488
|
+
entryPoint: "bfs_bottom_up",
|
|
3489
|
+
bindings: GRAPH_SLOTS.concat(
|
|
3490
|
+
decl(1, 0, "sweepIn", "storage-ro", "array<u32>"),
|
|
3491
|
+
decl(1, 1, "counters", "storage", "array<atomic<u32>>"),
|
|
3492
|
+
decl(1, 2, "depth", "storage", "array<atomic<u32>>"),
|
|
3493
|
+
decl(1, 3, "frontierOut", "storage", "array<u32>"),
|
|
3494
|
+
decl(2, 0, "P", "uniform", "FrontierParams")
|
|
3495
|
+
),
|
|
3496
|
+
overrideDecls: [],
|
|
3497
|
+
uniforms: [FRONTIER_PARAMS],
|
|
3498
|
+
needs: ["subgroups"],
|
|
3499
|
+
snippetSlots: [],
|
|
3500
|
+
phase: "P8"
|
|
3501
|
+
};
|
|
3502
|
+
const BFS_BITSET_BUILD = {
|
|
3503
|
+
id: "bfs-bitset-build",
|
|
3504
|
+
body: bfsBitsetBuildWgsl,
|
|
3505
|
+
entryPoint: "bfs_bitset_build",
|
|
3506
|
+
bindings: [
|
|
3507
|
+
decl(1, 0, "frontierIn", "storage-ro", "array<u32>"),
|
|
3508
|
+
decl(1, 1, "counters", "storage", "array<atomic<u32>>"),
|
|
3509
|
+
decl(1, 2, "bits", "storage", "array<atomic<u32>>"),
|
|
3510
|
+
decl(2, 0, "P", "uniform", "FrontierParams")
|
|
3511
|
+
],
|
|
3512
|
+
overrideDecls: [],
|
|
3513
|
+
uniforms: [FRONTIER_PARAMS],
|
|
3514
|
+
needs: [],
|
|
3515
|
+
snippetSlots: [],
|
|
3516
|
+
phase: "P8"
|
|
3517
|
+
};
|
|
3518
|
+
const BFS_UNVISITED_FLAGS = {
|
|
3519
|
+
id: "bfs-unvisited-flags",
|
|
3520
|
+
body: bfsUnvisitedFlagsWgsl,
|
|
3521
|
+
entryPoint: "bfs_unvisited_flags",
|
|
3522
|
+
bindings: [
|
|
3523
|
+
decl(1, 0, "outDegree", "storage-ro", "array<u32>"),
|
|
3524
|
+
decl(1, 1, "inDegree", "storage-ro", "array<u32>"),
|
|
3525
|
+
decl(1, 2, "depth", "storage-ro", "array<u32>"),
|
|
3526
|
+
decl(1, 3, "flags", "storage", "array<u32>"),
|
|
3527
|
+
decl(1, 4, "counters", "storage", "array<atomic<u32>>"),
|
|
3528
|
+
decl(2, 0, "P", "uniform", "FrontierParams")
|
|
3529
|
+
],
|
|
3530
|
+
overrideDecls: [],
|
|
3531
|
+
uniforms: [FRONTIER_PARAMS],
|
|
3532
|
+
needs: ["subgroups"],
|
|
3533
|
+
snippetSlots: [],
|
|
3534
|
+
phase: "P8"
|
|
3535
|
+
};
|
|
3536
|
+
const SSSP_RELAX = {
|
|
3537
|
+
id: "sssp-relax",
|
|
3538
|
+
body: ssspRelaxWgsl,
|
|
3539
|
+
entryPoint: "sssp_relax",
|
|
3540
|
+
bindings: GRAPH_SLOTS.concat(
|
|
3541
|
+
decl(1, 0, "dist", "storage", "array<atomic<u32>>"),
|
|
3542
|
+
decl(1, 1, "counters", "storage", "array<atomic<u32>>"),
|
|
3543
|
+
decl(1, 2, "queueIn", "storage-ro", "array<u32>"),
|
|
3544
|
+
decl(1, 3, "queueOut", "storage", "array<u32>"),
|
|
3545
|
+
decl(2, 0, "P", "uniform", "FrontierParams")
|
|
3546
|
+
),
|
|
3547
|
+
overrideDecls: [],
|
|
3548
|
+
uniforms: [FRONTIER_PARAMS],
|
|
3549
|
+
needs: [],
|
|
3550
|
+
snippetSlots: [],
|
|
3551
|
+
phase: "P8"
|
|
3552
|
+
};
|
|
3553
|
+
const BF_RELAX = {
|
|
3554
|
+
id: "bf-relax",
|
|
3555
|
+
body: bfRelaxWgsl,
|
|
3556
|
+
entryPoint: "bf_relax",
|
|
3557
|
+
bindings: [
|
|
3558
|
+
decl(1, 0, "edgeSrc", "storage-ro", "array<u32>"),
|
|
3559
|
+
decl(1, 1, "edgeDst", "storage-ro", "array<u32>"),
|
|
3560
|
+
decl(1, 2, "edgeToArc", "storage-ro", "array<u32>"),
|
|
3561
|
+
decl(1, 3, "weights", "storage-ro", "array<f32>"),
|
|
3562
|
+
decl(1, 4, "dist", "storage", "array<atomic<u32>>"),
|
|
3563
|
+
decl(1, 5, "flags", "storage", "array<atomic<u32>>"),
|
|
3564
|
+
decl(2, 0, "P", "uniform", "BfParams")
|
|
3565
|
+
],
|
|
3566
|
+
overrideDecls: [{ name: "UNDIRECTED", type: "bool", default: false }],
|
|
3567
|
+
uniforms: [BF_PARAMS],
|
|
3568
|
+
needs: [],
|
|
3569
|
+
snippetSlots: [],
|
|
3570
|
+
phase: "P8"
|
|
3571
|
+
};
|
|
3572
|
+
const CLOSENESS_SWEEP = {
|
|
3573
|
+
id: "closeness-sweep",
|
|
3574
|
+
body: closenessSweepWgsl,
|
|
3575
|
+
entryPoint: "closeness_sweep",
|
|
3576
|
+
bindings: GRAPH_SLOTS.concat(
|
|
3577
|
+
decl(1, 0, "frontierList", "storage-ro", "array<u32>"),
|
|
3578
|
+
decl(1, 1, "counters", "storage", "array<atomic<u32>>"),
|
|
3579
|
+
decl(1, 2, "bits", "storage", "array<atomic<u32>>"),
|
|
3580
|
+
decl(1, 3, "perSource", "storage", "array<atomic<u32>>"),
|
|
3581
|
+
decl(2, 0, "P", "uniform", "FrontierParams")
|
|
3582
|
+
),
|
|
3583
|
+
overrideDecls: [],
|
|
3584
|
+
uniforms: [FRONTIER_PARAMS],
|
|
3585
|
+
needs: [],
|
|
3586
|
+
snippetSlots: [],
|
|
3587
|
+
phase: "P8"
|
|
3588
|
+
};
|
|
3589
|
+
const CLOSENESS_REDUCE = {
|
|
3590
|
+
id: "closeness-reduce",
|
|
3591
|
+
body: closenessReduceWgsl,
|
|
3592
|
+
entryPoint: "closeness_reduce",
|
|
3593
|
+
bindings: [
|
|
3594
|
+
decl(1, 0, "counters", "storage", "array<atomic<u32>>"),
|
|
3595
|
+
decl(1, 1, "perSource", "storage", "array<atomic<u32>>"),
|
|
3596
|
+
decl(1, 2, "bits", "storage", "array<u32>"),
|
|
3597
|
+
decl(2, 0, "P", "uniform", "FrontierParams")
|
|
3598
|
+
],
|
|
3599
|
+
overrideDecls: [],
|
|
3600
|
+
uniforms: [FRONTIER_PARAMS],
|
|
3601
|
+
needs: [],
|
|
3602
|
+
snippetSlots: [],
|
|
3603
|
+
phase: "P8"
|
|
3604
|
+
};
|
|
3605
|
+
const REGISTRY = Object.freeze({
|
|
3606
|
+
degree: DEGREE,
|
|
3607
|
+
reduce: REDUCE,
|
|
3608
|
+
fill: FILL,
|
|
3609
|
+
"segmented-reduce": SEGMENTED_REDUCE,
|
|
3610
|
+
"fa2-stats-finalize": FA2_STATS_FINALIZE,
|
|
3611
|
+
"fa2-attraction": FA2_ATTRACTION,
|
|
3612
|
+
"fa2-repulsion-exact": FA2_REPULSION_EXACT,
|
|
3613
|
+
"fa2-speed-finalize": FA2_SPEED_FINALIZE,
|
|
3614
|
+
"fa2-integrate": FA2_INTEGRATE,
|
|
3615
|
+
"fa2-to-scene": FA2_TO_SCENE,
|
|
3616
|
+
"spmv-pull": SPMV_PULL,
|
|
3617
|
+
"pr-scale": PR_SCALE,
|
|
3618
|
+
"pr-finalize": PR_FINALIZE,
|
|
3619
|
+
"wcc-link-sample": WCC_LINK_SAMPLE,
|
|
3620
|
+
"wcc-link-edges": WCC_LINK_EDGES,
|
|
3621
|
+
"wcc-compress": WCC_COMPRESS,
|
|
3622
|
+
"wcc-sample": WCC_SAMPLE,
|
|
3623
|
+
"indirect-finalize": INDIRECT_FINALIZE,
|
|
3624
|
+
"scan-block": SCAN_BLOCK,
|
|
2558
3625
|
"scan-add": SCAN_ADD,
|
|
2559
3626
|
histogram: HISTOGRAM,
|
|
2560
3627
|
"counting-scatter": COUNTING_SCATTER,
|
|
@@ -2565,7 +3632,22 @@ const REGISTRY = Object.freeze({
|
|
|
2565
3632
|
"grid-centroid-hub": GRID_CENTROID_HUB,
|
|
2566
3633
|
"grid-downsample": GRID_DOWNSAMPLE,
|
|
2567
3634
|
"grid-far-field": GRID_FAR_FIELD,
|
|
2568
|
-
"grid-near-field": GRID_NEAR_FIELD
|
|
3635
|
+
"grid-near-field": GRID_NEAR_FIELD,
|
|
3636
|
+
"compact-scatter": COMPACT_SCATTER,
|
|
3637
|
+
"dedupe-claim": DEDUPE_CLAIM,
|
|
3638
|
+
"dedupe-filter": DEDUPE_FILTER,
|
|
3639
|
+
"frontier-finalize": FRONTIER_FINALIZE,
|
|
3640
|
+
"advance-expand": ADVANCE_EXPAND,
|
|
3641
|
+
"bfs-contract": BFS_CONTRACT,
|
|
3642
|
+
"sssp-pred": SSSP_PRED,
|
|
3643
|
+
"bfs-fused": BFS_FUSED,
|
|
3644
|
+
"bfs-bottom-up": BFS_BOTTOM_UP,
|
|
3645
|
+
"bfs-bitset-build": BFS_BITSET_BUILD,
|
|
3646
|
+
"bfs-unvisited-flags": BFS_UNVISITED_FLAGS,
|
|
3647
|
+
"sssp-relax": SSSP_RELAX,
|
|
3648
|
+
"bf-relax": BF_RELAX,
|
|
3649
|
+
"closeness-sweep": CLOSENESS_SWEEP,
|
|
3650
|
+
"closeness-reduce": CLOSENESS_REDUCE
|
|
2569
3651
|
});
|
|
2570
3652
|
const bodyOverrides = /* @__PURE__ */ new Map();
|
|
2571
3653
|
function entryOf(id) {
|
|
@@ -2724,7 +3806,7 @@ class ScanPlannerImpl {
|
|
|
2724
3806
|
}
|
|
2725
3807
|
const POISON = 3735928559;
|
|
2726
3808
|
const CHECK_BLOCKS = 32;
|
|
2727
|
-
const RING_SLOTS$
|
|
3809
|
+
const RING_SLOTS$6 = 8;
|
|
2728
3810
|
const checked = /* @__PURE__ */ new WeakMap();
|
|
2729
3811
|
function inputAt(i) {
|
|
2730
3812
|
return i + 1;
|
|
@@ -2752,7 +3834,7 @@ async function runCheck(ctx) {
|
|
|
2752
3834
|
const count = CHECK_BLOCKS * wg + 1;
|
|
2753
3835
|
const bytes = 4 * count;
|
|
2754
3836
|
const lease = ctx.pool.lease();
|
|
2755
|
-
const ring = new UniformRing(ctx.device, ctx.allocator, RING_SLOTS$
|
|
3837
|
+
const ring = new UniformRing(ctx.device, ctx.allocator, RING_SLOTS$6, "device-check/ring");
|
|
2756
3838
|
try {
|
|
2757
3839
|
const scope = {
|
|
2758
3840
|
device: ctx.device,
|
|
@@ -2892,8 +3974,8 @@ function rowCountOf(core, primitive) {
|
|
|
2892
3974
|
function arcCountOf(core) {
|
|
2893
3975
|
return core.colIdx === null ? 0 : core.colIdx.size / 4;
|
|
2894
3976
|
}
|
|
2895
|
-
function windowBinding(core, name,
|
|
2896
|
-
const buffer = core.arcBuffers?.[name][
|
|
3977
|
+
function windowBinding(core, name, w) {
|
|
3978
|
+
const buffer = core.arcBuffers?.[name][w.bufferIndex];
|
|
2897
3979
|
if (buffer === void 0) {
|
|
2898
3980
|
throw new WebGpuGraphError("E_INVALID_ARGUMENT", `windowBinding: the core has no window buffers of ${name}`, {
|
|
2899
3981
|
argument: "core",
|
|
@@ -2901,7 +3983,21 @@ function windowBinding(core, name, w2) {
|
|
|
2901
3983
|
expected: "a windowed core whose plan uploaded the array"
|
|
2902
3984
|
});
|
|
2903
3985
|
}
|
|
2904
|
-
return { buffer, offset:
|
|
3986
|
+
return { buffer, offset: w.offset, size: 4 * (w.end - w.start), window: w };
|
|
3987
|
+
}
|
|
3988
|
+
function coreWindows(core) {
|
|
3989
|
+
if (core.windows === null) {
|
|
3990
|
+
return [{ arcBase: 0, arcEnd: arcCountOf(core), core }];
|
|
3991
|
+
}
|
|
3992
|
+
return core.windows.map((w, k, all) => ({
|
|
3993
|
+
arcBase: w.start,
|
|
3994
|
+
arcEnd: k + 1 < all.length ? all[k + 1].start : w.end,
|
|
3995
|
+
core: {
|
|
3996
|
+
...core,
|
|
3997
|
+
colIdx: windowBinding(core, "colIdx", w),
|
|
3998
|
+
weights: core.weights === null ? null : windowBinding(core, "weights", w)
|
|
3999
|
+
}
|
|
4000
|
+
}));
|
|
2905
4001
|
}
|
|
2906
4002
|
function assertNotWindowed(core, primitive) {
|
|
2907
4003
|
if (core.plan === "windowed" || core.windows !== null) {
|
|
@@ -2919,22 +4015,22 @@ function assertWholeCore(core, arcCount, limit, algorithm) {
|
|
|
2919
4015
|
);
|
|
2920
4016
|
}
|
|
2921
4017
|
}
|
|
2922
|
-
function coreOfView(
|
|
2923
|
-
const { rowPtr } =
|
|
4018
|
+
function coreOfView(v, arcCount) {
|
|
4019
|
+
const { rowPtr } = v.bindings;
|
|
2924
4020
|
if (rowPtr === void 0) {
|
|
2925
|
-
throw new WebGpuGraphError("E_INVALID_ARGUMENT", `the ${
|
|
4021
|
+
throw new WebGpuGraphError("E_INVALID_ARGUMENT", `the ${v.view} view has no rowPtr binding`, {
|
|
2926
4022
|
argument: "view",
|
|
2927
|
-
value:
|
|
4023
|
+
value: v.view,
|
|
2928
4024
|
expected: "a view with a rowPtr binding (reverse)"
|
|
2929
4025
|
});
|
|
2930
4026
|
}
|
|
2931
|
-
const colIdx =
|
|
2932
|
-
const weights =
|
|
4027
|
+
const colIdx = v.bindings.colIdx === void 0 || v.bindings.colIdx.size === 0 ? null : v.bindings.colIdx;
|
|
4028
|
+
const weights = v.bindings.weights === void 0 || v.bindings.weights.size === 0 ? null : v.bindings.weights;
|
|
2933
4029
|
const bound = colIdx === null ? 0 : colIdx.size / 4;
|
|
2934
4030
|
if (bound !== arcCount) {
|
|
2935
4031
|
throw new WebGpuGraphError(
|
|
2936
4032
|
"E_INVALID_ARGUMENT",
|
|
2937
|
-
`the ${
|
|
4033
|
+
`the ${v.view} view binds ${bound} arcs but its arcCount scalar says ${arcCount}`,
|
|
2938
4034
|
{
|
|
2939
4035
|
argument: "arcCount",
|
|
2940
4036
|
value: arcCount,
|
|
@@ -2955,7 +4051,7 @@ function coreOfView(v2, arcCount) {
|
|
|
2955
4051
|
hasWeights: weights !== null
|
|
2956
4052
|
});
|
|
2957
4053
|
}
|
|
2958
|
-
function checkDest$
|
|
4054
|
+
function checkDest$5(dest, n) {
|
|
2959
4055
|
if (dest === void 0) {
|
|
2960
4056
|
return null;
|
|
2961
4057
|
}
|
|
@@ -2984,7 +4080,7 @@ async function degree(ctx, s, options) {
|
|
|
2984
4080
|
ctx.assertReady();
|
|
2985
4081
|
await assertDeviceComputes(ctx);
|
|
2986
4082
|
const n = s.nodeCount;
|
|
2987
|
-
const dest = checkDest$
|
|
4083
|
+
const dest = checkDest$5(options?.dest, n);
|
|
2988
4084
|
if (options?.signal?.aborted) {
|
|
2989
4085
|
throw new WebGpuGraphError("E_ABORTED", "degree: the signal was aborted before any work started", {});
|
|
2990
4086
|
}
|
|
@@ -3021,22 +4117,22 @@ async function degree(ctx, s, options) {
|
|
|
3021
4117
|
const fill = await ctx.pipelines.kernel(kernelSpec("fill"));
|
|
3022
4118
|
const zero = fill.bind({ dst: outBinding, P: params(FILL_PARAMS, { count: n, value: 0, mode: 0 }) });
|
|
3023
4119
|
fill.dispatch(pass, zero, plan1d(n, ctx.workgroupSize, ctx.caps), [0]);
|
|
3024
|
-
for (const
|
|
4120
|
+
for (const w of core.windows) {
|
|
3025
4121
|
const windowed = {
|
|
3026
4122
|
...core,
|
|
3027
|
-
colIdx: windowBinding(core, "colIdx",
|
|
3028
|
-
weights: core.weights === null ? null : windowBinding(core, "weights",
|
|
4123
|
+
colIdx: windowBinding(core, "colIdx", w),
|
|
4124
|
+
weights: core.weights === null ? null : windowBinding(core, "weights", w)
|
|
3029
4125
|
};
|
|
3030
4126
|
const P = params(RANGE_PARAMS, {
|
|
3031
|
-
start:
|
|
3032
|
-
end:
|
|
3033
|
-
arcBase:
|
|
3034
|
-
arcEnd:
|
|
4127
|
+
start: w.rowFirst,
|
|
4128
|
+
end: w.rowLast + 1,
|
|
4129
|
+
arcBase: w.start,
|
|
4130
|
+
arcEnd: w.end,
|
|
3035
4131
|
accumulate: 1,
|
|
3036
4132
|
n
|
|
3037
4133
|
});
|
|
3038
4134
|
const bound = kernel.bind({ ...graphBindings(windowed, null), out: outBinding, P });
|
|
3039
|
-
kernel.dispatch(pass, bound, plan1d(
|
|
4135
|
+
kernel.dispatch(pass, bound, plan1d(w.rowLast - w.rowFirst + 1, ctx.workgroupSize, ctx.caps), [0]);
|
|
3040
4136
|
}
|
|
3041
4137
|
}
|
|
3042
4138
|
pass.end();
|
|
@@ -3342,9 +4438,9 @@ class CommandBatch {
|
|
|
3342
4438
|
countDispatches(pass) {
|
|
3343
4439
|
const direct = pass.dispatchWorkgroups.bind(pass);
|
|
3344
4440
|
const indirect = pass.dispatchWorkgroupsIndirect.bind(pass);
|
|
3345
|
-
pass.dispatchWorkgroups = (
|
|
4441
|
+
pass.dispatchWorkgroups = (x, y, z) => {
|
|
3346
4442
|
this.dispatchCount += 1;
|
|
3347
|
-
direct(
|
|
4443
|
+
direct(x, y, z);
|
|
3348
4444
|
return void 0;
|
|
3349
4445
|
};
|
|
3350
4446
|
pass.dispatchWorkgroupsIndirect = (buffer, offset) => {
|
|
@@ -3390,6 +4486,11 @@ function algorithmScope(ctx, label, slots) {
|
|
|
3390
4486
|
pool: ctx.pool,
|
|
3391
4487
|
workgroupSize: ctx.workgroupSize,
|
|
3392
4488
|
scratch: (byteLength, scratchLabel) => lease.storage(byteLength, `${label}/${scratchLabel}`),
|
|
4489
|
+
indirect: (byteLength, indirectLabel) => lease.acquire(
|
|
4490
|
+
byteLength,
|
|
4491
|
+
BufferUsage.STORAGE | BufferUsage.INDIRECT | BufferUsage.COPY_DST | BufferUsage.COPY_SRC,
|
|
4492
|
+
`${label}/${indirectLabel}`
|
|
4493
|
+
),
|
|
3393
4494
|
params(block, values) {
|
|
3394
4495
|
const slot = ring.reserve(1);
|
|
3395
4496
|
ring.write(slot, block, values);
|
|
@@ -3401,16 +4502,17 @@ function algorithmScope(ctx, label, slots) {
|
|
|
3401
4502
|
dispose() {
|
|
3402
4503
|
ring.destroy();
|
|
3403
4504
|
lease.release();
|
|
3404
|
-
}
|
|
4505
|
+
},
|
|
4506
|
+
ringOverruns: () => ring.overruns
|
|
3405
4507
|
};
|
|
3406
4508
|
}
|
|
3407
|
-
const ALGORITHM = "connectedComponents";
|
|
3408
|
-
const ROUNDS_PER_BATCH = 4;
|
|
4509
|
+
const ALGORITHM$4 = "connectedComponents";
|
|
4510
|
+
const ROUNDS_PER_BATCH$1 = 4;
|
|
3409
4511
|
const MAX_WCC_ROUNDS = 64;
|
|
3410
4512
|
const SAMPLE_SIZE = 1024;
|
|
3411
4513
|
const MAX_STEPS = 1024;
|
|
3412
|
-
const RING_SLOTS$
|
|
3413
|
-
function checkDest$
|
|
4514
|
+
const RING_SLOTS$5 = 2 * ROUNDS_PER_BATCH$1;
|
|
4515
|
+
function checkDest$4(dest, n) {
|
|
3414
4516
|
if (dest === void 0) {
|
|
3415
4517
|
return null;
|
|
3416
4518
|
}
|
|
@@ -3419,7 +4521,7 @@ function checkDest$2(dest, n) {
|
|
|
3419
4521
|
}
|
|
3420
4522
|
throw new WebGpuGraphError(
|
|
3421
4523
|
"E_INVALID_ARGUMENT",
|
|
3422
|
-
`${ALGORITHM}: dest must be a Uint32Array of length ${n} over an ArrayBuffer`,
|
|
4524
|
+
`${ALGORITHM$4}: dest must be a Uint32Array of length ${n} over an ArrayBuffer`,
|
|
3423
4525
|
{
|
|
3424
4526
|
argument: "dest",
|
|
3425
4527
|
value: `${dest.constructor.name}(${dest.length})`,
|
|
@@ -3429,10 +4531,10 @@ function checkDest$2(dest, n) {
|
|
|
3429
4531
|
}
|
|
3430
4532
|
function coreOf$2(ctx, s) {
|
|
3431
4533
|
const core = ctx.residency.core(s);
|
|
3432
|
-
assertWholeCore(core, s.arcCount, ctx.caps.limits.maxStorageBufferBindingSize, ALGORITHM);
|
|
4534
|
+
assertWholeCore(core, s.arcCount, ctx.caps.limits.maxStorageBufferBindingSize, ALGORITHM$4);
|
|
3433
4535
|
return core;
|
|
3434
4536
|
}
|
|
3435
|
-
function bindingOf$
|
|
4537
|
+
function bindingOf$3(buffer, size) {
|
|
3436
4538
|
return { buffer, offset: 0, size, window: null };
|
|
3437
4539
|
}
|
|
3438
4540
|
function labelResult(labels, count) {
|
|
@@ -3448,19 +4550,19 @@ function labelResult(labels, count) {
|
|
|
3448
4550
|
const dense = new Uint32Array(n).fill(U32_MAX$2);
|
|
3449
4551
|
const sizes = new Uint32Array(count);
|
|
3450
4552
|
let next = 0;
|
|
3451
|
-
for (let
|
|
3452
|
-
let k = dense[labels[
|
|
4553
|
+
for (let v = 0; v < n; v++) {
|
|
4554
|
+
let k = dense[labels[v]];
|
|
3453
4555
|
if (k === U32_MAX$2) {
|
|
3454
4556
|
k = next++;
|
|
3455
|
-
dense[labels[
|
|
4557
|
+
dense[labels[v]] = k;
|
|
3456
4558
|
}
|
|
3457
4559
|
sizes[k]++;
|
|
3458
4560
|
}
|
|
3459
4561
|
const built = Array.from(sizes, (size) => new Uint32Array(size));
|
|
3460
4562
|
const filled = new Uint32Array(count);
|
|
3461
|
-
for (let
|
|
3462
|
-
const k = dense[labels[
|
|
3463
|
-
built[k][filled[k]++] =
|
|
4563
|
+
for (let v = 0; v < n; v++) {
|
|
4564
|
+
const k = dense[labels[v]];
|
|
4565
|
+
built[k][filled[k]++] = v;
|
|
3464
4566
|
}
|
|
3465
4567
|
groups = built;
|
|
3466
4568
|
return built;
|
|
@@ -3485,11 +4587,11 @@ function checkLabels(raw) {
|
|
|
3485
4587
|
const n = raw.length;
|
|
3486
4588
|
const seen = new Uint8Array(n);
|
|
3487
4589
|
let count = 0;
|
|
3488
|
-
for (let
|
|
3489
|
-
const label = raw[
|
|
4590
|
+
for (let v = 0; v < n; v++) {
|
|
4591
|
+
const label = raw[v];
|
|
3490
4592
|
if (label >= n) {
|
|
3491
|
-
throw new WebGpuGraphError("E_VALIDATION", `${ALGORITHM}: labels[${
|
|
3492
|
-
label: `${ALGORITHM}/labels`,
|
|
4593
|
+
throw new WebGpuGraphError("E_VALIDATION", `${ALGORITHM$4}: labels[${v}] = ${label} is not a node index`, {
|
|
4594
|
+
label: `${ALGORITHM$4}/labels`,
|
|
3493
4595
|
message: `the device produced a label outside [0, ${n})`
|
|
3494
4596
|
});
|
|
3495
4597
|
}
|
|
@@ -3505,9 +4607,9 @@ async function connectedComponents(ctx, s, options) {
|
|
|
3505
4607
|
await assertDeviceComputes(ctx);
|
|
3506
4608
|
const n = s.nodeCount;
|
|
3507
4609
|
const renumber = options?.renumber !== false;
|
|
3508
|
-
const dest = checkDest$
|
|
4610
|
+
const dest = checkDest$4(options?.dest, n);
|
|
3509
4611
|
if (options?.signal?.aborted) {
|
|
3510
|
-
throw new WebGpuGraphError("E_ABORTED", `${ALGORITHM}: the signal was aborted before any work started`, {});
|
|
4612
|
+
throw new WebGpuGraphError("E_ABORTED", `${ALGORITHM$4}: the signal was aborted before any work started`, {});
|
|
3511
4613
|
}
|
|
3512
4614
|
if (n === 0) {
|
|
3513
4615
|
options?.onProgress?.(1, 1);
|
|
@@ -3516,15 +4618,15 @@ async function connectedComponents(ctx, s, options) {
|
|
|
3516
4618
|
const core = coreOf$2(ctx, s);
|
|
3517
4619
|
if (s.arcCount === 0) {
|
|
3518
4620
|
const labels = dest ?? new Uint32Array(n);
|
|
3519
|
-
for (let
|
|
3520
|
-
labels[
|
|
4621
|
+
for (let v = 0; v < n; v++) {
|
|
4622
|
+
labels[v] = v;
|
|
3521
4623
|
}
|
|
3522
4624
|
options?.onProgress?.(1, 1);
|
|
3523
4625
|
return labelResult(labels, n);
|
|
3524
4626
|
}
|
|
3525
4627
|
const edges = ctx.residency.view(s, "edgeList");
|
|
3526
4628
|
const edgeCount = edges.scalars.edgeCount[0];
|
|
3527
|
-
const scope = algorithmScope(ctx, ALGORITHM, RING_SLOTS$
|
|
4629
|
+
const scope = algorithmScope(ctx, ALGORITHM$4, RING_SLOTS$5);
|
|
3528
4630
|
try {
|
|
3529
4631
|
const compBytes = 4 * (n + 1);
|
|
3530
4632
|
const comp = scope.scratch(compBytes, "comp");
|
|
@@ -3537,8 +4639,8 @@ async function connectedComponents(ctx, s, options) {
|
|
|
3537
4639
|
const compress = await ctx.pipelines.kernel(kernelSpec("wcc-compress", {}));
|
|
3538
4640
|
const sample = await ctx.pipelines.kernel(kernelSpec("wcc-sample", {}));
|
|
3539
4641
|
const { queue } = ctx.device;
|
|
3540
|
-
const compBinding = bindingOf$
|
|
3541
|
-
const histBinding = bindingOf$
|
|
4642
|
+
const compBinding = bindingOf$3(comp, compBytes);
|
|
4643
|
+
const histBinding = bindingOf$3(hist, 4 * items);
|
|
3542
4644
|
const rowPlan = planGridStride(n, ctx.workgroupSize, ctx.caps);
|
|
3543
4645
|
const edgePlan = planGridStride(edgeCount, ctx.workgroupSize, ctx.caps);
|
|
3544
4646
|
const flagIndex = n;
|
|
@@ -3553,12 +4655,12 @@ async function connectedComponents(ctx, s, options) {
|
|
|
3553
4655
|
return batch.submit();
|
|
3554
4656
|
};
|
|
3555
4657
|
queue.writeBuffer(comp, 4 * flagIndex, zero);
|
|
3556
|
-
const setup = new CommandBatch(ctx, `${ALGORITHM}/setup`);
|
|
4658
|
+
const setup = new CommandBatch(ctx, `${ALGORITHM$4}/setup`);
|
|
3557
4659
|
let pass = setup.pass("sample-rounds");
|
|
3558
4660
|
const fillParams = scope.params(FILL_PARAMS, { count: n, value: 0, mode: 1, pad0: 0 });
|
|
3559
4661
|
fill.dispatch(
|
|
3560
4662
|
pass,
|
|
3561
|
-
fill.bind({ dst: bindingOf$
|
|
4663
|
+
fill.bind({ dst: bindingOf$3(comp, 4 * n), P: fillParams.binding }),
|
|
3562
4664
|
plan1d(n, ctx.workgroupSize, ctx.caps),
|
|
3563
4665
|
[fillParams.offset]
|
|
3564
4666
|
);
|
|
@@ -3572,7 +4674,7 @@ async function connectedComponents(ctx, s, options) {
|
|
|
3572
4674
|
setup.endPass();
|
|
3573
4675
|
await submit(setup).readback;
|
|
3574
4676
|
ctx.assertReady();
|
|
3575
|
-
const sampler = new CommandBatch(ctx, `${ALGORITHM}/sample`);
|
|
4677
|
+
const sampler = new CommandBatch(ctx, `${ALGORITHM$4}/sample`);
|
|
3576
4678
|
pass = sampler.pass("sample");
|
|
3577
4679
|
const sampleParams = wccParams({ items, stride: 0, r: 0, giant: U32_MAX$2 });
|
|
3578
4680
|
sample.dispatch(
|
|
@@ -3590,9 +4692,9 @@ async function connectedComponents(ctx, s, options) {
|
|
|
3590
4692
|
let rounds = 0;
|
|
3591
4693
|
for (; ; ) {
|
|
3592
4694
|
queue.writeBuffer(comp, 4 * flagIndex, zero);
|
|
3593
|
-
const batch = new CommandBatch(ctx, `${ALGORITHM}/rounds`);
|
|
4695
|
+
const batch = new CommandBatch(ctx, `${ALGORITHM$4}/rounds`);
|
|
3594
4696
|
pass = batch.pass("edge-rounds");
|
|
3595
|
-
for (let i = 0; i < ROUNDS_PER_BATCH; i++) {
|
|
4697
|
+
for (let i = 0; i < ROUNDS_PER_BATCH$1; i++) {
|
|
3596
4698
|
const params = wccParams({ items: edgeCount, stride: edgePlan.stride ?? edgeCount, r: 0, giant });
|
|
3597
4699
|
const bound = linkEdges.bind({ ...edgeBindings, P: params.binding });
|
|
3598
4700
|
linkEdges.dispatch(pass, bound, edgePlan, [params.offset]);
|
|
@@ -3602,10 +4704,10 @@ async function connectedComponents(ctx, s, options) {
|
|
|
3602
4704
|
const flagRequest = batch.readback(comp, 4 * flagIndex, 4);
|
|
3603
4705
|
const submitted = submit(batch);
|
|
3604
4706
|
const back = await submitted.readback;
|
|
3605
|
-
rounds += ROUNDS_PER_BATCH;
|
|
4707
|
+
rounds += ROUNDS_PER_BATCH$1;
|
|
3606
4708
|
ctx.assertReady();
|
|
3607
4709
|
if (options?.signal?.aborted) {
|
|
3608
|
-
throw new WebGpuGraphError("E_ABORTED", `${ALGORITHM}: the signal was aborted`, {
|
|
4710
|
+
throw new WebGpuGraphError("E_ABORTED", `${ALGORITHM$4}: the signal was aborted`, {
|
|
3609
4711
|
batchId: submitted.id
|
|
3610
4712
|
});
|
|
3611
4713
|
}
|
|
@@ -3615,12 +4717,12 @@ async function connectedComponents(ctx, s, options) {
|
|
|
3615
4717
|
if (rounds >= MAX_WCC_ROUNDS) {
|
|
3616
4718
|
throw new WebGpuGraphError(
|
|
3617
4719
|
"E_VALIDATION",
|
|
3618
|
-
`${ALGORITHM}: the changed flag never settled in ${MAX_WCC_ROUNDS} rounds`,
|
|
3619
|
-
{ label: ALGORITHM, message: `the changed flag never settled in ${MAX_WCC_ROUNDS} rounds` }
|
|
4720
|
+
`${ALGORITHM$4}: the changed flag never settled in ${MAX_WCC_ROUNDS} rounds`,
|
|
4721
|
+
{ label: ALGORITHM$4, message: `the changed flag never settled in ${MAX_WCC_ROUNDS} rounds` }
|
|
3620
4722
|
);
|
|
3621
4723
|
}
|
|
3622
4724
|
}
|
|
3623
|
-
const final = new CommandBatch(ctx, `${ALGORITHM}/final`);
|
|
4725
|
+
const final = new CommandBatch(ctx, `${ALGORITHM$4}/final`);
|
|
3624
4726
|
recordCompress(final.pass("compress"));
|
|
3625
4727
|
final.endPass();
|
|
3626
4728
|
await submit(final).readback;
|
|
@@ -3801,13 +4903,13 @@ class TieredPlanner {
|
|
|
3801
4903
|
this.dispatches++;
|
|
3802
4904
|
}
|
|
3803
4905
|
const { windows } = core;
|
|
3804
|
-
windows.forEach((
|
|
4906
|
+
windows.forEach((w, k) => {
|
|
3805
4907
|
const windowed = {
|
|
3806
4908
|
...core,
|
|
3807
|
-
colIdx: windowBinding(core, "colIdx",
|
|
3808
|
-
weights: core.weights === null ? null : windowBinding(core, "weights",
|
|
4909
|
+
colIdx: windowBinding(core, "colIdx", w),
|
|
4910
|
+
weights: core.weights === null ? null : windowBinding(core, "weights", w)
|
|
3809
4911
|
};
|
|
3810
|
-
this.recordWindow(pass, windowed, out, n,
|
|
4912
|
+
this.recordWindow(pass, windowed, out, n, w, k + 1 < windows.length ? windows[k + 1].start : w.end);
|
|
3811
4913
|
});
|
|
3812
4914
|
}
|
|
3813
4915
|
/**
|
|
@@ -3824,16 +4926,16 @@ class TieredPlanner {
|
|
|
3824
4926
|
* @param w - the window, or null for the single whole-core dispatch
|
|
3825
4927
|
* @param nextStart - the next window's first arc (w.end for the last window)
|
|
3826
4928
|
*/
|
|
3827
|
-
recordWindow(pass, core, out, n,
|
|
4929
|
+
recordWindow(pass, core, out, n, w, nextStart) {
|
|
3828
4930
|
const wg = this.scope.workgroupSize;
|
|
3829
4931
|
for (const { tier, kernel, start, end } of this.tiers) {
|
|
3830
4932
|
let first = start;
|
|
3831
4933
|
let last = tier === 0 ? n : end;
|
|
3832
|
-
let arcEnd =
|
|
3833
|
-
if (
|
|
3834
|
-
first =
|
|
3835
|
-
last =
|
|
3836
|
-
arcEnd =
|
|
4934
|
+
let arcEnd = w === null ? arcCountOf(core) : Math.min(w.end, nextStart);
|
|
4935
|
+
if (w !== null && this.perm === null) {
|
|
4936
|
+
first = w.rowFirst;
|
|
4937
|
+
last = w.rowLast + 1;
|
|
4938
|
+
arcEnd = w.end;
|
|
3837
4939
|
}
|
|
3838
4940
|
const rows = last - first;
|
|
3839
4941
|
if (rows <= 0) {
|
|
@@ -3842,9 +4944,9 @@ class TieredPlanner {
|
|
|
3842
4944
|
const params = this.scope.params(RANGE_PARAMS, {
|
|
3843
4945
|
start: first,
|
|
3844
4946
|
end: last,
|
|
3845
|
-
arcBase:
|
|
4947
|
+
arcBase: w === null ? 0 : w.start,
|
|
3846
4948
|
arcEnd,
|
|
3847
|
-
accumulate:
|
|
4949
|
+
accumulate: w !== null || this.accumulate ? 1 : 0,
|
|
3848
4950
|
n
|
|
3849
4951
|
});
|
|
3850
4952
|
const bound = kernel.bind({ ...graphBindings(core, this.perm), out, P: params.binding });
|
|
@@ -3986,8 +5088,8 @@ async function prepareSpmvPull(scope, rev, options) {
|
|
|
3986
5088
|
return new SpmvPullPlannerImpl(scope, compiled, perm, options.weights);
|
|
3987
5089
|
}
|
|
3988
5090
|
const PR_BATCH = 8;
|
|
3989
|
-
const RING_SLOTS$
|
|
3990
|
-
function checkDest$
|
|
5091
|
+
const RING_SLOTS$4 = 2 * PR_BATCH + 1;
|
|
5092
|
+
function checkDest$3(dest, n, algorithm) {
|
|
3991
5093
|
if (dest === void 0) {
|
|
3992
5094
|
return null;
|
|
3993
5095
|
}
|
|
@@ -4009,10 +5111,10 @@ function coreOf$1(ctx, s, algorithm) {
|
|
|
4009
5111
|
assertWholeCore(core, s.arcCount, ctx.caps.limits.maxStorageBufferBindingSize, algorithm);
|
|
4010
5112
|
return core;
|
|
4011
5113
|
}
|
|
4012
|
-
function bindingOf(buffer, size) {
|
|
5114
|
+
function bindingOf$2(buffer, size) {
|
|
4013
5115
|
return { buffer, offset: 0, size, window: null };
|
|
4014
5116
|
}
|
|
4015
|
-
function aborted$
|
|
5117
|
+
function aborted$3(algorithm, batchId) {
|
|
4016
5118
|
return new WebGpuGraphError(
|
|
4017
5119
|
"E_ABORTED",
|
|
4018
5120
|
`${algorithm}: the signal was aborted`,
|
|
@@ -4034,9 +5136,9 @@ async function run(ctx, s, personalization, options, algorithm) {
|
|
|
4034
5136
|
expected: "a positive integer"
|
|
4035
5137
|
});
|
|
4036
5138
|
}
|
|
4037
|
-
const dest = checkDest$
|
|
5139
|
+
const dest = checkDest$3(options?.dest, n, algorithm);
|
|
4038
5140
|
if (options?.signal?.aborted) {
|
|
4039
|
-
throw aborted$
|
|
5141
|
+
throw aborted$3(algorithm);
|
|
4040
5142
|
}
|
|
4041
5143
|
if (n === 0) {
|
|
4042
5144
|
options?.onProgress?.(maxIterations, maxIterations);
|
|
@@ -4064,7 +5166,7 @@ async function run(ctx, s, personalization, options, algorithm) {
|
|
|
4064
5166
|
const weights = useWeights ? void 0 : null;
|
|
4065
5167
|
const weightedCore = useWeights ? core : { ...core, weights: null, hasWeights: false };
|
|
4066
5168
|
const weightedRev = useWeights ? rev : { ...rev, weights: null, hasWeights: false };
|
|
4067
|
-
const scope = algorithmScope(ctx, algorithm, RING_SLOTS$
|
|
5169
|
+
const scope = algorithmScope(ctx, algorithm, RING_SLOTS$4);
|
|
4068
5170
|
let uploaded = null;
|
|
4069
5171
|
try {
|
|
4070
5172
|
const bytes = 4 * n;
|
|
@@ -4100,10 +5202,10 @@ async function run(ctx, s, personalization, options, algorithm) {
|
|
|
4100
5202
|
const header = new ArrayBuffer(PR_PARTIAL.byteLength);
|
|
4101
5203
|
PR_PARTIAL.write(new DataView(header), { firstConverged: U32_MAX$2, iteration: 0 });
|
|
4102
5204
|
queue.writeBuffer(partials, 0, header);
|
|
4103
|
-
const rank = [bindingOf(rankA, bytes), bindingOf(rankB, bytes)];
|
|
4104
|
-
const xNormBinding = bindingOf(xNorm, bytes);
|
|
4105
|
-
const outWeightSumBinding = bindingOf(outWeightSum, bytes);
|
|
4106
|
-
const partialsBinding = bindingOf(partials, partialsBytes);
|
|
5205
|
+
const rank = [bindingOf$2(rankA, bytes), bindingOf$2(rankB, bytes)];
|
|
5206
|
+
const xNormBinding = bindingOf$2(xNorm, bytes);
|
|
5207
|
+
const outWeightSumBinding = bindingOf$2(outWeightSum, bytes);
|
|
5208
|
+
const partialsBinding = bindingOf$2(partials, partialsBytes);
|
|
4107
5209
|
const coefficients = { alpha, beta: 1 - alpha, uniformP: 1 / n };
|
|
4108
5210
|
let cur = 0;
|
|
4109
5211
|
let iterationsRun = 0;
|
|
@@ -4156,7 +5258,7 @@ async function run(ctx, s, personalization, options, algorithm) {
|
|
|
4156
5258
|
iterationsRun += k;
|
|
4157
5259
|
ctx.assertReady();
|
|
4158
5260
|
if (options?.signal?.aborted) {
|
|
4159
|
-
throw aborted$
|
|
5261
|
+
throw aborted$3(algorithm, submitted.id);
|
|
4160
5262
|
}
|
|
4161
5263
|
options?.onProgress?.(iterationsRun, maxIterations);
|
|
4162
5264
|
const folded = PR_PARTIAL.read(new DataView(back), headerRequest.offset);
|
|
@@ -4199,8 +5301,8 @@ async function personalizedPageRank(ctx, s, personalization, options) {
|
|
|
4199
5301
|
);
|
|
4200
5302
|
}
|
|
4201
5303
|
let total = 0;
|
|
4202
|
-
for (let
|
|
4203
|
-
const mass = personalization[
|
|
5304
|
+
for (let v = 0; v < n; v++) {
|
|
5305
|
+
const mass = personalization[v];
|
|
4204
5306
|
if (!Number.isFinite(mass) || mass < 0) {
|
|
4205
5307
|
throw invalid2(mass, "finite and non-negative in every entry");
|
|
4206
5308
|
}
|
|
@@ -4210,14 +5312,14 @@ async function personalizedPageRank(ctx, s, personalization, options) {
|
|
|
4210
5312
|
throw invalid2(total, "a vector whose entries sum to a positive number");
|
|
4211
5313
|
}
|
|
4212
5314
|
const normalised2 = new Float32Array(n);
|
|
4213
|
-
for (let
|
|
4214
|
-
normalised2[
|
|
5315
|
+
for (let v = 0; v < n; v++) {
|
|
5316
|
+
normalised2[v] = personalization[v] / total;
|
|
4215
5317
|
}
|
|
4216
5318
|
return run(ctx, s, normalised2, options, "personalizedPageRank");
|
|
4217
5319
|
}
|
|
4218
5320
|
const BATCH = 8;
|
|
4219
|
-
const RING_SLOTS = 4 * BATCH + 8;
|
|
4220
|
-
function checkDest(dest, n, algorithm) {
|
|
5321
|
+
const RING_SLOTS$3 = 4 * BATCH + 8;
|
|
5322
|
+
function checkDest$2(dest, n, algorithm) {
|
|
4221
5323
|
if (dest === void 0) {
|
|
4222
5324
|
return null;
|
|
4223
5325
|
}
|
|
@@ -4243,7 +5345,7 @@ function reverseOf(ctx, s) {
|
|
|
4243
5345
|
const view = ctx.residency.view(s, "reverse");
|
|
4244
5346
|
return coreOfView(view, view.scalars.arcCount[0]);
|
|
4245
5347
|
}
|
|
4246
|
-
function aborted(algorithm, batchId) {
|
|
5348
|
+
function aborted$2(algorithm, batchId) {
|
|
4247
5349
|
return new WebGpuGraphError(
|
|
4248
5350
|
"E_ABORTED",
|
|
4249
5351
|
`${algorithm}: the signal was aborted`,
|
|
@@ -4255,7 +5357,7 @@ function whole(buffer, size) {
|
|
|
4255
5357
|
}
|
|
4256
5358
|
async function runPowerIteration(ctx, n, config) {
|
|
4257
5359
|
await assertDeviceComputes(ctx);
|
|
4258
|
-
const scope = algorithmScope(ctx, config.label, RING_SLOTS);
|
|
5360
|
+
const scope = algorithmScope(ctx, config.label, RING_SLOTS$3);
|
|
4259
5361
|
try {
|
|
4260
5362
|
const bytes = 4 * n;
|
|
4261
5363
|
const ring = (config.alternate === null ? ["rankA", "rankB"] : ["rankA", "rankB", "rankC"]).map(
|
|
@@ -4328,7 +5430,7 @@ async function runPowerIteration(ctx, n, config) {
|
|
|
4328
5430
|
iterationsRun += k;
|
|
4329
5431
|
ctx.assertReady();
|
|
4330
5432
|
if (config.signal?.aborted === true) {
|
|
4331
|
-
throw aborted(config.label, submitted.id);
|
|
5433
|
+
throw aborted$2(config.label, submitted.id);
|
|
4332
5434
|
}
|
|
4333
5435
|
config.onProgress?.(iterationsRun, config.maxIterations);
|
|
4334
5436
|
const folded = PR_PARTIAL.read(new DataView(back), headerRequest.offset);
|
|
@@ -4363,9 +5465,9 @@ function resolve(ctx, s, options, algorithm) {
|
|
|
4363
5465
|
expected: "a positive integer"
|
|
4364
5466
|
});
|
|
4365
5467
|
}
|
|
4366
|
-
const dest = checkDest(options?.dest, n, algorithm);
|
|
5468
|
+
const dest = checkDest$2(options?.dest, n, algorithm);
|
|
4367
5469
|
if (options?.signal?.aborted) {
|
|
4368
|
-
throw aborted(algorithm);
|
|
5470
|
+
throw aborted$2(algorithm);
|
|
4369
5471
|
}
|
|
4370
5472
|
return { n, maxIterations, tolerance, weights: options?.weighted === false ? null : void 0, dest };
|
|
4371
5473
|
}
|
|
@@ -4382,13 +5484,13 @@ function finite(value, fallback, argument) {
|
|
|
4382
5484
|
}
|
|
4383
5485
|
function normalised(raw, dest, norm) {
|
|
4384
5486
|
let acc = 0;
|
|
4385
|
-
for (const
|
|
4386
|
-
acc += norm === "sum" ? Math.abs(
|
|
5487
|
+
for (const v of raw) {
|
|
5488
|
+
acc += norm === "sum" ? Math.abs(v) : v * v;
|
|
4387
5489
|
}
|
|
4388
5490
|
const scale = norm === "sum" ? acc : Math.sqrt(acc);
|
|
4389
5491
|
const out = dest ?? raw;
|
|
4390
|
-
for (let
|
|
4391
|
-
out[
|
|
5492
|
+
for (let v = 0; v < raw.length; v++) {
|
|
5493
|
+
out[v] = scale > 0 ? raw[v] / scale : raw[v];
|
|
4392
5494
|
}
|
|
4393
5495
|
return out;
|
|
4394
5496
|
}
|
|
@@ -4433,76 +5535,1867 @@ async function eigenvectorCentrality(ctx, s, options) {
|
|
|
4433
5535
|
});
|
|
4434
5536
|
return scoresOf(run2, r.dest, "l2");
|
|
4435
5537
|
}
|
|
4436
|
-
async function katzCentrality(ctx, s, options) {
|
|
4437
|
-
const algorithm = "katzCentrality";
|
|
4438
|
-
const alpha = finite(options?.alpha, 0.1, "alpha");
|
|
4439
|
-
const beta = finite(options?.beta, 1, "beta");
|
|
4440
|
-
const r = resolve(ctx, s, options, algorithm);
|
|
4441
|
-
if (r.n === 0) {
|
|
4442
|
-
return empty(r.dest, r.maxIterations, options?.onProgress);
|
|
5538
|
+
async function katzCentrality(ctx, s, options) {
|
|
5539
|
+
const algorithm = "katzCentrality";
|
|
5540
|
+
const alpha = finite(options?.alpha, 0.1, "alpha");
|
|
5541
|
+
const beta = finite(options?.beta, 1, "beta");
|
|
5542
|
+
const r = resolve(ctx, s, options, algorithm);
|
|
5543
|
+
if (r.n === 0) {
|
|
5544
|
+
return empty(r.dest, r.maxIterations, options?.onProgress);
|
|
5545
|
+
}
|
|
5546
|
+
const run2 = await runPowerIteration(ctx, r.n, {
|
|
5547
|
+
normMode: 4,
|
|
5548
|
+
adjacency: reverseOf(ctx, s),
|
|
5549
|
+
alternate: null,
|
|
5550
|
+
alpha,
|
|
5551
|
+
beta,
|
|
5552
|
+
uniformP: 1,
|
|
5553
|
+
maxIterations: r.maxIterations,
|
|
5554
|
+
tolerance: r.tolerance,
|
|
5555
|
+
weights: r.weights,
|
|
5556
|
+
label: algorithm,
|
|
5557
|
+
signal: options?.signal,
|
|
5558
|
+
onProgress: options?.onProgress
|
|
5559
|
+
});
|
|
5560
|
+
return scoresOf(run2, r.dest, "l2");
|
|
5561
|
+
}
|
|
5562
|
+
async function hits(ctx, s, options) {
|
|
5563
|
+
const algorithm = "hits";
|
|
5564
|
+
const r = resolve(ctx, s, options, algorithm);
|
|
5565
|
+
const total = 2 * r.maxIterations;
|
|
5566
|
+
if (r.n === 0) {
|
|
5567
|
+
const { scores, iterations, converged, precision } = empty(r.dest, total, options?.onProgress);
|
|
5568
|
+
return { hubs: scores, authorities: new Float32Array(0), iterations, converged, precision };
|
|
5569
|
+
}
|
|
5570
|
+
const onProgress = options?.onProgress;
|
|
5571
|
+
const shared = {
|
|
5572
|
+
normMode: 1,
|
|
5573
|
+
alpha: 1,
|
|
5574
|
+
beta: 0,
|
|
5575
|
+
uniformP: 0,
|
|
5576
|
+
maxIterations: r.maxIterations,
|
|
5577
|
+
tolerance: r.tolerance,
|
|
5578
|
+
weights: r.weights,
|
|
5579
|
+
signal: options?.signal
|
|
5580
|
+
};
|
|
5581
|
+
const forward = coreOf(ctx, s, algorithm);
|
|
5582
|
+
const reverse = reverseOf(ctx, s);
|
|
5583
|
+
const hubSeeded = await runPowerIteration(ctx, r.n, {
|
|
5584
|
+
...shared,
|
|
5585
|
+
adjacency: reverse,
|
|
5586
|
+
alternate: forward,
|
|
5587
|
+
label: `${algorithm}/hub-seeded`,
|
|
5588
|
+
onProgress: onProgress === void 0 ? void 0 : (done) => {
|
|
5589
|
+
onProgress(done, total);
|
|
5590
|
+
}
|
|
5591
|
+
});
|
|
5592
|
+
const authoritySeeded = await runPowerIteration(ctx, r.n, {
|
|
5593
|
+
...shared,
|
|
5594
|
+
adjacency: forward,
|
|
5595
|
+
alternate: reverse,
|
|
5596
|
+
label: `${algorithm}/authority-seeded`,
|
|
5597
|
+
onProgress: onProgress === void 0 ? void 0 : (done) => {
|
|
5598
|
+
onProgress(r.maxIterations + done, total);
|
|
5599
|
+
}
|
|
5600
|
+
});
|
|
5601
|
+
return {
|
|
5602
|
+
hubs: normalised(latestOfKind(hubSeeded, authoritySeeded), r.dest, "sum"),
|
|
5603
|
+
authorities: normalised(latestOfKind(authoritySeeded, hubSeeded), null, "sum"),
|
|
5604
|
+
iterations: Math.max(hubSeeded.iterations, authoritySeeded.iterations),
|
|
5605
|
+
converged: hubSeeded.converged && authoritySeeded.converged,
|
|
5606
|
+
precision: "f32"
|
|
5607
|
+
};
|
|
5608
|
+
}
|
|
5609
|
+
async function prepareCompact(scope) {
|
|
5610
|
+
const scatter = await scope.pipelines.kernel(kernelSpec("compact-scatter"));
|
|
5611
|
+
const claim = await scope.pipelines.kernel(kernelSpec("dedupe-claim"));
|
|
5612
|
+
const filter = await scope.pipelines.kernel(kernelSpec("dedupe-filter"));
|
|
5613
|
+
const scan = await prepareScan(scope);
|
|
5614
|
+
return new CompactPlannerImpl(scope, scatter, claim, filter, scan);
|
|
5615
|
+
}
|
|
5616
|
+
function checkCount(what, count) {
|
|
5617
|
+
if (!Number.isSafeInteger(count) || count < 0 || count > U32_MAX$2) {
|
|
5618
|
+
throw new WebGpuGraphError("E_INVALID_ARGUMENT", `${what}: count must be a non-negative integer below 2^32`, {
|
|
5619
|
+
argument: "count",
|
|
5620
|
+
value: count
|
|
5621
|
+
});
|
|
5622
|
+
}
|
|
5623
|
+
}
|
|
5624
|
+
function checkWords$1(what, name, binding, words) {
|
|
5625
|
+
if (binding.size < 4 * words) {
|
|
5626
|
+
throw new WebGpuGraphError("E_INVALID_ARGUMENT", `${what}: ${name} is smaller than 4 x ${words} bytes`, {
|
|
5627
|
+
argument: name,
|
|
5628
|
+
value: binding.size,
|
|
5629
|
+
expected: 4 * words
|
|
5630
|
+
});
|
|
5631
|
+
}
|
|
5632
|
+
}
|
|
5633
|
+
function checkWordIndex(what, name, index, block) {
|
|
5634
|
+
if (!Number.isSafeInteger(index) || index < 0 || 4 * (index + 1) > block.size) {
|
|
5635
|
+
throw new WebGpuGraphError("E_INVALID_ARGUMENT", `${what}: ${name} ${index} is outside the block`, {
|
|
5636
|
+
argument: name,
|
|
5637
|
+
value: index,
|
|
5638
|
+
expected: `0 <= ${name} < ${Math.floor(block.size / 4)}`
|
|
5639
|
+
});
|
|
5640
|
+
}
|
|
5641
|
+
}
|
|
5642
|
+
function checkCompactArguments(r) {
|
|
5643
|
+
checkCount("compact", r.count);
|
|
5644
|
+
checkWords$1("compact", "queue", r.queue, r.count);
|
|
5645
|
+
checkWords$1("compact", "flags", r.flags, r.count);
|
|
5646
|
+
checkWords$1("compact", "out", r.out, r.count);
|
|
5647
|
+
checkWordIndex("compact", "outIndex", r.outIndex, r.outCount);
|
|
5648
|
+
}
|
|
5649
|
+
function checkDedupeArguments(r) {
|
|
5650
|
+
checkCount("dedupe", r.count);
|
|
5651
|
+
checkWords$1("dedupe", "queue", r.queue, r.count);
|
|
5652
|
+
checkWords$1("dedupe", "out", r.out, Math.min(r.count, Math.floor(r.owner.size / 4)));
|
|
5653
|
+
checkWordIndex("dedupe", "outIndex", r.outIndex, r.outCount);
|
|
5654
|
+
if (r.countIndex === U32_MAX$2) {
|
|
5655
|
+
return;
|
|
5656
|
+
}
|
|
5657
|
+
checkWordIndex("dedupe", "countIndex", r.countIndex, r.counters);
|
|
5658
|
+
if (r.countIndex === r.outIndex) {
|
|
5659
|
+
throw new WebGpuGraphError("E_INVALID_ARGUMENT", "dedupe: countIndex must not be the output word outIndex", {
|
|
5660
|
+
argument: "countIndex",
|
|
5661
|
+
value: r.countIndex
|
|
5662
|
+
});
|
|
5663
|
+
}
|
|
5664
|
+
if (r.counters.buffer !== r.outCount.buffer || r.counters.offset !== r.outCount.offset || r.counters.size !== r.outCount.size) {
|
|
5665
|
+
throw new WebGpuGraphError(
|
|
5666
|
+
"E_INVALID_ARGUMENT",
|
|
5667
|
+
"dedupe: counters must be the same range as outCount when countIndex names a device word",
|
|
5668
|
+
{
|
|
5669
|
+
argument: "counters",
|
|
5670
|
+
value: r.counters.size,
|
|
5671
|
+
expected: r.outCount.size
|
|
5672
|
+
}
|
|
5673
|
+
);
|
|
5674
|
+
}
|
|
5675
|
+
}
|
|
5676
|
+
class CompactPlannerImpl {
|
|
5677
|
+
/**
|
|
5678
|
+
* Wraps the resolved kernels; use prepareCompact().
|
|
5679
|
+
* @param scope - the caller's scope
|
|
5680
|
+
* @param scatter - the `compact-scatter` kernel
|
|
5681
|
+
* @param claim - the `dedupe-claim` kernel
|
|
5682
|
+
* @param filter - the `dedupe-filter` kernel
|
|
5683
|
+
* @param scan - the scan planner of the same scope
|
|
5684
|
+
*/
|
|
5685
|
+
constructor(scope, scatter, claim, filter, scan) {
|
|
5686
|
+
this.dispatches = 0;
|
|
5687
|
+
this.scope = scope;
|
|
5688
|
+
this.scatter = scatter;
|
|
5689
|
+
this.claim = claim;
|
|
5690
|
+
this.filter = filter;
|
|
5691
|
+
this.scan = scan;
|
|
5692
|
+
}
|
|
5693
|
+
/**
|
|
5694
|
+
* Dispatches the last record*() issued.
|
|
5695
|
+
* @returns the count
|
|
5696
|
+
*/
|
|
5697
|
+
get lastDispatches() {
|
|
5698
|
+
return this.dispatches;
|
|
5699
|
+
}
|
|
5700
|
+
/**
|
|
5701
|
+
* Records the scan and the scatter (see the interface).
|
|
5702
|
+
* @param pass - the compute pass
|
|
5703
|
+
* @param r - the record
|
|
5704
|
+
*/
|
|
5705
|
+
record(pass, r) {
|
|
5706
|
+
checkCompactArguments(r);
|
|
5707
|
+
if (r.count === 0) {
|
|
5708
|
+
this.dispatches = 0;
|
|
5709
|
+
return;
|
|
5710
|
+
}
|
|
5711
|
+
const size = 4 * r.count;
|
|
5712
|
+
const offsets = { buffer: this.scope.scratch(size, "compact/offsets"), offset: 0, size, window: null };
|
|
5713
|
+
this.scan.record(pass, r.flags, r.count, offsets);
|
|
5714
|
+
const params = this.scope.params(COMPACT_PARAMS, {
|
|
5715
|
+
count: r.count,
|
|
5716
|
+
outIndex: r.outIndex,
|
|
5717
|
+
countIndex: U32_MAX$2,
|
|
5718
|
+
stride: 0
|
|
5719
|
+
});
|
|
5720
|
+
const bound = this.scatter.bind({
|
|
5721
|
+
queue: r.queue,
|
|
5722
|
+
flags: r.flags,
|
|
5723
|
+
offsets,
|
|
5724
|
+
out: r.out,
|
|
5725
|
+
outCount: r.outCount,
|
|
5726
|
+
P: params.binding
|
|
5727
|
+
});
|
|
5728
|
+
this.scatter.dispatch(pass, bound, plan1d(r.count, this.scope.workgroupSize, this.scope.caps), [params.offset]);
|
|
5729
|
+
this.dispatches = this.scan.lastDispatches + 1;
|
|
5730
|
+
}
|
|
5731
|
+
/**
|
|
5732
|
+
* Records the claim and the filter over plan1d(count) (see the interface).
|
|
5733
|
+
* @param pass - the compute pass
|
|
5734
|
+
* @param r - the record
|
|
5735
|
+
*/
|
|
5736
|
+
recordDedupe(pass, r) {
|
|
5737
|
+
checkDedupeArguments(r);
|
|
5738
|
+
if (r.count === 0) {
|
|
5739
|
+
this.dispatches = 0;
|
|
5740
|
+
return;
|
|
5741
|
+
}
|
|
5742
|
+
const plan = planGridStride(r.count, this.scope.workgroupSize, this.scope.caps);
|
|
5743
|
+
const bound = this.bindDedupe(r, plan.stride ?? this.scope.workgroupSize);
|
|
5744
|
+
this.claim.dispatch(pass, bound.claim, plan, [bound.offset]);
|
|
5745
|
+
this.filter.dispatch(pass, bound.filter, plan, [bound.offset]);
|
|
5746
|
+
this.dispatches = 2;
|
|
5747
|
+
}
|
|
5748
|
+
/**
|
|
5749
|
+
* One params record (both kernels read the same values) and the two bind groups.
|
|
5750
|
+
* @param r - the record
|
|
5751
|
+
* @param stride - the grid-stride plan's stride (entries per pass over the grid)
|
|
5752
|
+
* @returns the bound kernels and the record's dynamic offset
|
|
5753
|
+
*/
|
|
5754
|
+
bindDedupe(r, stride) {
|
|
5755
|
+
const params = this.scope.params(COMPACT_PARAMS, {
|
|
5756
|
+
count: r.count,
|
|
5757
|
+
outIndex: r.outIndex,
|
|
5758
|
+
countIndex: r.countIndex,
|
|
5759
|
+
stride
|
|
5760
|
+
});
|
|
5761
|
+
return {
|
|
5762
|
+
claim: this.claim.bind({ queue: r.queue, owner: r.owner, counters: r.counters, P: params.binding }),
|
|
5763
|
+
filter: this.filter.bind({
|
|
5764
|
+
queue: r.queue,
|
|
5765
|
+
owner: r.owner,
|
|
5766
|
+
out: r.out,
|
|
5767
|
+
outCount: r.outCount,
|
|
5768
|
+
P: params.binding
|
|
5769
|
+
}),
|
|
5770
|
+
offset: params.offset
|
|
5771
|
+
};
|
|
5772
|
+
}
|
|
5773
|
+
}
|
|
5774
|
+
const W = Object.freeze({
|
|
5775
|
+
frontierCount: 0,
|
|
5776
|
+
nextFrontierCount: 1,
|
|
5777
|
+
frontierDegreeSum: 2,
|
|
5778
|
+
prevFrontierCount: 3,
|
|
5779
|
+
prevDegreeSum: 4,
|
|
5780
|
+
unvisitedCount: 5,
|
|
5781
|
+
unvisitedDegreeSum: 6,
|
|
5782
|
+
unvisitedListLen: 7,
|
|
5783
|
+
edgeCount: 8,
|
|
5784
|
+
edgeCountUnclamped: 9,
|
|
5785
|
+
overflowLevels: 10,
|
|
5786
|
+
level: 11,
|
|
5787
|
+
visitedCount: 12,
|
|
5788
|
+
switches: 13,
|
|
5789
|
+
direction: 14,
|
|
5790
|
+
done: 15,
|
|
5791
|
+
arcsScanned: 16,
|
|
5792
|
+
fusedLevels: 17,
|
|
5793
|
+
twoPhaseLevels: 18,
|
|
5794
|
+
bottomUpLevels: 19,
|
|
5795
|
+
farCount: 20,
|
|
5796
|
+
nextFarCount: 21,
|
|
5797
|
+
thresholdBits: 22,
|
|
5798
|
+
deltaBits: 23,
|
|
5799
|
+
path: 24
|
|
5800
|
+
});
|
|
5801
|
+
function definedWords(words) {
|
|
5802
|
+
const out = {};
|
|
5803
|
+
for (const [name, value] of Object.entries(words)) {
|
|
5804
|
+
if (value !== void 0) {
|
|
5805
|
+
out[name] = value;
|
|
5806
|
+
}
|
|
5807
|
+
}
|
|
5808
|
+
return out;
|
|
5809
|
+
}
|
|
5810
|
+
const MAX_ROLE = 3;
|
|
5811
|
+
function assertCount(argument, value) {
|
|
5812
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
5813
|
+
throw new WebGpuGraphError("E_INVALID_ARGUMENT", `frontier: ${argument} must be a non-negative integer`, {
|
|
5814
|
+
argument,
|
|
5815
|
+
value,
|
|
5816
|
+
expected: "a non-negative integer"
|
|
5817
|
+
});
|
|
5818
|
+
}
|
|
5819
|
+
}
|
|
5820
|
+
class Frontier {
|
|
5821
|
+
/**
|
|
5822
|
+
* Wraps the leased buffers; use prepareFrontier().
|
|
5823
|
+
* @param vertices - the two vertex queues
|
|
5824
|
+
* @param counters - the counters block
|
|
5825
|
+
* @param args - the args buffer
|
|
5826
|
+
* @param edgeQueue - the edge queue
|
|
5827
|
+
* @param edgeCapacity - the edge queue's entry count
|
|
5828
|
+
* @param n - the vertex count
|
|
5829
|
+
*/
|
|
5830
|
+
constructor(vertices, counters, args, edgeQueue, edgeCapacity, n) {
|
|
5831
|
+
this.sideIndex = 0;
|
|
5832
|
+
this.vertices = vertices;
|
|
5833
|
+
this.counters = counters;
|
|
5834
|
+
this.args = args;
|
|
5835
|
+
this.edgeQueue = edgeQueue;
|
|
5836
|
+
this.edgeCapacity = edgeCapacity;
|
|
5837
|
+
this.n = n;
|
|
5838
|
+
}
|
|
5839
|
+
/**
|
|
5840
|
+
* Which vertex queue is the input of the current level (a host-side index between two cached bind-group sets).
|
|
5841
|
+
* @returns 0 or 1
|
|
5842
|
+
*/
|
|
5843
|
+
get side() {
|
|
5844
|
+
return this.sideIndex;
|
|
5845
|
+
}
|
|
5846
|
+
/**
|
|
5847
|
+
* The vertex queue the current level expands from.
|
|
5848
|
+
* @returns `vertices[side]`
|
|
5849
|
+
*/
|
|
5850
|
+
get input() {
|
|
5851
|
+
return this.sideIndex === 0 ? this.vertices[0] : this.vertices[1];
|
|
5852
|
+
}
|
|
5853
|
+
/**
|
|
5854
|
+
* The vertex queue the current level's claims append to.
|
|
5855
|
+
* @returns `vertices[1 - side]`
|
|
5856
|
+
*/
|
|
5857
|
+
get output() {
|
|
5858
|
+
return this.sideIndex === 0 ? this.vertices[1] : this.vertices[0];
|
|
5859
|
+
}
|
|
5860
|
+
/** Flips the two vertex queues; the counts rotate inside the block, so nothing else moves. */
|
|
5861
|
+
swap() {
|
|
5862
|
+
this.sideIndex = this.sideIndex === 0 ? 1 : 0;
|
|
5863
|
+
}
|
|
5864
|
+
/**
|
|
5865
|
+
* Seeds a traversal: one `queue.writeBuffer` of the whole 96-byte block (zero except the caller's words) and one of
|
|
5866
|
+
* `vertices[0][0] = source`, both ordered before the submit that follows; the source is on side 0 afterwards.
|
|
5867
|
+
* `frontierCount` is not a word to seed: the first boundary rotates word 1 into it (the BFS seed is
|
|
5868
|
+
* `{ nextFrontierCount: 1, level: U32_MAX }`). A source outside `[0, n)`, an unknown word or a value that is not a
|
|
5869
|
+
* u32 is E_INVALID_ARGUMENT before anything is written.
|
|
5870
|
+
* @param queue - the device queue
|
|
5871
|
+
* @param source - the source vertex
|
|
5872
|
+
* @param seed - the words to seed
|
|
5873
|
+
*/
|
|
5874
|
+
reset(queue, source, seed) {
|
|
5875
|
+
if (!Number.isInteger(source) || source < 0 || source >= this.n) {
|
|
5876
|
+
throw new WebGpuGraphError("E_INVALID_ARGUMENT", `frontier: source ${source} is outside [0, ${this.n})`, {
|
|
5877
|
+
argument: "source",
|
|
5878
|
+
value: source,
|
|
5879
|
+
expected: `an integer in [0, ${this.n})`
|
|
5880
|
+
});
|
|
5881
|
+
}
|
|
5882
|
+
const bytes = new ArrayBuffer(FRONTIER_COUNTERS.byteLength);
|
|
5883
|
+
FRONTIER_COUNTERS.write(new DataView(bytes), definedWords(seed));
|
|
5884
|
+
this.sideIndex = 0;
|
|
5885
|
+
queue.writeBuffer(this.counters.buffer, this.counters.offset, bytes);
|
|
5886
|
+
queue.writeBuffer(this.vertices[0].buffer, this.vertices[0].offset, Uint32Array.of(source));
|
|
5887
|
+
}
|
|
5888
|
+
}
|
|
5889
|
+
async function prepareFrontier(scope, n, arcCount, edgeCapacity) {
|
|
5890
|
+
assertCount("n", n);
|
|
5891
|
+
assertCount("arcCount", arcCount);
|
|
5892
|
+
const capacity = edgeCapacity ?? Math.max(1, Math.min(arcCount, Math.floor(scope.caps.limits.maxStorageBufferBindingSize / 4)));
|
|
5893
|
+
if (!Number.isSafeInteger(capacity) || capacity < 1 || capacity > U32_MAX$2) {
|
|
5894
|
+
throw new WebGpuGraphError("E_INVALID_ARGUMENT", "frontier: edgeCapacity must be an integer >= 1", {
|
|
5895
|
+
argument: "edgeCapacity",
|
|
5896
|
+
value: capacity,
|
|
5897
|
+
expected: "an integer in [1, 2^32)"
|
|
5898
|
+
});
|
|
5899
|
+
}
|
|
5900
|
+
const kernel = await scope.pipelines.kernel(kernelSpec("frontier-finalize"));
|
|
5901
|
+
const queueBytes = 4 * Math.max(1, n);
|
|
5902
|
+
const argsBytes = MAX_LEVELS_PER_SUBMIT * FRONTIER_CANDIDATES * INDIRECT_ARGS_STRIDE;
|
|
5903
|
+
const vertices = [
|
|
5904
|
+
{ buffer: scope.scratch(queueBytes, "frontier/vertices-0"), offset: 0, size: queueBytes, window: null },
|
|
5905
|
+
{ buffer: scope.scratch(queueBytes, "frontier/vertices-1"), offset: 0, size: queueBytes, window: null }
|
|
5906
|
+
];
|
|
5907
|
+
const counters = {
|
|
5908
|
+
buffer: scope.scratch(FRONTIER_COUNTERS.byteLength, "frontier/counters"),
|
|
5909
|
+
offset: 0,
|
|
5910
|
+
size: FRONTIER_COUNTERS.byteLength,
|
|
5911
|
+
window: null
|
|
5912
|
+
};
|
|
5913
|
+
const args = {
|
|
5914
|
+
buffer: scope.indirect(argsBytes, "frontier/args"),
|
|
5915
|
+
offset: 0,
|
|
5916
|
+
size: argsBytes,
|
|
5917
|
+
window: null
|
|
5918
|
+
};
|
|
5919
|
+
const edgeQueue = {
|
|
5920
|
+
buffer: scope.scratch(4 * capacity, "frontier/edge-queue"),
|
|
5921
|
+
offset: 0,
|
|
5922
|
+
size: 4 * capacity,
|
|
5923
|
+
window: null
|
|
5924
|
+
};
|
|
5925
|
+
const frontier = new Frontier(vertices, counters, args, edgeQueue, capacity, n);
|
|
5926
|
+
return new FrontierPlannerImpl(scope, kernel, frontier);
|
|
5927
|
+
}
|
|
5928
|
+
class FrontierPlannerImpl {
|
|
5929
|
+
/**
|
|
5930
|
+
* Wraps the resolved kernel; use prepareFrontier().
|
|
5931
|
+
* @param scope - the caller's scope
|
|
5932
|
+
* @param kernel - the `frontier-finalize` kernel
|
|
5933
|
+
* @param frontier - the leased queue
|
|
5934
|
+
*/
|
|
5935
|
+
constructor(scope, kernel, frontier) {
|
|
5936
|
+
this.scope = scope;
|
|
5937
|
+
this.kernel = kernel;
|
|
5938
|
+
this.frontier = frontier;
|
|
5939
|
+
}
|
|
5940
|
+
/**
|
|
5941
|
+
* Records one selector dispatch (see the interface).
|
|
5942
|
+
* @param pass - the compute pass
|
|
5943
|
+
* @param role - the role
|
|
5944
|
+
* @param level - the level inside the submit
|
|
5945
|
+
* @param fields - the caller's fields
|
|
5946
|
+
*/
|
|
5947
|
+
recordFinalize(pass, role, level, fields) {
|
|
5948
|
+
if (!Number.isInteger(level) || level < 0 || level >= MAX_LEVELS_PER_SUBMIT) {
|
|
5949
|
+
throw new WebGpuGraphError("E_INVALID_ARGUMENT", `frontier: level ${level} is outside the submit`, {
|
|
5950
|
+
argument: "level",
|
|
5951
|
+
value: level,
|
|
5952
|
+
expected: `an integer in [0, ${MAX_LEVELS_PER_SUBMIT})`
|
|
5953
|
+
});
|
|
5954
|
+
}
|
|
5955
|
+
if (!Number.isInteger(role) || role < 0 || role > MAX_ROLE) {
|
|
5956
|
+
throw new WebGpuGraphError("E_INVALID_ARGUMENT", `frontier: role ${role} is not a finalize role`, {
|
|
5957
|
+
argument: "role",
|
|
5958
|
+
value: role,
|
|
5959
|
+
expected: `an integer in [0, ${MAX_ROLE}]`
|
|
5960
|
+
});
|
|
5961
|
+
}
|
|
5962
|
+
const { scope, frontier } = this;
|
|
5963
|
+
const params = scope.params(FRONTIER_PARAMS, {
|
|
5964
|
+
...definedWords(fields),
|
|
5965
|
+
role,
|
|
5966
|
+
slotBase: level * FRONTIER_CANDIDATES,
|
|
5967
|
+
wg: scope.workgroupSize,
|
|
5968
|
+
edgeCapacity: frontier.edgeCapacity,
|
|
5969
|
+
n: frontier.n
|
|
5970
|
+
});
|
|
5971
|
+
const bound = this.kernel.bind({ counters: frontier.counters, args: frontier.args, P: params.binding });
|
|
5972
|
+
this.kernel.dispatch(pass, bound, plan1d(1, scope.workgroupSize, scope.caps), [params.offset]);
|
|
5973
|
+
}
|
|
5974
|
+
}
|
|
5975
|
+
async function prepareAdvance(scope, core) {
|
|
5976
|
+
const kernel = await scope.pipelines.kernel(kernelSpec("advance-expand", graphOverrides(core, null)));
|
|
5977
|
+
return new AdvancePlannerImpl(scope, core, kernel);
|
|
5978
|
+
}
|
|
5979
|
+
class AdvancePlannerImpl {
|
|
5980
|
+
/**
|
|
5981
|
+
* Wraps the resolved kernel; use prepareAdvance().
|
|
5982
|
+
* @param scope - the caller's scope
|
|
5983
|
+
* @param core - the core the kernel was compiled for
|
|
5984
|
+
* @param kernel - the `advance-expand` kernel
|
|
5985
|
+
*/
|
|
5986
|
+
constructor(scope, core, kernel) {
|
|
5987
|
+
this.scope = scope;
|
|
5988
|
+
this.kernel = kernel;
|
|
5989
|
+
this.windows = coreWindows(core);
|
|
5990
|
+
this.n = rowCountOf(core, "advance");
|
|
5991
|
+
}
|
|
5992
|
+
/**
|
|
5993
|
+
* Records one direct expansion per window (see the interface).
|
|
5994
|
+
* @param pass - the compute pass
|
|
5995
|
+
* @param frontier - the queue to expand
|
|
5996
|
+
*/
|
|
5997
|
+
record(pass, frontier) {
|
|
5998
|
+
if (frontier.n !== this.n) {
|
|
5999
|
+
throw new WebGpuGraphError(
|
|
6000
|
+
"E_INVALID_ARGUMENT",
|
|
6001
|
+
`advance: the frontier holds ${frontier.n} vertices, the core ${this.n} rows`,
|
|
6002
|
+
{
|
|
6003
|
+
argument: "frontier",
|
|
6004
|
+
value: frontier.n,
|
|
6005
|
+
expected: this.n
|
|
6006
|
+
}
|
|
6007
|
+
);
|
|
6008
|
+
}
|
|
6009
|
+
const { scope } = this;
|
|
6010
|
+
const plan = planGridStride(frontier.n, scope.workgroupSize, scope.caps);
|
|
6011
|
+
for (const w of this.windows) {
|
|
6012
|
+
const params = scope.params(FRONTIER_PARAMS, {
|
|
6013
|
+
wg: scope.workgroupSize,
|
|
6014
|
+
edgeCapacity: frontier.edgeCapacity,
|
|
6015
|
+
n: frontier.n,
|
|
6016
|
+
arcBase: w.arcBase,
|
|
6017
|
+
arcEnd: w.arcEnd,
|
|
6018
|
+
stride: plan.stride ?? scope.workgroupSize
|
|
6019
|
+
});
|
|
6020
|
+
const bound = this.kernel.bind({
|
|
6021
|
+
...graphBindings(w.core, null),
|
|
6022
|
+
frontierIn: frontier.input,
|
|
6023
|
+
counters: frontier.counters,
|
|
6024
|
+
edgeQueue: frontier.edgeQueue,
|
|
6025
|
+
P: params.binding
|
|
6026
|
+
});
|
|
6027
|
+
this.kernel.dispatch(pass, bound, plan, [params.offset]);
|
|
6028
|
+
}
|
|
6029
|
+
}
|
|
6030
|
+
}
|
|
6031
|
+
const RADIX_DIGIT_BITS = 8;
|
|
6032
|
+
function radixHistBytes(count, wg) {
|
|
6033
|
+
return 4 * RADIX_BINS * Math.ceil(count / wg);
|
|
6034
|
+
}
|
|
6035
|
+
async function prepareRadixSort(scope) {
|
|
6036
|
+
const hist = await scope.pipelines.kernel(kernelSpec("radix-hist"));
|
|
6037
|
+
const scatter = await scope.pipelines.kernel(kernelSpec("radix-scatter"));
|
|
6038
|
+
const scan = await prepareScan(scope);
|
|
6039
|
+
return new RadixSortPlannerImpl(scope, hist, scatter, scan);
|
|
6040
|
+
}
|
|
6041
|
+
function checkBinding(argument, binding, bytes) {
|
|
6042
|
+
if (binding.size < bytes) {
|
|
6043
|
+
throw new WebGpuGraphError("E_INVALID_ARGUMENT", `radixSort: ${argument} is smaller than ${bytes} bytes`, {
|
|
6044
|
+
argument,
|
|
6045
|
+
value: binding.size,
|
|
6046
|
+
expected: bytes
|
|
6047
|
+
});
|
|
6048
|
+
}
|
|
6049
|
+
}
|
|
6050
|
+
function checkRecordArguments(keys, vals, count, bits, scratch, wg) {
|
|
6051
|
+
if (bits !== 8 && bits !== 16 && bits !== 24 && bits !== 32) {
|
|
6052
|
+
throw new WebGpuGraphError("E_INVALID_ARGUMENT", "radixSort: bits must be 8, 16, 24 or 32", {
|
|
6053
|
+
argument: "bits",
|
|
6054
|
+
value: bits,
|
|
6055
|
+
expected: [8, 16, 24, 32]
|
|
6056
|
+
});
|
|
6057
|
+
}
|
|
6058
|
+
if (!Number.isSafeInteger(count) || count < 0 || count > U32_MAX$2) {
|
|
6059
|
+
throw new WebGpuGraphError("E_INVALID_ARGUMENT", "radixSort: count must be a non-negative integer below 2^32", {
|
|
6060
|
+
argument: "count",
|
|
6061
|
+
value: count
|
|
6062
|
+
});
|
|
6063
|
+
}
|
|
6064
|
+
const pairBytes = 4 * count;
|
|
6065
|
+
checkBinding("keys", keys, pairBytes);
|
|
6066
|
+
checkBinding("vals", vals, pairBytes);
|
|
6067
|
+
checkBinding("scratch.keys", scratch.keys, pairBytes);
|
|
6068
|
+
checkBinding("scratch.vals", scratch.vals, pairBytes);
|
|
6069
|
+
const tableBytes = radixHistBytes(count, wg);
|
|
6070
|
+
checkBinding("scratch.hist", scratch.hist, tableBytes);
|
|
6071
|
+
checkBinding("scratch.offsets", scratch.offsets, tableBytes);
|
|
6072
|
+
}
|
|
6073
|
+
class RadixSortPlannerImpl {
|
|
6074
|
+
/**
|
|
6075
|
+
* Wraps the resolved kernels; use prepareRadixSort().
|
|
6076
|
+
* @param scope - the caller's scope
|
|
6077
|
+
* @param hist - the `radix-hist` kernel
|
|
6078
|
+
* @param scatter - the `radix-scatter` kernel
|
|
6079
|
+
* @param scan - the scan planner of the same scope
|
|
6080
|
+
*/
|
|
6081
|
+
constructor(scope, hist, scatter, scan) {
|
|
6082
|
+
this.dispatches = 0;
|
|
6083
|
+
this.scope = scope;
|
|
6084
|
+
this.hist = hist;
|
|
6085
|
+
this.scatter = scatter;
|
|
6086
|
+
this.scan = scan;
|
|
6087
|
+
}
|
|
6088
|
+
/**
|
|
6089
|
+
* Dispatches the last record() issued.
|
|
6090
|
+
* @returns the count
|
|
6091
|
+
*/
|
|
6092
|
+
get lastDispatches() {
|
|
6093
|
+
return this.dispatches;
|
|
6094
|
+
}
|
|
6095
|
+
/**
|
|
6096
|
+
* Records the passes into the pass (see the interface).
|
|
6097
|
+
* @param pass - the compute pass
|
|
6098
|
+
* @param keys - the keys
|
|
6099
|
+
* @param vals - the values
|
|
6100
|
+
* @param count - the pair count
|
|
6101
|
+
* @param bits - the key width
|
|
6102
|
+
* @param scratch - the second pair, the histogram table and its scanned twin
|
|
6103
|
+
* @returns the pair the result lives in
|
|
6104
|
+
*/
|
|
6105
|
+
record(pass, keys, vals, count, bits, scratch) {
|
|
6106
|
+
const wg = this.scope.workgroupSize;
|
|
6107
|
+
checkRecordArguments(keys, vals, count, bits, scratch, wg);
|
|
6108
|
+
if (count === 0) {
|
|
6109
|
+
this.dispatches = 0;
|
|
6110
|
+
return { keys, vals };
|
|
6111
|
+
}
|
|
6112
|
+
const groups = Math.ceil(count / wg);
|
|
6113
|
+
const plan = plan1d(count, wg, this.scope.caps);
|
|
6114
|
+
const tableWords = RADIX_BINS * groups;
|
|
6115
|
+
const tableBytes = 4 * tableWords;
|
|
6116
|
+
const histTable = { ...scratch.hist, size: tableBytes };
|
|
6117
|
+
const offsets = { ...scratch.offsets, size: tableBytes };
|
|
6118
|
+
let src = { keys, vals };
|
|
6119
|
+
let dst = { keys: scratch.keys, vals: scratch.vals };
|
|
6120
|
+
let dispatches = 0;
|
|
6121
|
+
const passes = bits / RADIX_DIGIT_BITS;
|
|
6122
|
+
for (let p = 0; p < passes; p++) {
|
|
6123
|
+
const params = this.scope.params(RADIX_PARAMS, { count, shift: RADIX_DIGIT_BITS * p, groups, pad0: 0 });
|
|
6124
|
+
const histBound = this.hist.bind({ keys: src.keys, hist: histTable, P: params.binding });
|
|
6125
|
+
this.hist.dispatch(pass, histBound, plan, [params.offset]);
|
|
6126
|
+
this.scan.record(pass, histTable, tableWords, offsets);
|
|
6127
|
+
const scatterBound = this.scatter.bind({
|
|
6128
|
+
keys: src.keys,
|
|
6129
|
+
vals: src.vals,
|
|
6130
|
+
offsets,
|
|
6131
|
+
keysOut: dst.keys,
|
|
6132
|
+
valsOut: dst.vals,
|
|
6133
|
+
P: params.binding
|
|
6134
|
+
});
|
|
6135
|
+
this.scatter.dispatch(pass, scatterBound, plan, [params.offset]);
|
|
6136
|
+
dispatches += 2 + this.scan.lastDispatches;
|
|
6137
|
+
[src, dst] = [dst, src];
|
|
6138
|
+
}
|
|
6139
|
+
this.dispatches = dispatches;
|
|
6140
|
+
return src;
|
|
6141
|
+
}
|
|
6142
|
+
}
|
|
6143
|
+
const ALGORITHM$3 = "breadthFirstSearch";
|
|
6144
|
+
function bfsRingSlots(windows, levelsPerSubmit) {
|
|
6145
|
+
return Math.max((5 + 4 * windows) * levelsPerSubmit + 16, RESULT_BATCH_SLOTS + windows);
|
|
6146
|
+
}
|
|
6147
|
+
const RESULT_BATCH_SLOTS = 2 + 4 * 8;
|
|
6148
|
+
function normaliseMaxDepth(maxDepth) {
|
|
6149
|
+
if (maxDepth === void 0 || Number.isNaN(maxDepth) || maxDepth >= U32_MAX$2) {
|
|
6150
|
+
return U32_MAX$2;
|
|
6151
|
+
}
|
|
6152
|
+
return Math.max(0, Math.ceil(maxDepth));
|
|
6153
|
+
}
|
|
6154
|
+
function checkDest$1(dest, n) {
|
|
6155
|
+
if (dest === void 0) {
|
|
6156
|
+
return null;
|
|
6157
|
+
}
|
|
6158
|
+
if (dest instanceof Uint32Array && dest.length === n && dest.buffer instanceof ArrayBuffer) {
|
|
6159
|
+
return dest;
|
|
6160
|
+
}
|
|
6161
|
+
throw new WebGpuGraphError(
|
|
6162
|
+
"E_INVALID_ARGUMENT",
|
|
6163
|
+
`${ALGORITHM$3}: dest must be a Uint32Array of length ${n} over an ArrayBuffer`,
|
|
6164
|
+
{
|
|
6165
|
+
argument: "dest",
|
|
6166
|
+
value: `${dest.constructor.name}(${dest.length})`,
|
|
6167
|
+
expected: `Uint32Array(${n}) over an ArrayBuffer`
|
|
6168
|
+
}
|
|
6169
|
+
);
|
|
6170
|
+
}
|
|
6171
|
+
function bindingOf$1(buffer, size) {
|
|
6172
|
+
return { buffer, offset: 0, size, window: null };
|
|
6173
|
+
}
|
|
6174
|
+
function degreeView(ctx, s, name) {
|
|
6175
|
+
const { bindings } = ctx.residency.view(s, name);
|
|
6176
|
+
const { [name]: binding } = bindings;
|
|
6177
|
+
if (binding === void 0) {
|
|
6178
|
+
throw new WebGpuGraphError("E_VALIDATION", `${ALGORITHM$3}: the ${name} view has no ${name} binding`, {
|
|
6179
|
+
label: `${ALGORITHM$3}/${name}`,
|
|
6180
|
+
message: `the ${name} view has no ${name} binding`
|
|
6181
|
+
});
|
|
6182
|
+
}
|
|
6183
|
+
return binding;
|
|
6184
|
+
}
|
|
6185
|
+
function aborted$1(batchId) {
|
|
6186
|
+
return new WebGpuGraphError(
|
|
6187
|
+
"E_ABORTED",
|
|
6188
|
+
`${ALGORITHM$3}: the signal was aborted`,
|
|
6189
|
+
batchId === void 0 ? {} : { batchId }
|
|
6190
|
+
);
|
|
6191
|
+
}
|
|
6192
|
+
function wordOf$1(block, name) {
|
|
6193
|
+
const value = block[name];
|
|
6194
|
+
if (typeof value !== "number") {
|
|
6195
|
+
throw new WebGpuGraphError("E_VALIDATION", `${ALGORITHM$3}: counters.${name} did not decode to a number`, {
|
|
6196
|
+
label: `${ALGORITHM$3}/counters`,
|
|
6197
|
+
message: `the field ${name} did not decode to a number`
|
|
6198
|
+
});
|
|
6199
|
+
}
|
|
6200
|
+
return value;
|
|
6201
|
+
}
|
|
6202
|
+
async function bfsWithTuning(ctx, s, source, options, tuning) {
|
|
6203
|
+
ctx.assertReady();
|
|
6204
|
+
await assertDeviceComputes(ctx);
|
|
6205
|
+
const n = s.nodeCount;
|
|
6206
|
+
if (!Number.isInteger(source) || source < 0 || source >= n) {
|
|
6207
|
+
throw new WebGpuGraphError("E_INVALID_ARGUMENT", `${ALGORITHM$3}: source ${source} is outside [0, ${n})`, {
|
|
6208
|
+
argument: "source",
|
|
6209
|
+
value: source,
|
|
6210
|
+
expected: `an integer in [0, ${n})`
|
|
6211
|
+
});
|
|
6212
|
+
}
|
|
6213
|
+
const levelsPerSubmit = tuning.levelsPerSubmit ?? MAX_LEVELS_PER_SUBMIT;
|
|
6214
|
+
if (!Number.isInteger(levelsPerSubmit) || levelsPerSubmit < 1 || levelsPerSubmit > MAX_LEVELS_PER_SUBMIT) {
|
|
6215
|
+
throw new WebGpuGraphError(
|
|
6216
|
+
"E_INVALID_ARGUMENT",
|
|
6217
|
+
`${ALGORITHM$3}: levelsPerSubmit must be an integer in [1, ${MAX_LEVELS_PER_SUBMIT}]`,
|
|
6218
|
+
{
|
|
6219
|
+
argument: "levelsPerSubmit",
|
|
6220
|
+
value: levelsPerSubmit,
|
|
6221
|
+
expected: `an integer in [1, ${MAX_LEVELS_PER_SUBMIT}]`
|
|
6222
|
+
}
|
|
6223
|
+
);
|
|
6224
|
+
}
|
|
6225
|
+
const dest = checkDest$1(options?.dest, n);
|
|
6226
|
+
const maxDepth = normaliseMaxDepth(options?.maxDepth);
|
|
6227
|
+
const predKind = tuning.predKind ?? 1;
|
|
6228
|
+
if (options?.signal?.aborted) {
|
|
6229
|
+
throw aborted$1();
|
|
6230
|
+
}
|
|
6231
|
+
const core = ctx.residency.core(s);
|
|
6232
|
+
const forward = coreWindows(core);
|
|
6233
|
+
if (s.directed && 4 * s.arcCount > ctx.caps.limits.maxStorageBufferBindingSize) {
|
|
6234
|
+
throw new WebGpuGraphError(
|
|
6235
|
+
"E_TOO_LARGE",
|
|
6236
|
+
`${ALGORITHM$3}: the reverse adjacency of a directed snapshot (${4 * s.arcCount} bytes) needs arc windows, which no view executes (spec 4.3); the bottom-up sweep binds it whole`,
|
|
6237
|
+
{
|
|
6238
|
+
needed: 4 * s.arcCount,
|
|
6239
|
+
limit: ctx.caps.limits.maxStorageBufferBindingSize,
|
|
6240
|
+
path: "windowed",
|
|
6241
|
+
algorithm: ALGORITHM$3
|
|
6242
|
+
}
|
|
6243
|
+
);
|
|
6244
|
+
}
|
|
6245
|
+
const reverse = s.directed ? coreOfView(ctx.residency.view(s, "reverse"), s.arcCount) : core;
|
|
6246
|
+
const backward = coreWindows(reverse);
|
|
6247
|
+
const outDegree = degreeView(ctx, s, "outDegree");
|
|
6248
|
+
const inDegree = degreeView(ctx, s, "inDegree");
|
|
6249
|
+
const scope = algorithmScope(ctx, ALGORITHM$3, bfsRingSlots(forward.length, levelsPerSubmit));
|
|
6250
|
+
tuning.onScope?.(scope);
|
|
6251
|
+
try {
|
|
6252
|
+
const bytes = 4 * n;
|
|
6253
|
+
const wg = ctx.workgroupSize;
|
|
6254
|
+
const depth = bindingOf$1(scope.scratch(bytes, "depth"), bytes);
|
|
6255
|
+
const bitsBase = Math.ceil(n / 64) * 64;
|
|
6256
|
+
const bitsWords = Math.ceil(n / 32);
|
|
6257
|
+
const sweepBytes = 4 * (bitsBase + bitsWords);
|
|
6258
|
+
const sweepIn = bindingOf$1(scope.scratch(sweepBytes, "sweep-in"), sweepBytes);
|
|
6259
|
+
const unvisitedList = { buffer: sweepIn.buffer, offset: 0, size: bytes, window: null };
|
|
6260
|
+
const frontierBits = {
|
|
6261
|
+
buffer: sweepIn.buffer,
|
|
6262
|
+
offset: 4 * bitsBase,
|
|
6263
|
+
size: 4 * bitsWords,
|
|
6264
|
+
window: null
|
|
6265
|
+
};
|
|
6266
|
+
const flags = bindingOf$1(scope.scratch(bytes, "unvisited-flags"), bytes);
|
|
6267
|
+
const iota = bindingOf$1(scope.scratch(bytes, "iota"), bytes);
|
|
6268
|
+
const compactCount = bindingOf$1(scope.scratch(4, "compact-count"), 4);
|
|
6269
|
+
await ctx.allocator.check();
|
|
6270
|
+
const planner = await prepareFrontier(scope, n, s.arcCount, tuning.edgeCapacity);
|
|
6271
|
+
const advance = await prepareAdvance(scope, core);
|
|
6272
|
+
const compact = await prepareCompact(scope);
|
|
6273
|
+
const contract = await ctx.pipelines.kernel(kernelSpec("bfs-contract"));
|
|
6274
|
+
const fused = await ctx.pipelines.kernel(kernelSpec("bfs-fused", graphOverrides(core, null)));
|
|
6275
|
+
const bitset = await ctx.pipelines.kernel(kernelSpec("bfs-bitset-build"));
|
|
6276
|
+
const bottomUp = await ctx.pipelines.kernel(kernelSpec("bfs-bottom-up", graphOverrides(reverse, null)));
|
|
6277
|
+
const unvisited = await ctx.pipelines.kernel(kernelSpec("bfs-unvisited-flags"));
|
|
6278
|
+
const pred = await ctx.pipelines.kernel(kernelSpec("sssp-pred", { ...graphOverrides(core, null), MODE: 1 }));
|
|
6279
|
+
const fill = await ctx.pipelines.kernel(kernelSpec("fill"));
|
|
6280
|
+
const sort = await prepareRadixSort(scope);
|
|
6281
|
+
const { frontier } = planner;
|
|
6282
|
+
const { counters } = frontier;
|
|
6283
|
+
const { queue } = ctx.device;
|
|
6284
|
+
const fillPlan = plan1d(n, wg, ctx.caps);
|
|
6285
|
+
const levelPlan = planGridStride(Math.max(n, frontier.edgeCapacity), wg, ctx.caps);
|
|
6286
|
+
const sweepPlan = planGridStride(n, wg, ctx.caps);
|
|
6287
|
+
const fusedPlan = planGridStride(n * wg, wg, ctx.caps);
|
|
6288
|
+
const bitsPlan = plan1d(bitsWords, wg, ctx.caps);
|
|
6289
|
+
const recordFill = (pass2, dst, value, mode) => {
|
|
6290
|
+
const params = scope.params(FILL_PARAMS, { count: n, value, mode, pad0: 0 });
|
|
6291
|
+
fill.dispatch(pass2, fill.bind({ dst, P: params.binding }), fillPlan, [params.offset]);
|
|
6292
|
+
};
|
|
6293
|
+
const flagsPlan = planGridStride(n, wg, ctx.caps);
|
|
6294
|
+
const recordRebuild = (pass2) => {
|
|
6295
|
+
const params = scope.params(FRONTIER_PARAMS, { wg, n, stride: flagsPlan.stride ?? n });
|
|
6296
|
+
const bound = unvisited.bind({ outDegree, inDegree, depth, flags, counters, P: params.binding });
|
|
6297
|
+
unvisited.dispatch(pass2, bound, flagsPlan, [params.offset]);
|
|
6298
|
+
compact.record(pass2, {
|
|
6299
|
+
queue: iota,
|
|
6300
|
+
flags,
|
|
6301
|
+
count: n,
|
|
6302
|
+
out: unvisitedList,
|
|
6303
|
+
outCount: compactCount,
|
|
6304
|
+
outIndex: 0
|
|
6305
|
+
});
|
|
6306
|
+
};
|
|
6307
|
+
const submit = (batch) => {
|
|
6308
|
+
scope.flush();
|
|
6309
|
+
return batch.submit();
|
|
6310
|
+
};
|
|
6311
|
+
const setup = new CommandBatch(ctx, `${ALGORITHM$3}/setup`);
|
|
6312
|
+
const setupPass = setup.pass("fill");
|
|
6313
|
+
recordFill(setupPass, depth, INVALID_INDEX, 0);
|
|
6314
|
+
recordFill(setupPass, iota, 0, 1);
|
|
6315
|
+
setup.endPass();
|
|
6316
|
+
await submit(setup).readback;
|
|
6317
|
+
ctx.assertReady();
|
|
6318
|
+
queue.writeBuffer(depth.buffer, depth.offset + 4 * source, Uint32Array.of(0));
|
|
6319
|
+
frontier.reset(queue, source, { nextFrontierCount: 1, level: U32_MAX$2 });
|
|
6320
|
+
const fields = {
|
|
6321
|
+
mode: tuning.direction === "top-down" ? 1 : 0,
|
|
6322
|
+
alpha: tuning.alpha ?? Math.max(1, Math.floor(s.arcCount / n)),
|
|
6323
|
+
beta: tuning.beta ?? BEAMER_BETA,
|
|
6324
|
+
fusedMax: tuning.fusedMax ?? FUSED_FRONTIER_MAX,
|
|
6325
|
+
maxDepth
|
|
6326
|
+
};
|
|
6327
|
+
let levelsRecorded = 0;
|
|
6328
|
+
let submits = 0;
|
|
6329
|
+
for (; ; ) {
|
|
6330
|
+
queue.writeBuffer(counters.buffer, counters.offset + 4 * W.unvisitedCount, new Uint32Array(3));
|
|
6331
|
+
const batch = new CommandBatch(ctx, `${ALGORITHM$3}/levels`);
|
|
6332
|
+
const pass2 = batch.pass("bfs");
|
|
6333
|
+
recordRebuild(pass2);
|
|
6334
|
+
const bitsParams = scope.params(FILL_PARAMS, { count: bitsWords, value: 0, mode: 0, pad0: 0 });
|
|
6335
|
+
const boundBitsFill = fill.bind({ dst: frontierBits, P: bitsParams.binding });
|
|
6336
|
+
for (let level2 = 0; level2 < levelsPerSubmit; level2++) {
|
|
6337
|
+
planner.recordFinalize(pass2, 0, level2, { ...fields, firstOfSubmit: Math.min(level2, 2) });
|
|
6338
|
+
advance.record(pass2, frontier);
|
|
6339
|
+
planner.recordFinalize(pass2, 1, level2, fields);
|
|
6340
|
+
const params = scope.params(FRONTIER_PARAMS, {
|
|
6341
|
+
wg,
|
|
6342
|
+
n,
|
|
6343
|
+
edgeCapacity: frontier.edgeCapacity,
|
|
6344
|
+
arcBase: 0,
|
|
6345
|
+
arcEnd: s.arcCount,
|
|
6346
|
+
bitsBase,
|
|
6347
|
+
stride: levelPlan.stride ?? wg
|
|
6348
|
+
});
|
|
6349
|
+
const boundContract = contract.bind({
|
|
6350
|
+
edgeQueue: frontier.edgeQueue,
|
|
6351
|
+
counters,
|
|
6352
|
+
depth,
|
|
6353
|
+
frontierOut: frontier.output,
|
|
6354
|
+
P: params.binding
|
|
6355
|
+
});
|
|
6356
|
+
contract.dispatch(pass2, boundContract, levelPlan, [params.offset]);
|
|
6357
|
+
for (const w of forward) {
|
|
6358
|
+
const fusedParams = scope.params(FRONTIER_PARAMS, {
|
|
6359
|
+
wg,
|
|
6360
|
+
n,
|
|
6361
|
+
edgeCapacity: frontier.edgeCapacity,
|
|
6362
|
+
arcBase: w.arcBase,
|
|
6363
|
+
arcEnd: w.arcEnd,
|
|
6364
|
+
stride: fusedPlan.x * fusedPlan.y
|
|
6365
|
+
});
|
|
6366
|
+
const boundFused = fused.bind({
|
|
6367
|
+
...graphBindings(w.core, null),
|
|
6368
|
+
frontierIn: frontier.input,
|
|
6369
|
+
counters,
|
|
6370
|
+
depth,
|
|
6371
|
+
frontierOut: frontier.output,
|
|
6372
|
+
P: fusedParams.binding
|
|
6373
|
+
});
|
|
6374
|
+
fused.dispatch(pass2, boundFused, fusedPlan, [fusedParams.offset]);
|
|
6375
|
+
}
|
|
6376
|
+
fill.dispatch(pass2, boundBitsFill, bitsPlan, [bitsParams.offset]);
|
|
6377
|
+
const boundBitset = bitset.bind({
|
|
6378
|
+
frontierIn: frontier.input,
|
|
6379
|
+
counters,
|
|
6380
|
+
bits: sweepIn,
|
|
6381
|
+
P: params.binding
|
|
6382
|
+
});
|
|
6383
|
+
bitset.dispatch(pass2, boundBitset, levelPlan, [params.offset]);
|
|
6384
|
+
for (const w of backward) {
|
|
6385
|
+
const sweepParams = scope.params(FRONTIER_PARAMS, {
|
|
6386
|
+
wg,
|
|
6387
|
+
n,
|
|
6388
|
+
arcBase: w.arcBase,
|
|
6389
|
+
arcEnd: w.arcEnd,
|
|
6390
|
+
bitsBase,
|
|
6391
|
+
stride: sweepPlan.stride ?? wg
|
|
6392
|
+
});
|
|
6393
|
+
const boundSweep = bottomUp.bind({
|
|
6394
|
+
...graphBindings(w.core, null),
|
|
6395
|
+
sweepIn,
|
|
6396
|
+
counters,
|
|
6397
|
+
depth,
|
|
6398
|
+
frontierOut: frontier.output,
|
|
6399
|
+
P: sweepParams.binding
|
|
6400
|
+
});
|
|
6401
|
+
bottomUp.dispatch(pass2, boundSweep, sweepPlan, [sweepParams.offset]);
|
|
6402
|
+
}
|
|
6403
|
+
frontier.swap();
|
|
6404
|
+
}
|
|
6405
|
+
batch.endPass();
|
|
6406
|
+
const doneRequest = batch.readback(counters.buffer, counters.offset + 4 * W.done, 4);
|
|
6407
|
+
const inspect = tuning.onLevel === void 0 ? null : {
|
|
6408
|
+
block: batch.readback(counters.buffer, counters.offset, FRONTIER_COUNTERS.byteLength),
|
|
6409
|
+
frontier: batch.readback(frontier.input.buffer, frontier.input.offset, frontier.input.size),
|
|
6410
|
+
count: batch.readback(compactCount.buffer, compactCount.offset, 4)
|
|
6411
|
+
};
|
|
6412
|
+
const submitted = submit(batch);
|
|
6413
|
+
const back2 = await submitted.readback;
|
|
6414
|
+
levelsRecorded += levelsPerSubmit;
|
|
6415
|
+
submits += 1;
|
|
6416
|
+
ctx.assertReady();
|
|
6417
|
+
if (options?.signal?.aborted) {
|
|
6418
|
+
throw aborted$1(submitted.id);
|
|
6419
|
+
}
|
|
6420
|
+
options?.onProgress?.(Math.min(levelsRecorded, n), n);
|
|
6421
|
+
if (inspect !== null && tuning.onLevel !== void 0) {
|
|
6422
|
+
const block2 = FRONTIER_COUNTERS.read(new DataView(back2), inspect.block.offset);
|
|
6423
|
+
const claimed = new Uint32Array(back2, inspect.frontier.offset, wordOf$1(block2, "nextFrontierCount"));
|
|
6424
|
+
const rebuilt = new Uint32Array(back2, inspect.count.offset, 1)[0];
|
|
6425
|
+
tuning.onLevel(levelsRecorded - 1, block2, claimed.slice(), rebuilt);
|
|
6426
|
+
}
|
|
6427
|
+
if (new Uint32Array(back2, doneRequest.offset, 1)[0] !== 0) {
|
|
6428
|
+
break;
|
|
6429
|
+
}
|
|
6430
|
+
if (submits > n + 1) {
|
|
6431
|
+
throw new WebGpuGraphError(
|
|
6432
|
+
"E_VALIDATION",
|
|
6433
|
+
`${ALGORITHM$3}: the done flag never rose in ${submits} submits (a traversal has at most ${n} levels)`,
|
|
6434
|
+
{ label: ALGORITHM$3, message: `the done flag never rose in ${submits} submits` }
|
|
6435
|
+
);
|
|
6436
|
+
}
|
|
6437
|
+
}
|
|
6438
|
+
const keys = bindingOf$1(scope.scratch(bytes, "order/keys"), bytes);
|
|
6439
|
+
const vals = bindingOf$1(scope.scratch(bytes, "order/vals"), bytes);
|
|
6440
|
+
const histBytes = radixHistBytes(n, wg);
|
|
6441
|
+
const scratch = {
|
|
6442
|
+
keys: bindingOf$1(scope.scratch(bytes, "order/keys-scratch"), bytes),
|
|
6443
|
+
vals: bindingOf$1(scope.scratch(bytes, "order/vals-scratch"), bytes),
|
|
6444
|
+
hist: bindingOf$1(scope.scratch(histBytes, "order/hist"), histBytes),
|
|
6445
|
+
offsets: bindingOf$1(scope.scratch(histBytes, "order/offsets"), histBytes)
|
|
6446
|
+
};
|
|
6447
|
+
const parent = bindingOf$1(scope.scratch(bytes, "parent"), bytes);
|
|
6448
|
+
const result = new CommandBatch(ctx, `${ALGORITHM$3}/result`);
|
|
6449
|
+
result.copy(depth, keys, bytes);
|
|
6450
|
+
const pass = result.pass("result");
|
|
6451
|
+
recordFill(pass, vals, 0, 1);
|
|
6452
|
+
const sorted = sort.record(pass, keys, vals, n, 32, scratch);
|
|
6453
|
+
recordFill(pass, parent, INVALID_INDEX, 0);
|
|
6454
|
+
const predPlan = planGridStride(n, wg, ctx.caps);
|
|
6455
|
+
for (const w of forward) {
|
|
6456
|
+
const predParams = scope.params(FRONTIER_PARAMS, {
|
|
6457
|
+
wg,
|
|
6458
|
+
n,
|
|
6459
|
+
arcBase: w.arcBase,
|
|
6460
|
+
arcEnd: w.arcEnd,
|
|
6461
|
+
predKind,
|
|
6462
|
+
source,
|
|
6463
|
+
stride: predPlan.stride ?? n
|
|
6464
|
+
});
|
|
6465
|
+
const predBound = pred.bind({
|
|
6466
|
+
...graphBindings(w.core, null),
|
|
6467
|
+
dist: depth,
|
|
6468
|
+
pred: parent,
|
|
6469
|
+
P: predParams.binding
|
|
6470
|
+
});
|
|
6471
|
+
pred.dispatch(pass, predBound, predPlan, [predParams.offset]);
|
|
6472
|
+
}
|
|
6473
|
+
result.endPass();
|
|
6474
|
+
const depthRequest = result.readback(depth.buffer, depth.offset, bytes);
|
|
6475
|
+
const parentRequest = result.readback(parent.buffer, parent.offset, bytes);
|
|
6476
|
+
const orderRequest = result.readback(sorted.vals.buffer, sorted.vals.offset, bytes);
|
|
6477
|
+
const blockRequest = result.readback(counters.buffer, counters.offset, FRONTIER_COUNTERS.byteLength);
|
|
6478
|
+
const back = await submit(result).readback;
|
|
6479
|
+
ctx.assertReady();
|
|
6480
|
+
const block = FRONTIER_COUNTERS.read(new DataView(back), blockRequest.offset);
|
|
6481
|
+
const visitedCount = wordOf$1(block, "visitedCount");
|
|
6482
|
+
if (visitedCount > n) {
|
|
6483
|
+
throw new WebGpuGraphError(
|
|
6484
|
+
"E_VALIDATION",
|
|
6485
|
+
`${ALGORITHM$3}: visitedCount ${visitedCount} exceeds the ${n} vertices (a duplicate claim)`,
|
|
6486
|
+
{
|
|
6487
|
+
label: `${ALGORITHM$3}/visitedCount`,
|
|
6488
|
+
message: `the device counted ${visitedCount} visits of ${n} vertices`
|
|
6489
|
+
}
|
|
6490
|
+
);
|
|
6491
|
+
}
|
|
6492
|
+
const level = wordOf$1(block, "level");
|
|
6493
|
+
const levels = wordOf$1(block, "frontierCount") === 0 ? level : level + 1;
|
|
6494
|
+
const depthOut = dest ?? new Uint32Array(n);
|
|
6495
|
+
depthOut.set(new Uint32Array(back, depthRequest.offset, n));
|
|
6496
|
+
return {
|
|
6497
|
+
depth: depthOut,
|
|
6498
|
+
parent: new Uint32Array(back, parentRequest.offset, n).slice(),
|
|
6499
|
+
order: new Uint32Array(back, orderRequest.offset, visitedCount).slice(),
|
|
6500
|
+
visitedCount,
|
|
6501
|
+
levels,
|
|
6502
|
+
switches: wordOf$1(block, "switches")
|
|
6503
|
+
};
|
|
6504
|
+
} finally {
|
|
6505
|
+
scope.dispose();
|
|
6506
|
+
}
|
|
6507
|
+
}
|
|
6508
|
+
function breadthFirstSearch(ctx, s, source, options) {
|
|
6509
|
+
return bfsWithTuning(ctx, s, source, options, {});
|
|
6510
|
+
}
|
|
6511
|
+
const ALGORITHM$2 = "sssp";
|
|
6512
|
+
const HALF_ALIGN = 64;
|
|
6513
|
+
const RING_SLOTS$2 = 4 * MAX_LEVELS_PER_SUBMIT + 40;
|
|
6514
|
+
function bitsOf(value) {
|
|
6515
|
+
return new Uint32Array(Float32Array.of(value).buffer)[0];
|
|
6516
|
+
}
|
|
6517
|
+
function bindingOf(buffer, size) {
|
|
6518
|
+
return { buffer, offset: 0, size, window: null };
|
|
6519
|
+
}
|
|
6520
|
+
function aborted(algorithm, batchId) {
|
|
6521
|
+
return new WebGpuGraphError(
|
|
6522
|
+
"E_ABORTED",
|
|
6523
|
+
`${algorithm}: the signal was aborted`,
|
|
6524
|
+
batchId === void 0 ? {} : { batchId }
|
|
6525
|
+
);
|
|
6526
|
+
}
|
|
6527
|
+
function assertSource(algorithm, source, n) {
|
|
6528
|
+
if (!Number.isInteger(source) || source < 0 || source >= n) {
|
|
6529
|
+
throw new WebGpuGraphError("E_INVALID_ARGUMENT", `${algorithm}: source ${source} is outside [0, ${n})`, {
|
|
6530
|
+
argument: "source",
|
|
6531
|
+
value: source,
|
|
6532
|
+
expected: `an integer in [0, ${n})`
|
|
6533
|
+
});
|
|
6534
|
+
}
|
|
6535
|
+
}
|
|
6536
|
+
function wordOf(block, name) {
|
|
6537
|
+
const value = block[name];
|
|
6538
|
+
if (typeof value !== "number") {
|
|
6539
|
+
throw new WebGpuGraphError("E_VALIDATION", `${ALGORITHM$2}: counters.${name} did not decode to a number`, {
|
|
6540
|
+
label: `${ALGORITHM$2}/counters`,
|
|
6541
|
+
message: `the field ${name} did not decode to a number`
|
|
6542
|
+
});
|
|
6543
|
+
}
|
|
6544
|
+
return value;
|
|
6545
|
+
}
|
|
6546
|
+
function checkDest(algorithm, dest, n) {
|
|
6547
|
+
if (dest === void 0) {
|
|
6548
|
+
return null;
|
|
6549
|
+
}
|
|
6550
|
+
if (dest instanceof Float32Array && dest.length === n && dest.buffer instanceof ArrayBuffer) {
|
|
6551
|
+
return dest;
|
|
6552
|
+
}
|
|
6553
|
+
throw new WebGpuGraphError(
|
|
6554
|
+
"E_INVALID_ARGUMENT",
|
|
6555
|
+
`${algorithm}: dest must be a Float32Array of length ${n} over an ArrayBuffer`,
|
|
6556
|
+
{
|
|
6557
|
+
argument: "dest",
|
|
6558
|
+
value: `${dest.constructor.name}(${dest.length})`,
|
|
6559
|
+
expected: `Float32Array(${n}) over an ArrayBuffer`
|
|
6560
|
+
}
|
|
6561
|
+
);
|
|
6562
|
+
}
|
|
6563
|
+
function normaliseCutoff(algorithm, cutoff) {
|
|
6564
|
+
if (cutoff === void 0) {
|
|
6565
|
+
return Infinity;
|
|
6566
|
+
}
|
|
6567
|
+
if (Number.isNaN(cutoff)) {
|
|
6568
|
+
throw new WebGpuGraphError("E_INVALID_ARGUMENT", `${algorithm}: cutoff must not be NaN`, {
|
|
6569
|
+
argument: "cutoff",
|
|
6570
|
+
value: cutoff,
|
|
6571
|
+
expected: "a number (the CPU port and the kernel would disagree on NaN)"
|
|
6572
|
+
});
|
|
6573
|
+
}
|
|
6574
|
+
return cutoff;
|
|
6575
|
+
}
|
|
6576
|
+
function resolveWeights$1(algorithm, s, weights) {
|
|
6577
|
+
if (weights === void 0) {
|
|
6578
|
+
if (s.weights === null) {
|
|
6579
|
+
return null;
|
|
6580
|
+
}
|
|
6581
|
+
let sum2 = 0;
|
|
6582
|
+
for (const w of s.weights) {
|
|
6583
|
+
sum2 += w;
|
|
6584
|
+
}
|
|
6585
|
+
return {
|
|
6586
|
+
values: s.weights,
|
|
6587
|
+
override: null,
|
|
6588
|
+
allOne: s.flags.allWeightsOne,
|
|
6589
|
+
nonNegative: s.flags.nonNegativeWeights,
|
|
6590
|
+
finite: s.flags.finiteWeights,
|
|
6591
|
+
sum: sum2
|
|
6592
|
+
};
|
|
6593
|
+
}
|
|
6594
|
+
if (weights.length !== s.arcCount) {
|
|
6595
|
+
throw new WebGpuGraphError(
|
|
6596
|
+
"E_INVALID_ARGUMENT",
|
|
6597
|
+
`${algorithm}: weights has ${weights.length} entries, the snapshot ${s.arcCount} arcs`,
|
|
6598
|
+
{ argument: "weights", value: weights.length, expected: s.arcCount }
|
|
6599
|
+
);
|
|
6600
|
+
}
|
|
6601
|
+
const values = weights instanceof Float32Array ? weights : Float32Array.from(weights);
|
|
6602
|
+
let allOne = true;
|
|
6603
|
+
let nonNegative = true;
|
|
6604
|
+
let finite2 = true;
|
|
6605
|
+
let sum = 0;
|
|
6606
|
+
for (const w of values) {
|
|
6607
|
+
if (w !== 1) {
|
|
6608
|
+
allOne = false;
|
|
6609
|
+
}
|
|
6610
|
+
if (w < 0) {
|
|
6611
|
+
nonNegative = false;
|
|
6612
|
+
}
|
|
6613
|
+
if (!Number.isFinite(w)) {
|
|
6614
|
+
finite2 = false;
|
|
6615
|
+
}
|
|
6616
|
+
sum += w;
|
|
6617
|
+
}
|
|
6618
|
+
return { values, override: values, allOne, nonNegative, finite: finite2, sum };
|
|
6619
|
+
}
|
|
6620
|
+
async function unitWeightRoute(ctx, s, source, cutoff, dest, options) {
|
|
6621
|
+
const maxDepth = cutoff === Infinity ? void 0 : Math.floor(cutoff);
|
|
6622
|
+
const bfs = await bfsWithTuning(
|
|
6623
|
+
ctx,
|
|
6624
|
+
s,
|
|
6625
|
+
source,
|
|
6626
|
+
{ maxDepth, signal: options?.signal, onProgress: options?.onProgress },
|
|
6627
|
+
{ predKind: 0 }
|
|
6628
|
+
);
|
|
6629
|
+
const dist = dest ?? new Float32Array(s.nodeCount);
|
|
6630
|
+
for (let v = 0; v < s.nodeCount; v++) {
|
|
6631
|
+
const d = bfs.depth[v];
|
|
6632
|
+
dist[v] = d === INVALID_INDEX ? Infinity : d;
|
|
6633
|
+
}
|
|
6634
|
+
return { dist, predArc: bfs.parent, reachedCount: bfs.visitedCount };
|
|
6635
|
+
}
|
|
6636
|
+
function predBufferWords(n) {
|
|
6637
|
+
return 2 * Math.ceil(n / 64) * 64 + 64;
|
|
6638
|
+
}
|
|
6639
|
+
async function predecessorPass(input) {
|
|
6640
|
+
const { algorithm, ctx, scope, predKernel, recordFill, graph, dist, pred, n, arcCount, source, mode } = input;
|
|
6641
|
+
const wg = ctx.workgroupSize;
|
|
6642
|
+
const { queue } = ctx.device;
|
|
6643
|
+
const bytes = 4 * n;
|
|
6644
|
+
const hb = Math.ceil(n / 64) * 64;
|
|
6645
|
+
const predArcs = { buffer: pred.buffer, offset: pred.offset, size: bytes, window: null };
|
|
6646
|
+
queue.writeBuffer(pred.buffer, pred.offset + 4 * (hb + source), Uint32Array.of(0));
|
|
6647
|
+
queue.writeBuffer(pred.buffer, pred.offset + 4 * 2 * hb, new Uint32Array(2));
|
|
6648
|
+
const predPlan = planGridStride(n, wg, ctx.caps);
|
|
6649
|
+
const predFields = {
|
|
6650
|
+
wg,
|
|
6651
|
+
n,
|
|
6652
|
+
arcBase: 0,
|
|
6653
|
+
arcEnd: arcCount,
|
|
6654
|
+
predKind: 0,
|
|
6655
|
+
source,
|
|
6656
|
+
stride: predPlan.stride ?? n,
|
|
6657
|
+
mode
|
|
6658
|
+
};
|
|
6659
|
+
const maxBatches = Math.ceil((n + 1) / MAX_LEVELS_PER_SUBMIT) + 1;
|
|
6660
|
+
let batches = 0;
|
|
6661
|
+
for (; ; ) {
|
|
6662
|
+
const batch = new CommandBatch(ctx, `${algorithm}/pred`);
|
|
6663
|
+
const pass = batch.pass("pred");
|
|
6664
|
+
const recordRole = (role, iteration) => {
|
|
6665
|
+
const params = scope.params(FRONTIER_PARAMS, { ...predFields, role, iteration });
|
|
6666
|
+
const bound = predKernel.bind({ ...graph, dist, pred, P: params.binding });
|
|
6667
|
+
predKernel.dispatch(pass, bound, predPlan, [params.offset]);
|
|
6668
|
+
};
|
|
6669
|
+
if (batches === 0 && mode === 0) {
|
|
6670
|
+
recordRole(0, 0);
|
|
6671
|
+
}
|
|
6672
|
+
for (let iteration = 0; iteration < MAX_LEVELS_PER_SUBMIT; iteration++) {
|
|
6673
|
+
recordRole(1, iteration);
|
|
6674
|
+
}
|
|
6675
|
+
recordFill(pass, predArcs, n, INVALID_INDEX);
|
|
6676
|
+
recordRole(2, 0);
|
|
6677
|
+
batch.endPass();
|
|
6678
|
+
const distRequest = batch.readback(dist.buffer, dist.offset, bytes);
|
|
6679
|
+
const predRequest = batch.readback(pred.buffer, pred.offset, bytes);
|
|
6680
|
+
const flagsRequest = batch.readback(pred.buffer, pred.offset + 4 * 2 * hb, 8);
|
|
6681
|
+
scope.flush();
|
|
6682
|
+
const back = await batch.submit().readback;
|
|
6683
|
+
ctx.assertReady();
|
|
6684
|
+
batches += 1;
|
|
6685
|
+
const [changed, orphans] = new Uint32Array(back, flagsRequest.offset, 2);
|
|
6686
|
+
if (changed < MAX_LEVELS_PER_SUBMIT) {
|
|
6687
|
+
return {
|
|
6688
|
+
dist: new Float32Array(back, distRequest.offset, n),
|
|
6689
|
+
predArc: new Uint32Array(back, predRequest.offset, n).slice(),
|
|
6690
|
+
orphans
|
|
6691
|
+
};
|
|
6692
|
+
}
|
|
6693
|
+
if (batches > maxBatches) {
|
|
6694
|
+
throw new WebGpuGraphError(
|
|
6695
|
+
"E_VALIDATION",
|
|
6696
|
+
`${algorithm}: the hop passes still changed something after ${batches} batches (a hop count is at most ${n})`,
|
|
6697
|
+
{ label: `${algorithm}/pred`, message: `the hop passes did not converge in ${batches} batches` }
|
|
6698
|
+
);
|
|
6699
|
+
}
|
|
6700
|
+
queue.writeBuffer(pred.buffer, pred.offset + 4 * 2 * hb, new Uint32Array(2));
|
|
6701
|
+
}
|
|
6702
|
+
}
|
|
6703
|
+
async function ssspWithTuning(ctx, s, source, options, tuning) {
|
|
6704
|
+
ctx.assertReady();
|
|
6705
|
+
await assertDeviceComputes(ctx);
|
|
6706
|
+
const n = s.nodeCount;
|
|
6707
|
+
assertSource(ALGORITHM$2, source, n);
|
|
6708
|
+
const roundsPerSubmit = tuning.roundsPerSubmit ?? MAX_LEVELS_PER_SUBMIT;
|
|
6709
|
+
if (!Number.isInteger(roundsPerSubmit) || roundsPerSubmit < 1 || roundsPerSubmit > MAX_LEVELS_PER_SUBMIT) {
|
|
6710
|
+
throw new WebGpuGraphError(
|
|
6711
|
+
"E_INVALID_ARGUMENT",
|
|
6712
|
+
`${ALGORITHM$2}: roundsPerSubmit must be an integer in [1, ${MAX_LEVELS_PER_SUBMIT}]`,
|
|
6713
|
+
{
|
|
6714
|
+
argument: "roundsPerSubmit",
|
|
6715
|
+
value: roundsPerSubmit,
|
|
6716
|
+
expected: `an integer in [1, ${MAX_LEVELS_PER_SUBMIT}]`
|
|
6717
|
+
}
|
|
6718
|
+
);
|
|
6719
|
+
}
|
|
6720
|
+
if (tuning.delta !== void 0 && !(Number.isFinite(tuning.delta) && tuning.delta > 0)) {
|
|
6721
|
+
throw new WebGpuGraphError("E_INVALID_ARGUMENT", `${ALGORITHM$2}: delta must be a finite positive number`, {
|
|
6722
|
+
argument: "delta",
|
|
6723
|
+
value: tuning.delta,
|
|
6724
|
+
expected: "a finite positive number"
|
|
6725
|
+
});
|
|
6726
|
+
}
|
|
6727
|
+
const dest = checkDest(ALGORITHM$2, options?.dest, n);
|
|
6728
|
+
const vector2 = resolveWeights$1(ALGORITHM$2, s, options?.weights);
|
|
6729
|
+
const cutoff = normaliseCutoff(ALGORITHM$2, options?.cutoff);
|
|
6730
|
+
if (options?.signal?.aborted) {
|
|
6731
|
+
throw aborted(ALGORITHM$2);
|
|
6732
|
+
}
|
|
6733
|
+
if (vector2 === null || vector2.allOne) {
|
|
6734
|
+
return unitWeightRoute(ctx, s, source, cutoff, dest, options);
|
|
6735
|
+
}
|
|
6736
|
+
if (!vector2.nonNegative) {
|
|
6737
|
+
throw new WebGpuGraphError("E_UNSUPPORTED", `${ALGORITHM$2}: a negative weight has no shortest path here`, {
|
|
6738
|
+
feature: "sssp.negativeWeights",
|
|
6739
|
+
hint: "use bellmanFord"
|
|
6740
|
+
});
|
|
6741
|
+
}
|
|
6742
|
+
if (!vector2.finite) {
|
|
6743
|
+
throw new WebGpuGraphError("E_UNSUPPORTED", `${ALGORITHM$2}: a NaN or infinite weight has no bit-pattern order`, {
|
|
6744
|
+
feature: "sssp.nonFiniteWeights"
|
|
6745
|
+
});
|
|
6746
|
+
}
|
|
6747
|
+
const { arcCount } = s;
|
|
6748
|
+
const core = ctx.residency.core(s);
|
|
6749
|
+
const limit = ctx.caps.limits.maxStorageBufferBindingSize;
|
|
6750
|
+
assertWholeCore(core, arcCount, limit, ALGORITHM$2);
|
|
6751
|
+
const cap = Math.ceil(Math.max(1, arcCount) / HALF_ALIGN) * HALF_ALIGN;
|
|
6752
|
+
if (8 * cap > limit) {
|
|
6753
|
+
throw new WebGpuGraphError(
|
|
6754
|
+
"E_TOO_LARGE",
|
|
6755
|
+
`${ALGORITHM$2}: the near-far queue of ${cap} entries per half needs ${8 * cap} bytes, above the ${limit}-byte binding limit (the relax is never windowed)`,
|
|
6756
|
+
{ needed: 8 * cap, limit, path: "sssp.queue", algorithm: ALGORITHM$2 }
|
|
6757
|
+
);
|
|
6758
|
+
}
|
|
6759
|
+
const delta = Math.fround(
|
|
6760
|
+
tuning.delta ?? (vector2.sum === 0 ? 1 : SSSP_DELTA_FACTOR * (vector2.sum / arcCount) / (arcCount / n))
|
|
6761
|
+
);
|
|
6762
|
+
const deltaBits = bitsOf(delta);
|
|
6763
|
+
const maxRounds = n + Math.ceil(vector2.sum / delta) + 1;
|
|
6764
|
+
const maxSubmits = Math.ceil((maxRounds + 1) / roundsPerSubmit) + 1;
|
|
6765
|
+
const scope = algorithmScope(ctx, ALGORITHM$2, RING_SLOTS$2);
|
|
6766
|
+
try {
|
|
6767
|
+
const wg = ctx.workgroupSize;
|
|
6768
|
+
const bytes = 4 * n;
|
|
6769
|
+
const dist = bindingOf(scope.scratch(bytes, "dist"), bytes);
|
|
6770
|
+
const owner = bindingOf(scope.scratch(bytes, "owner"), bytes);
|
|
6771
|
+
const queueOut = bindingOf(scope.scratch(8 * cap, "queue-out"), 8 * cap);
|
|
6772
|
+
const nearHalf = { buffer: queueOut.buffer, offset: 0, size: 4 * cap, window: null };
|
|
6773
|
+
const farHalf = { buffer: queueOut.buffer, offset: 4 * cap, size: 4 * cap, window: null };
|
|
6774
|
+
const predWords = predBufferWords(n);
|
|
6775
|
+
const pred = bindingOf(scope.scratch(4 * predWords, "pred"), 4 * predWords);
|
|
6776
|
+
const { queue } = ctx.device;
|
|
6777
|
+
let weightsBinding;
|
|
6778
|
+
if (vector2.override !== null) {
|
|
6779
|
+
const uploaded = scope.scratch(4 * arcCount, "weights");
|
|
6780
|
+
queue.writeBuffer(uploaded, 0, vector2.override);
|
|
6781
|
+
weightsBinding = bindingOf(uploaded, 4 * arcCount);
|
|
6782
|
+
}
|
|
6783
|
+
await ctx.allocator.check();
|
|
6784
|
+
const planner = await prepareFrontier(scope, n, arcCount, cap);
|
|
6785
|
+
const compact = await prepareCompact(scope);
|
|
6786
|
+
const overrides = graphOverrides(core, null, weightsBinding);
|
|
6787
|
+
const relax = await ctx.pipelines.kernel(kernelSpec("sssp-relax", overrides));
|
|
6788
|
+
const predKernel = await ctx.pipelines.kernel(kernelSpec("sssp-pred", { ...overrides, MODE: 0 }));
|
|
6789
|
+
const fill = await ctx.pipelines.kernel(kernelSpec("fill"));
|
|
6790
|
+
const graph = graphBindings(core, null, weightsBinding);
|
|
6791
|
+
const { frontier } = planner;
|
|
6792
|
+
const { counters } = frontier;
|
|
6793
|
+
const nearIn = frontier.vertices[0];
|
|
6794
|
+
const farIn = frontier.vertices[1];
|
|
6795
|
+
const recordFill = (pass, dst, count, value) => {
|
|
6796
|
+
const params = scope.params(FILL_PARAMS, { count, value, mode: 0, pad0: 0 });
|
|
6797
|
+
fill.dispatch(pass, fill.bind({ dst, P: params.binding }), plan1d(count, wg, ctx.caps), [params.offset]);
|
|
6798
|
+
};
|
|
6799
|
+
const submit = (batch) => {
|
|
6800
|
+
scope.flush();
|
|
6801
|
+
return batch.submit();
|
|
6802
|
+
};
|
|
6803
|
+
const setup = new CommandBatch(ctx, `${ALGORITHM$2}/setup`);
|
|
6804
|
+
const setupPass = setup.pass("fill");
|
|
6805
|
+
recordFill(setupPass, dist, n, F32_INF_BITS);
|
|
6806
|
+
recordFill(setupPass, pred, predWords, INVALID_INDEX);
|
|
6807
|
+
setup.endPass();
|
|
6808
|
+
await submit(setup).readback;
|
|
6809
|
+
ctx.assertReady();
|
|
6810
|
+
queue.writeBuffer(dist.buffer, dist.offset + 4 * source, Uint32Array.of(0));
|
|
6811
|
+
frontier.reset(queue, source, { nextFrontierCount: 1, thresholdBits: deltaBits, deltaBits });
|
|
6812
|
+
queue.writeBuffer(queueOut.buffer, queueOut.offset, Uint32Array.of(source));
|
|
6813
|
+
const nearDedupe = {
|
|
6814
|
+
queue: nearHalf,
|
|
6815
|
+
count: cap,
|
|
6816
|
+
countIndex: W.edgeCount,
|
|
6817
|
+
// role 2 writes the raw near count here when it chooses a near round, 0 otherwise
|
|
6818
|
+
counters,
|
|
6819
|
+
owner,
|
|
6820
|
+
out: nearIn,
|
|
6821
|
+
outCount: counters,
|
|
6822
|
+
outIndex: W.frontierCount
|
|
6823
|
+
};
|
|
6824
|
+
const farDedupe = {
|
|
6825
|
+
queue: farHalf,
|
|
6826
|
+
count: cap,
|
|
6827
|
+
countIndex: W.edgeCountUnclamped,
|
|
6828
|
+
// and the raw far count here on a far round
|
|
6829
|
+
counters,
|
|
6830
|
+
owner,
|
|
6831
|
+
out: farIn,
|
|
6832
|
+
outCount: counters,
|
|
6833
|
+
outIndex: W.farCount
|
|
6834
|
+
};
|
|
6835
|
+
const relaxPlan = planGridStride(n, wg, ctx.caps);
|
|
6836
|
+
const relaxFields = {
|
|
6837
|
+
wg,
|
|
6838
|
+
n,
|
|
6839
|
+
edgeCapacity: cap,
|
|
6840
|
+
arcBase: 0,
|
|
6841
|
+
arcEnd: arcCount,
|
|
6842
|
+
cutoffBits: bitsOf(cutoff),
|
|
6843
|
+
source,
|
|
6844
|
+
stride: relaxPlan.stride ?? wg
|
|
6845
|
+
};
|
|
6846
|
+
let roundsRecorded = 0;
|
|
6847
|
+
let submits = 0;
|
|
6848
|
+
for (; ; ) {
|
|
6849
|
+
const batch = new CommandBatch(ctx, `${ALGORITHM$2}/rounds`);
|
|
6850
|
+
const pass = batch.pass("sssp");
|
|
6851
|
+
const near = scope.params(FRONTIER_PARAMS, { ...relaxFields, role: 0 });
|
|
6852
|
+
const far = scope.params(FRONTIER_PARAMS, { ...relaxFields, role: 1 });
|
|
6853
|
+
const boundNear = relax.bind({ ...graph, dist, counters, queueIn: nearIn, queueOut, P: near.binding });
|
|
6854
|
+
const boundFar = relax.bind({ ...graph, dist, counters, queueIn: farIn, queueOut, P: far.binding });
|
|
6855
|
+
for (let round = 0; round < roundsPerSubmit; round++) {
|
|
6856
|
+
planner.recordFinalize(pass, 2, round, {});
|
|
6857
|
+
compact.recordDedupe(pass, nearDedupe);
|
|
6858
|
+
compact.recordDedupe(pass, farDedupe);
|
|
6859
|
+
planner.recordFinalize(pass, 3, round, {});
|
|
6860
|
+
relax.dispatch(pass, boundNear, relaxPlan, [near.offset]);
|
|
6861
|
+
relax.dispatch(pass, boundFar, relaxPlan, [far.offset]);
|
|
6862
|
+
}
|
|
6863
|
+
batch.endPass();
|
|
6864
|
+
const doneRequest = batch.readback(counters.buffer, counters.offset + 4 * W.done, 4);
|
|
6865
|
+
const inspect = tuning.onRound === void 0 ? null : batch.readback(counters.buffer, counters.offset, FRONTIER_COUNTERS.byteLength);
|
|
6866
|
+
const submitted = submit(batch);
|
|
6867
|
+
const back = await submitted.readback;
|
|
6868
|
+
roundsRecorded += roundsPerSubmit;
|
|
6869
|
+
submits += 1;
|
|
6870
|
+
ctx.assertReady();
|
|
6871
|
+
if (options?.signal?.aborted) {
|
|
6872
|
+
throw aborted(ALGORITHM$2, submitted.id);
|
|
6873
|
+
}
|
|
6874
|
+
options?.onProgress?.(Math.min(roundsRecorded, maxRounds), maxRounds);
|
|
6875
|
+
if (inspect !== null && tuning.onRound !== void 0) {
|
|
6876
|
+
tuning.onRound(roundsRecorded - 1, FRONTIER_COUNTERS.read(new DataView(back), inspect.offset));
|
|
6877
|
+
}
|
|
6878
|
+
const done = new Uint32Array(back, doneRequest.offset, 1)[0];
|
|
6879
|
+
if (done === 1) {
|
|
6880
|
+
break;
|
|
6881
|
+
}
|
|
6882
|
+
if (done !== 0) {
|
|
6883
|
+
const block = FRONTIER_COUNTERS.read(
|
|
6884
|
+
new DataView(
|
|
6885
|
+
await ctx.readback.read(
|
|
6886
|
+
counters.buffer,
|
|
6887
|
+
FRONTIER_COUNTERS.byteLength,
|
|
6888
|
+
void 0,
|
|
6889
|
+
counters.offset
|
|
6890
|
+
)
|
|
6891
|
+
)
|
|
6892
|
+
);
|
|
6893
|
+
if (done === 2) {
|
|
6894
|
+
const needed = Math.max(wordOf(block, "nextFrontierCount"), wordOf(block, "nextFarCount"));
|
|
6895
|
+
throw new WebGpuGraphError(
|
|
6896
|
+
"E_TOO_LARGE",
|
|
6897
|
+
`${ALGORITHM$2}: a raw pile of ${needed} entries overflowed its ${cap}-entry half`,
|
|
6898
|
+
{ needed, limit: cap, path: "sssp.pile", algorithm: ALGORITHM$2 }
|
|
6899
|
+
);
|
|
6900
|
+
}
|
|
6901
|
+
throw new WebGpuGraphError(
|
|
6902
|
+
"E_UNSUPPORTED",
|
|
6903
|
+
`${ALGORITHM$2}: the f32 threshold ${wordOf(block, "thresholdBits")} absorbed the delta ${deltaBits} (as bit patterns); the far pile can no longer be bucketed`,
|
|
6904
|
+
{ feature: "sssp.thresholdAbsorbed", hint: "the distances outgrew the delta's f32 precision" }
|
|
6905
|
+
);
|
|
6906
|
+
}
|
|
6907
|
+
if (submits > maxSubmits) {
|
|
6908
|
+
throw new WebGpuGraphError(
|
|
6909
|
+
"E_VALIDATION",
|
|
6910
|
+
`${ALGORITHM$2}: the done flag never rose in ${submits} submits (at most ${maxRounds} rounds)`,
|
|
6911
|
+
{ label: `${ALGORITHM$2}/rounds`, message: `the done flag never rose in ${submits} submits` }
|
|
6912
|
+
);
|
|
6913
|
+
}
|
|
6914
|
+
}
|
|
6915
|
+
const passed = await predecessorPass({
|
|
6916
|
+
algorithm: ALGORITHM$2,
|
|
6917
|
+
ctx,
|
|
6918
|
+
scope,
|
|
6919
|
+
predKernel,
|
|
6920
|
+
recordFill,
|
|
6921
|
+
graph,
|
|
6922
|
+
dist,
|
|
6923
|
+
pred,
|
|
6924
|
+
n,
|
|
6925
|
+
arcCount,
|
|
6926
|
+
source,
|
|
6927
|
+
mode: 0
|
|
6928
|
+
});
|
|
6929
|
+
if (passed.orphans !== 0) {
|
|
6930
|
+
throw new WebGpuGraphError(
|
|
6931
|
+
"E_VALIDATION",
|
|
6932
|
+
`${ALGORITHM$2}: ${passed.orphans} reached node(s) the predecessor key never reached (a kernel bug)`,
|
|
6933
|
+
{ label: `${ALGORITHM$2}/pred`, message: `${passed.orphans} orphan(s) in the predecessor pass` }
|
|
6934
|
+
);
|
|
6935
|
+
}
|
|
6936
|
+
const distOut = dest ?? new Float32Array(n);
|
|
6937
|
+
distOut.set(passed.dist);
|
|
6938
|
+
let reachedCount = 0;
|
|
6939
|
+
for (const d of distOut) {
|
|
6940
|
+
if (d !== Infinity) {
|
|
6941
|
+
reachedCount += 1;
|
|
6942
|
+
}
|
|
6943
|
+
}
|
|
6944
|
+
return { dist: distOut, predArc: passed.predArc, reachedCount };
|
|
6945
|
+
} finally {
|
|
6946
|
+
scope.dispose();
|
|
6947
|
+
}
|
|
6948
|
+
}
|
|
6949
|
+
function sssp(ctx, s, source, options) {
|
|
6950
|
+
return ssspWithTuning(ctx, s, source, options, {});
|
|
6951
|
+
}
|
|
6952
|
+
const ALGORITHM$1 = "bellmanFord";
|
|
6953
|
+
const ROUNDS_PER_BATCH = 8;
|
|
6954
|
+
const MAX_RETRIES = 16;
|
|
6955
|
+
const RING_SLOTS$1 = MAX_LEVELS_PER_SUBMIT + 16;
|
|
6956
|
+
function assertSymmetric(s, vector2) {
|
|
6957
|
+
const { arcToEdge, edgeToArc } = s;
|
|
6958
|
+
for (let a = 0; a < s.arcCount; a++) {
|
|
6959
|
+
const forward = edgeToArc[arcToEdge[a]];
|
|
6960
|
+
if (vector2[a] !== vector2[forward]) {
|
|
6961
|
+
throw new WebGpuGraphError(
|
|
6962
|
+
"E_UNSUPPORTED",
|
|
6963
|
+
`${ALGORITHM$1}: weights[${a}] = ${vector2[a]} differs from weights[${forward}] = ${vector2[forward]}, the forward arc of the same undirected edge; the kernel reads one weight per edge`,
|
|
6964
|
+
{ feature: "bellmanFord.asymmetricUndirectedWeights", hint: "use a directed snapshot" }
|
|
6965
|
+
);
|
|
6966
|
+
}
|
|
6967
|
+
}
|
|
6968
|
+
}
|
|
6969
|
+
async function bellmanFordWithTuning(ctx, s, source, options, tuning) {
|
|
6970
|
+
ctx.assertReady();
|
|
6971
|
+
await assertDeviceComputes(ctx);
|
|
6972
|
+
const n = s.nodeCount;
|
|
6973
|
+
assertSource(ALGORITHM$1, source, n);
|
|
6974
|
+
const maxRetries = tuning.maxRetries ?? MAX_RETRIES;
|
|
6975
|
+
if (!Number.isInteger(maxRetries) || maxRetries < 1) {
|
|
6976
|
+
throw new WebGpuGraphError("E_INVALID_ARGUMENT", `${ALGORITHM$1}: maxRetries must be an integer >= 1`, {
|
|
6977
|
+
argument: "maxRetries",
|
|
6978
|
+
value: maxRetries,
|
|
6979
|
+
expected: "an integer >= 1"
|
|
6980
|
+
});
|
|
6981
|
+
}
|
|
6982
|
+
const roundsPerBatch = tuning.roundsPerBatch ?? ROUNDS_PER_BATCH;
|
|
6983
|
+
if (!Number.isInteger(roundsPerBatch) || roundsPerBatch < 1 || roundsPerBatch > MAX_LEVELS_PER_SUBMIT) {
|
|
6984
|
+
throw new WebGpuGraphError(
|
|
6985
|
+
"E_INVALID_ARGUMENT",
|
|
6986
|
+
`${ALGORITHM$1}: roundsPerBatch must be an integer in [1, ${MAX_LEVELS_PER_SUBMIT}]`,
|
|
6987
|
+
{
|
|
6988
|
+
argument: "roundsPerBatch",
|
|
6989
|
+
value: roundsPerBatch,
|
|
6990
|
+
expected: `an integer in [1, ${MAX_LEVELS_PER_SUBMIT}]`
|
|
6991
|
+
}
|
|
6992
|
+
);
|
|
6993
|
+
}
|
|
6994
|
+
const dest = checkDest(ALGORITHM$1, options?.dest, n);
|
|
6995
|
+
const vector2 = resolveWeights$1(ALGORITHM$1, s, options?.weights);
|
|
6996
|
+
const cutoff = normaliseCutoff(ALGORITHM$1, options?.cutoff);
|
|
6997
|
+
if (options?.signal?.aborted) {
|
|
6998
|
+
throw aborted(ALGORITHM$1);
|
|
6999
|
+
}
|
|
7000
|
+
if (vector2 === null || vector2.allOne) {
|
|
7001
|
+
const unit = await unitWeightRoute(ctx, s, source, cutoff, dest, options);
|
|
7002
|
+
return { result: { ...unit, hasNegativeCycle: false }, rounds: 0, retryExhaustedRounds: 0 };
|
|
7003
|
+
}
|
|
7004
|
+
if (!vector2.finite) {
|
|
7005
|
+
throw new WebGpuGraphError("E_UNSUPPORTED", `${ALGORITHM$1}: a NaN or infinite weight has no shortest path`, {
|
|
7006
|
+
feature: "bellmanFord.nonFiniteWeights"
|
|
7007
|
+
});
|
|
7008
|
+
}
|
|
7009
|
+
if (!s.directed && vector2.override !== null) {
|
|
7010
|
+
assertSymmetric(s, vector2.override);
|
|
7011
|
+
}
|
|
7012
|
+
const { arcCount } = s;
|
|
7013
|
+
const core = ctx.residency.core(s, ["rowPtr", "colIdx", "weights", "edgeToArc"]);
|
|
7014
|
+
assertWholeCore(core, arcCount, ctx.caps.limits.maxStorageBufferBindingSize, ALGORITHM$1);
|
|
7015
|
+
const edges = ctx.residency.view(s, "edgeList");
|
|
7016
|
+
const edgeCount = edges.scalars.edgeCount[0];
|
|
7017
|
+
const scope = algorithmScope(ctx, ALGORITHM$1, RING_SLOTS$1);
|
|
7018
|
+
try {
|
|
7019
|
+
const wg = ctx.workgroupSize;
|
|
7020
|
+
const bytes = 4 * n;
|
|
7021
|
+
const dist = bindingOf(scope.scratch(bytes, "dist"), bytes);
|
|
7022
|
+
const predWords = predBufferWords(n);
|
|
7023
|
+
const pred = bindingOf(scope.scratch(4 * predWords, "pred"), 4 * predWords);
|
|
7024
|
+
const flags = bindingOf(scope.scratch(BF_FLAGS.byteLength, "flags"), BF_FLAGS.byteLength);
|
|
7025
|
+
let iota = null;
|
|
7026
|
+
let { edgeToArc } = core;
|
|
7027
|
+
if (edgeToArc === null) {
|
|
7028
|
+
iota = bindingOf(scope.scratch(4 * edgeCount, "iota"), 4 * edgeCount);
|
|
7029
|
+
edgeToArc = iota;
|
|
7030
|
+
}
|
|
7031
|
+
const { queue } = ctx.device;
|
|
7032
|
+
let weightsBinding;
|
|
7033
|
+
if (vector2.override !== null) {
|
|
7034
|
+
const uploaded = scope.scratch(4 * arcCount, "weights");
|
|
7035
|
+
queue.writeBuffer(uploaded, 0, vector2.override);
|
|
7036
|
+
weightsBinding = bindingOf(uploaded, 4 * arcCount);
|
|
7037
|
+
}
|
|
7038
|
+
await ctx.allocator.check();
|
|
7039
|
+
const overrides = graphOverrides(core, null, weightsBinding);
|
|
7040
|
+
const relax = await ctx.pipelines.kernel(kernelSpec("bf-relax", { UNDIRECTED: !s.directed }));
|
|
7041
|
+
const predKernel = await ctx.pipelines.kernel(kernelSpec("sssp-pred", { ...overrides, MODE: 0 }));
|
|
7042
|
+
const fill = await ctx.pipelines.kernel(kernelSpec("fill"));
|
|
7043
|
+
const graph = graphBindings(core, null, weightsBinding);
|
|
7044
|
+
const recordFill = (pass, dst, count, value, mode = 0) => {
|
|
7045
|
+
const params = scope.params(FILL_PARAMS, { count, value, mode, pad0: 0 });
|
|
7046
|
+
fill.dispatch(pass, fill.bind({ dst, P: params.binding }), plan1d(count, wg, ctx.caps), [params.offset]);
|
|
7047
|
+
};
|
|
7048
|
+
const submit = (batch) => {
|
|
7049
|
+
scope.flush();
|
|
7050
|
+
return batch.submit();
|
|
7051
|
+
};
|
|
7052
|
+
const setup = new CommandBatch(ctx, `${ALGORITHM$1}/setup`);
|
|
7053
|
+
const setupPass = setup.pass("fill");
|
|
7054
|
+
recordFill(setupPass, dist, n, F32_INF_BITS);
|
|
7055
|
+
recordFill(setupPass, pred, predWords, INVALID_INDEX);
|
|
7056
|
+
if (iota !== null) {
|
|
7057
|
+
recordFill(setupPass, iota, edgeCount, 0, 1);
|
|
7058
|
+
}
|
|
7059
|
+
setup.endPass();
|
|
7060
|
+
await submit(setup).readback;
|
|
7061
|
+
ctx.assertReady();
|
|
7062
|
+
queue.writeBuffer(dist.buffer, dist.offset + 4 * source, Uint32Array.of(0));
|
|
7063
|
+
const edgePlan = planGridStride(edgeCount, wg, ctx.caps);
|
|
7064
|
+
const relaxBindings = {
|
|
7065
|
+
edgeSrc: edges.bindings.src,
|
|
7066
|
+
edgeDst: edges.bindings.dst,
|
|
7067
|
+
edgeToArc,
|
|
7068
|
+
weights: graph.weights,
|
|
7069
|
+
dist,
|
|
7070
|
+
flags
|
|
7071
|
+
};
|
|
7072
|
+
const relaxFields = {
|
|
7073
|
+
edgeCount,
|
|
7074
|
+
stride: edgePlan.stride ?? edgeCount,
|
|
7075
|
+
maxRetries,
|
|
7076
|
+
cutoffBits: bitsOf(cutoff)
|
|
7077
|
+
};
|
|
7078
|
+
const zero = new Uint32Array(BF_FLAGS.byteLength / 4);
|
|
7079
|
+
const runRounds = async (count, label) => {
|
|
7080
|
+
queue.writeBuffer(flags.buffer, flags.offset, zero);
|
|
7081
|
+
const batch = new CommandBatch(ctx, `${ALGORITHM$1}/${label}`);
|
|
7082
|
+
const pass = batch.pass("relax");
|
|
7083
|
+
const params = scope.params(BF_PARAMS, relaxFields);
|
|
7084
|
+
const bound = relax.bind({ ...relaxBindings, P: params.binding });
|
|
7085
|
+
for (let round = 0; round < count; round++) {
|
|
7086
|
+
relax.dispatch(pass, bound, edgePlan, [params.offset]);
|
|
7087
|
+
}
|
|
7088
|
+
batch.endPass();
|
|
7089
|
+
const request = batch.readback(flags.buffer, flags.offset, BF_FLAGS.byteLength);
|
|
7090
|
+
const submitted = submit(batch);
|
|
7091
|
+
const back = await submitted.readback;
|
|
7092
|
+
ctx.assertReady();
|
|
7093
|
+
const block = BF_FLAGS.read(new DataView(back), request.offset);
|
|
7094
|
+
return { changed: Number(block.changed), retryExhausted: Number(block.retryExhausted), id: submitted.id };
|
|
7095
|
+
};
|
|
7096
|
+
let rounds = 0;
|
|
7097
|
+
let retryExhaustedRounds = 0;
|
|
7098
|
+
let hasNegativeCycle = false;
|
|
7099
|
+
for (; ; ) {
|
|
7100
|
+
const remaining = n - 1 - rounds;
|
|
7101
|
+
if (remaining <= 0) {
|
|
7102
|
+
const decision = await runRounds(1, "decision");
|
|
7103
|
+
if (decision.retryExhausted !== 0) {
|
|
7104
|
+
throw new WebGpuGraphError(
|
|
7105
|
+
"E_VALIDATION",
|
|
7106
|
+
`${ALGORITHM$1}: a lane exhausted the ${maxRetries}-retry compare-exchange bound in the decision round, so its change is not a verdict`,
|
|
7107
|
+
{
|
|
7108
|
+
label: `${ALGORITHM$1}/retry`,
|
|
7109
|
+
message: "retryExhausted in the decision round",
|
|
7110
|
+
batchId: decision.id
|
|
7111
|
+
}
|
|
7112
|
+
);
|
|
7113
|
+
}
|
|
7114
|
+
hasNegativeCycle = decision.changed !== 0;
|
|
7115
|
+
break;
|
|
7116
|
+
}
|
|
7117
|
+
const count = Math.min(roundsPerBatch, remaining);
|
|
7118
|
+
const batch = await runRounds(count, "rounds");
|
|
7119
|
+
rounds += count;
|
|
7120
|
+
if (batch.retryExhausted !== 0) {
|
|
7121
|
+
retryExhaustedRounds += 1;
|
|
7122
|
+
}
|
|
7123
|
+
if (options?.signal?.aborted) {
|
|
7124
|
+
throw aborted(ALGORITHM$1, batch.id);
|
|
7125
|
+
}
|
|
7126
|
+
options?.onProgress?.(rounds, n);
|
|
7127
|
+
if (batch.changed === 0 && batch.retryExhausted === 0) {
|
|
7128
|
+
break;
|
|
7129
|
+
}
|
|
7130
|
+
}
|
|
7131
|
+
const passed = await predecessorPass({
|
|
7132
|
+
algorithm: ALGORITHM$1,
|
|
7133
|
+
ctx,
|
|
7134
|
+
scope,
|
|
7135
|
+
predKernel,
|
|
7136
|
+
recordFill,
|
|
7137
|
+
graph,
|
|
7138
|
+
dist,
|
|
7139
|
+
pred,
|
|
7140
|
+
n,
|
|
7141
|
+
arcCount,
|
|
7142
|
+
source,
|
|
7143
|
+
mode: 1
|
|
7144
|
+
});
|
|
7145
|
+
if (passed.orphans !== 0 && !hasNegativeCycle) {
|
|
7146
|
+
throw new WebGpuGraphError(
|
|
7147
|
+
"E_UNSUPPORTED",
|
|
7148
|
+
`${ALGORITHM$1}: ${passed.orphans} reached node(s) the tight subgraph never reaches (a cycle of weights below one f32 ulp relaxed once)`,
|
|
7149
|
+
{
|
|
7150
|
+
feature: "bellmanFord.roundedCycle",
|
|
7151
|
+
hint: "a cycle of weights below one f32 ulp relaxed once at a distance above 2^24; scale the weights or shorten the distances"
|
|
7152
|
+
}
|
|
7153
|
+
);
|
|
7154
|
+
}
|
|
7155
|
+
const distOut = dest ?? new Float32Array(n);
|
|
7156
|
+
distOut.set(passed.dist);
|
|
7157
|
+
let reachedCount = 0;
|
|
7158
|
+
for (const d of distOut) {
|
|
7159
|
+
if (d !== Infinity) {
|
|
7160
|
+
reachedCount += 1;
|
|
7161
|
+
}
|
|
7162
|
+
}
|
|
7163
|
+
return {
|
|
7164
|
+
result: { dist: distOut, predArc: passed.predArc, reachedCount, hasNegativeCycle },
|
|
7165
|
+
rounds,
|
|
7166
|
+
retryExhaustedRounds
|
|
7167
|
+
};
|
|
7168
|
+
} finally {
|
|
7169
|
+
scope.dispose();
|
|
7170
|
+
}
|
|
7171
|
+
}
|
|
7172
|
+
async function bellmanFord(ctx, s, source, options) {
|
|
7173
|
+
return (await bellmanFordWithTuning(ctx, s, source, options, {})).result;
|
|
7174
|
+
}
|
|
7175
|
+
const ALGORITHM = "closenessCentrality";
|
|
7176
|
+
const SOURCES_PER_BATCH = 32;
|
|
7177
|
+
const PER_SOURCE_WORDS = 4 * SOURCES_PER_BATCH;
|
|
7178
|
+
const RING_SLOTS = 8 * MAX_LEVELS_PER_SUBMIT + 16;
|
|
7179
|
+
function reusingScratch(scope) {
|
|
7180
|
+
const held = /* @__PURE__ */ new Map();
|
|
7181
|
+
return {
|
|
7182
|
+
...scope,
|
|
7183
|
+
scratch: (byteLength, label) => {
|
|
7184
|
+
const key = `${label}/${byteLength}`;
|
|
7185
|
+
let buffer = held.get(key);
|
|
7186
|
+
if (buffer === void 0) {
|
|
7187
|
+
buffer = scope.scratch(byteLength, label);
|
|
7188
|
+
held.set(key, buffer);
|
|
7189
|
+
}
|
|
7190
|
+
return buffer;
|
|
7191
|
+
}
|
|
7192
|
+
};
|
|
7193
|
+
}
|
|
7194
|
+
async function weightedRoute(ctx, s, scores, options) {
|
|
7195
|
+
const n = s.nodeCount;
|
|
7196
|
+
for (let source = 0; source < n; source++) {
|
|
7197
|
+
if (options?.signal?.aborted) {
|
|
7198
|
+
throw aborted(ALGORITHM);
|
|
7199
|
+
}
|
|
7200
|
+
const { dist } = await sssp(ctx, s, source, { signal: options?.signal });
|
|
7201
|
+
let sum = 0;
|
|
7202
|
+
for (let v = 0; v < n; v++) {
|
|
7203
|
+
const d = dist[v];
|
|
7204
|
+
if (v !== source && d !== Infinity) {
|
|
7205
|
+
sum += d;
|
|
7206
|
+
}
|
|
7207
|
+
}
|
|
7208
|
+
scores[source] = sum === 0 ? 0 : 1 / sum;
|
|
7209
|
+
options?.onProgress?.(source + 1, n);
|
|
7210
|
+
}
|
|
7211
|
+
return { scores, iterations: n, converged: true, precision: "f32" };
|
|
7212
|
+
}
|
|
7213
|
+
async function sweepRoute(ctx, s, scores, levelsPerSubmit, options, tuning) {
|
|
7214
|
+
const n = s.nodeCount;
|
|
7215
|
+
if (n === 0) {
|
|
7216
|
+
return { scores, iterations: 0, converged: true, precision: "f32" };
|
|
7217
|
+
}
|
|
7218
|
+
const core = ctx.residency.core(s);
|
|
7219
|
+
assertWholeCore(core, s.arcCount, ctx.caps.limits.maxStorageBufferBindingSize, ALGORITHM);
|
|
7220
|
+
const scope = algorithmScope(ctx, ALGORITHM, RING_SLOTS);
|
|
7221
|
+
try {
|
|
7222
|
+
const wg = ctx.workgroupSize;
|
|
7223
|
+
const bytes = 4 * n;
|
|
7224
|
+
const bitsBase = Math.ceil(n / 64) * 64;
|
|
7225
|
+
const regionBytes = 4 * bitsBase;
|
|
7226
|
+
const bits = bindingOf(scope.scratch(4 * regionBytes, "bits"), 4 * regionBytes);
|
|
7227
|
+
const region = (index) => ({
|
|
7228
|
+
buffer: bits.buffer,
|
|
7229
|
+
offset: index * regionBytes,
|
|
7230
|
+
size: regionBytes,
|
|
7231
|
+
window: null
|
|
7232
|
+
});
|
|
7233
|
+
const flags = region(3);
|
|
7234
|
+
const frontierList = bindingOf(scope.scratch(bytes, "frontier-list"), bytes);
|
|
7235
|
+
const iota = bindingOf(scope.scratch(bytes, "iota"), bytes);
|
|
7236
|
+
const counters = bindingOf(
|
|
7237
|
+
scope.scratch(FRONTIER_COUNTERS.byteLength, "counters"),
|
|
7238
|
+
FRONTIER_COUNTERS.byteLength
|
|
7239
|
+
);
|
|
7240
|
+
const perSourceBytes = 4 * PER_SOURCE_WORDS;
|
|
7241
|
+
const perSource = bindingOf(scope.scratch(perSourceBytes, "per-source"), perSourceBytes);
|
|
7242
|
+
await ctx.allocator.check();
|
|
7243
|
+
const compact = await prepareCompact(reusingScratch(scope));
|
|
7244
|
+
const sweep = await ctx.pipelines.kernel(kernelSpec("closeness-sweep", graphOverrides(core, null)));
|
|
7245
|
+
const reduce = await ctx.pipelines.kernel(kernelSpec("closeness-reduce"));
|
|
7246
|
+
const fill = await ctx.pipelines.kernel(kernelSpec("fill"));
|
|
7247
|
+
const graph = graphBindings(core, null);
|
|
7248
|
+
const onePlan = plan1d(1, wg, ctx.caps);
|
|
7249
|
+
const regionPlan = plan1d(bitsBase, wg, ctx.caps);
|
|
7250
|
+
const sweepPlan = planGridStride(n, wg, ctx.caps);
|
|
7251
|
+
const recordFill = (pass, dst, count, mode) => {
|
|
7252
|
+
const params = scope.params(FILL_PARAMS, { count, value: 0, mode, pad0: 0 });
|
|
7253
|
+
fill.dispatch(pass, fill.bind({ dst, P: params.binding }), plan1d(count, wg, ctx.caps), [params.offset]);
|
|
7254
|
+
};
|
|
7255
|
+
const submit = (batch) => {
|
|
7256
|
+
scope.flush();
|
|
7257
|
+
return batch.submit();
|
|
7258
|
+
};
|
|
7259
|
+
const setup = new CommandBatch(ctx, `${ALGORITHM}/setup`);
|
|
7260
|
+
recordFill(setup.pass("fill"), iota, n, 1);
|
|
7261
|
+
setup.endPass();
|
|
7262
|
+
await submit(setup).readback;
|
|
7263
|
+
ctx.assertReady();
|
|
7264
|
+
let batches = 0;
|
|
7265
|
+
for (let batchStart = 0; batchStart < n; batchStart += SOURCES_PER_BATCH) {
|
|
7266
|
+
let level = 0;
|
|
7267
|
+
for (let first = true; ; first = false) {
|
|
7268
|
+
const batch = new CommandBatch(ctx, `${ALGORITHM}/levels`);
|
|
7269
|
+
const pass = batch.pass("closeness");
|
|
7270
|
+
if (first) {
|
|
7271
|
+
recordFill(pass, bits, 4 * bitsBase, 0);
|
|
7272
|
+
recordFill(pass, perSource, PER_SOURCE_WORDS, 0);
|
|
7273
|
+
const seed = scope.params(FRONTIER_PARAMS, { role: 1, n, bitsBase, source: batchStart });
|
|
7274
|
+
reduce.dispatch(pass, reduce.bind({ counters, perSource, bits, P: seed.binding }), onePlan, [
|
|
7275
|
+
seed.offset
|
|
7276
|
+
]);
|
|
7277
|
+
}
|
|
7278
|
+
const boundary = scope.params(FRONTIER_PARAMS, { role: 0, n, bitsBase });
|
|
7279
|
+
const boundBoundary = reduce.bind({ counters, perSource, bits, P: boundary.binding });
|
|
7280
|
+
const clear = scope.params(FILL_PARAMS, { count: bitsBase, value: 0, mode: 0, pad0: 0 });
|
|
7281
|
+
const boundClearNext = [region(2), region(1)].map((dst) => fill.bind({ dst, P: clear.binding }));
|
|
7282
|
+
const boundClearFlags = fill.bind({ dst: flags, P: clear.binding });
|
|
7283
|
+
const boundSweep = [0, 1].map((mode) => {
|
|
7284
|
+
const params = scope.params(FRONTIER_PARAMS, {
|
|
7285
|
+
wg,
|
|
7286
|
+
n,
|
|
7287
|
+
bitsBase,
|
|
7288
|
+
arcBase: 0,
|
|
7289
|
+
arcEnd: s.arcCount,
|
|
7290
|
+
mode,
|
|
7291
|
+
stride: sweepPlan.stride ?? wg
|
|
7292
|
+
});
|
|
7293
|
+
return {
|
|
7294
|
+
bound: sweep.bind({ ...graph, frontierList, counters, bits, perSource, P: params.binding }),
|
|
7295
|
+
offset: params.offset
|
|
7296
|
+
};
|
|
7297
|
+
});
|
|
7298
|
+
for (let k = 0; k < levelsPerSubmit; k++, level++) {
|
|
7299
|
+
const parity = level % 2;
|
|
7300
|
+
reduce.dispatch(pass, boundBoundary, onePlan, [boundary.offset]);
|
|
7301
|
+
compact.record(pass, {
|
|
7302
|
+
queue: iota,
|
|
7303
|
+
flags,
|
|
7304
|
+
count: n,
|
|
7305
|
+
out: frontierList,
|
|
7306
|
+
outCount: counters,
|
|
7307
|
+
outIndex: W.frontierCount
|
|
7308
|
+
});
|
|
7309
|
+
fill.dispatch(pass, boundClearNext[parity], regionPlan, [clear.offset]);
|
|
7310
|
+
fill.dispatch(pass, boundClearFlags, regionPlan, [clear.offset]);
|
|
7311
|
+
sweep.dispatch(pass, boundSweep[parity].bound, sweepPlan, [boundSweep[parity].offset]);
|
|
7312
|
+
}
|
|
7313
|
+
batch.endPass();
|
|
7314
|
+
const doneRequest = batch.readback(counters.buffer, counters.offset + 4 * W.done, 4);
|
|
7315
|
+
const blockRequest = batch.readback(perSource.buffer, perSource.offset, perSourceBytes);
|
|
7316
|
+
const submitted = submit(batch);
|
|
7317
|
+
const back = await submitted.readback;
|
|
7318
|
+
ctx.assertReady();
|
|
7319
|
+
if (options?.signal?.aborted) {
|
|
7320
|
+
throw aborted(ALGORITHM, submitted.id);
|
|
7321
|
+
}
|
|
7322
|
+
if (new Uint32Array(back, doneRequest.offset, 1)[0] !== 0) {
|
|
7323
|
+
const block = new Uint32Array(back, blockRequest.offset, PER_SOURCE_WORDS);
|
|
7324
|
+
const count = Math.min(SOURCES_PER_BATCH, n - batchStart);
|
|
7325
|
+
for (let i = 0; i < count; i++) {
|
|
7326
|
+
const sum = block[3 * SOURCES_PER_BATCH + i] * 2 ** 32 + block[2 * SOURCES_PER_BATCH + i];
|
|
7327
|
+
scores[batchStart + i] = sum === 0 ? 0 : 1 / sum;
|
|
7328
|
+
}
|
|
7329
|
+
tuning.onBatch?.(batchStart, block.slice());
|
|
7330
|
+
break;
|
|
7331
|
+
}
|
|
7332
|
+
if (level > n + 3) {
|
|
7333
|
+
throw new WebGpuGraphError(
|
|
7334
|
+
"E_VALIDATION",
|
|
7335
|
+
`${ALGORITHM}: the done flag never rose in ${level} levels of the batch at ${batchStart}`,
|
|
7336
|
+
{ label: ALGORITHM, message: `the done flag never rose in ${level} levels` }
|
|
7337
|
+
);
|
|
7338
|
+
}
|
|
7339
|
+
}
|
|
7340
|
+
batches += 1;
|
|
7341
|
+
options?.onProgress?.(Math.min(batchStart + SOURCES_PER_BATCH, n), n);
|
|
7342
|
+
}
|
|
7343
|
+
return { scores, iterations: batches, converged: true, precision: "f32" };
|
|
7344
|
+
} finally {
|
|
7345
|
+
scope.dispose();
|
|
4443
7346
|
}
|
|
4444
|
-
const run2 = await runPowerIteration(ctx, r.n, {
|
|
4445
|
-
normMode: 4,
|
|
4446
|
-
adjacency: reverseOf(ctx, s),
|
|
4447
|
-
alternate: null,
|
|
4448
|
-
alpha,
|
|
4449
|
-
beta,
|
|
4450
|
-
uniformP: 1,
|
|
4451
|
-
maxIterations: r.maxIterations,
|
|
4452
|
-
tolerance: r.tolerance,
|
|
4453
|
-
weights: r.weights,
|
|
4454
|
-
label: algorithm,
|
|
4455
|
-
signal: options?.signal,
|
|
4456
|
-
onProgress: options?.onProgress
|
|
4457
|
-
});
|
|
4458
|
-
return scoresOf(run2, r.dest, "l2");
|
|
4459
7347
|
}
|
|
4460
|
-
async function
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
const
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
7348
|
+
async function closenessWithTuning(ctx, s, options, tuning) {
|
|
7349
|
+
ctx.assertReady();
|
|
7350
|
+
await assertDeviceComputes(ctx);
|
|
7351
|
+
for (const key of ["maxIterations", "tolerance"]) {
|
|
7352
|
+
if (options?.[key] !== void 0) {
|
|
7353
|
+
throw new WebGpuGraphError("E_UNSUPPORTED", `${ALGORITHM}: ${key} has no meaning for an exact traversal`, {
|
|
7354
|
+
option: key,
|
|
7355
|
+
hint: "closeness is an exact traversal; the option has no meaning here"
|
|
7356
|
+
});
|
|
7357
|
+
}
|
|
4467
7358
|
}
|
|
4468
|
-
const
|
|
4469
|
-
const
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
const
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
7359
|
+
const n = s.nodeCount;
|
|
7360
|
+
const levelsPerSubmit = tuning.levelsPerSubmit ?? MAX_LEVELS_PER_SUBMIT;
|
|
7361
|
+
if (!Number.isInteger(levelsPerSubmit) || levelsPerSubmit < 1 || levelsPerSubmit > MAX_LEVELS_PER_SUBMIT) {
|
|
7362
|
+
throw new WebGpuGraphError(
|
|
7363
|
+
"E_INVALID_ARGUMENT",
|
|
7364
|
+
`${ALGORITHM}: levelsPerSubmit must be an integer in [1, ${MAX_LEVELS_PER_SUBMIT}]`,
|
|
7365
|
+
{
|
|
7366
|
+
argument: "levelsPerSubmit",
|
|
7367
|
+
value: levelsPerSubmit,
|
|
7368
|
+
expected: `an integer in [1, ${MAX_LEVELS_PER_SUBMIT}]`
|
|
7369
|
+
}
|
|
7370
|
+
);
|
|
7371
|
+
}
|
|
7372
|
+
const scores = checkDest(ALGORITHM, options?.dest, n) ?? new Float32Array(n);
|
|
7373
|
+
const weighted = options?.weighted ?? s.flags.weighted;
|
|
7374
|
+
if (options?.signal?.aborted) {
|
|
7375
|
+
throw aborted(ALGORITHM);
|
|
7376
|
+
}
|
|
7377
|
+
if (weighted && s.weights !== null && !s.flags.allWeightsOne) {
|
|
7378
|
+
if (!s.flags.nonNegativeWeights) {
|
|
7379
|
+
throw new WebGpuGraphError(
|
|
7380
|
+
"E_UNSUPPORTED",
|
|
7381
|
+
`${ALGORITHM}: a negative weight has no shortest-path distance to sum`,
|
|
7382
|
+
{
|
|
7383
|
+
feature: "closenessCentrality.negativeWeights",
|
|
7384
|
+
hint: "pass weighted: false to ignore the column"
|
|
7385
|
+
}
|
|
7386
|
+
);
|
|
4488
7387
|
}
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
alternate: reverse,
|
|
4494
|
-
label: `${algorithm}/authority-seeded`,
|
|
4495
|
-
onProgress: onProgress === void 0 ? void 0 : (done) => {
|
|
4496
|
-
onProgress(r.maxIterations + done, total);
|
|
7388
|
+
if (!s.flags.finiteWeights) {
|
|
7389
|
+
throw new WebGpuGraphError("E_UNSUPPORTED", `${ALGORITHM}: a NaN or infinite weight has no shortest path`, {
|
|
7390
|
+
feature: "closenessCentrality.nonFiniteWeights"
|
|
7391
|
+
});
|
|
4497
7392
|
}
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
precision: "f32"
|
|
4505
|
-
};
|
|
7393
|
+
return weightedRoute(ctx, s, scores, options);
|
|
7394
|
+
}
|
|
7395
|
+
return sweepRoute(ctx, s, scores, levelsPerSubmit, options, tuning);
|
|
7396
|
+
}
|
|
7397
|
+
function closenessCentrality(ctx, s, options) {
|
|
7398
|
+
return closenessWithTuning(ctx, s, options, {});
|
|
4506
7399
|
}
|
|
4507
7400
|
const U32_MAX = 4294967295;
|
|
4508
7401
|
async function prepareHistogram(scope) {
|
|
@@ -4646,128 +7539,16 @@ class CountingSortPlannerImpl {
|
|
|
4646
7539
|
this.dispatches += 1;
|
|
4647
7540
|
}
|
|
4648
7541
|
}
|
|
4649
|
-
|
|
4650
|
-
function radixHistBytes(count, wg) {
|
|
4651
|
-
return 4 * RADIX_BINS * Math.ceil(count / wg);
|
|
4652
|
-
}
|
|
4653
|
-
async function prepareRadixSort(scope) {
|
|
4654
|
-
const hist = await scope.pipelines.kernel(kernelSpec("radix-hist"));
|
|
4655
|
-
const scatter = await scope.pipelines.kernel(kernelSpec("radix-scatter"));
|
|
4656
|
-
const scan = await prepareScan(scope);
|
|
4657
|
-
return new RadixSortPlannerImpl(scope, hist, scatter, scan);
|
|
4658
|
-
}
|
|
4659
|
-
function checkBinding(argument, binding, bytes) {
|
|
4660
|
-
if (binding.size < bytes) {
|
|
4661
|
-
throw new WebGpuGraphError("E_INVALID_ARGUMENT", `radixSort: ${argument} is smaller than ${bytes} bytes`, {
|
|
4662
|
-
argument,
|
|
4663
|
-
value: binding.size,
|
|
4664
|
-
expected: bytes
|
|
4665
|
-
});
|
|
4666
|
-
}
|
|
4667
|
-
}
|
|
4668
|
-
function checkRecordArguments(keys, vals, count, bits, scratch, wg) {
|
|
4669
|
-
if (bits !== 8 && bits !== 16 && bits !== 24 && bits !== 32) {
|
|
4670
|
-
throw new WebGpuGraphError("E_INVALID_ARGUMENT", "radixSort: bits must be 8, 16, 24 or 32", {
|
|
4671
|
-
argument: "bits",
|
|
4672
|
-
value: bits,
|
|
4673
|
-
expected: [8, 16, 24, 32]
|
|
4674
|
-
});
|
|
4675
|
-
}
|
|
4676
|
-
if (!Number.isSafeInteger(count) || count < 0 || count > U32_MAX$2) {
|
|
4677
|
-
throw new WebGpuGraphError("E_INVALID_ARGUMENT", "radixSort: count must be a non-negative integer below 2^32", {
|
|
4678
|
-
argument: "count",
|
|
4679
|
-
value: count
|
|
4680
|
-
});
|
|
4681
|
-
}
|
|
4682
|
-
const pairBytes = 4 * count;
|
|
4683
|
-
checkBinding("keys", keys, pairBytes);
|
|
4684
|
-
checkBinding("vals", vals, pairBytes);
|
|
4685
|
-
checkBinding("scratch.keys", scratch.keys, pairBytes);
|
|
4686
|
-
checkBinding("scratch.vals", scratch.vals, pairBytes);
|
|
4687
|
-
const tableBytes = radixHistBytes(count, wg);
|
|
4688
|
-
checkBinding("scratch.hist", scratch.hist, tableBytes);
|
|
4689
|
-
checkBinding("scratch.offsets", scratch.offsets, tableBytes);
|
|
4690
|
-
}
|
|
4691
|
-
class RadixSortPlannerImpl {
|
|
4692
|
-
/**
|
|
4693
|
-
* Wraps the resolved kernels; use prepareRadixSort().
|
|
4694
|
-
* @param scope - the caller's scope
|
|
4695
|
-
* @param hist - the `radix-hist` kernel
|
|
4696
|
-
* @param scatter - the `radix-scatter` kernel
|
|
4697
|
-
* @param scan - the scan planner of the same scope
|
|
4698
|
-
*/
|
|
4699
|
-
constructor(scope, hist, scatter, scan) {
|
|
4700
|
-
this.dispatches = 0;
|
|
4701
|
-
this.scope = scope;
|
|
4702
|
-
this.hist = hist;
|
|
4703
|
-
this.scatter = scatter;
|
|
4704
|
-
this.scan = scan;
|
|
4705
|
-
}
|
|
4706
|
-
/**
|
|
4707
|
-
* Dispatches the last record() issued.
|
|
4708
|
-
* @returns the count
|
|
4709
|
-
*/
|
|
4710
|
-
get lastDispatches() {
|
|
4711
|
-
return this.dispatches;
|
|
4712
|
-
}
|
|
4713
|
-
/**
|
|
4714
|
-
* Records the passes into the pass (see the interface).
|
|
4715
|
-
* @param pass - the compute pass
|
|
4716
|
-
* @param keys - the keys
|
|
4717
|
-
* @param vals - the values
|
|
4718
|
-
* @param count - the pair count
|
|
4719
|
-
* @param bits - the key width
|
|
4720
|
-
* @param scratch - the second pair, the histogram table and its scanned twin
|
|
4721
|
-
* @returns the pair the result lives in
|
|
4722
|
-
*/
|
|
4723
|
-
record(pass, keys, vals, count, bits, scratch) {
|
|
4724
|
-
const wg = this.scope.workgroupSize;
|
|
4725
|
-
checkRecordArguments(keys, vals, count, bits, scratch, wg);
|
|
4726
|
-
if (count === 0) {
|
|
4727
|
-
this.dispatches = 0;
|
|
4728
|
-
return { keys, vals };
|
|
4729
|
-
}
|
|
4730
|
-
const groups = Math.ceil(count / wg);
|
|
4731
|
-
const plan = plan1d(count, wg, this.scope.caps);
|
|
4732
|
-
const tableWords = RADIX_BINS * groups;
|
|
4733
|
-
const tableBytes = 4 * tableWords;
|
|
4734
|
-
const histTable = { ...scratch.hist, size: tableBytes };
|
|
4735
|
-
const offsets = { ...scratch.offsets, size: tableBytes };
|
|
4736
|
-
let src = { keys, vals };
|
|
4737
|
-
let dst = { keys: scratch.keys, vals: scratch.vals };
|
|
4738
|
-
let dispatches = 0;
|
|
4739
|
-
const passes = bits / RADIX_DIGIT_BITS;
|
|
4740
|
-
for (let p = 0; p < passes; p++) {
|
|
4741
|
-
const params = this.scope.params(RADIX_PARAMS, { count, shift: RADIX_DIGIT_BITS * p, groups, pad0: 0 });
|
|
4742
|
-
const histBound = this.hist.bind({ keys: src.keys, hist: histTable, P: params.binding });
|
|
4743
|
-
this.hist.dispatch(pass, histBound, plan, [params.offset]);
|
|
4744
|
-
this.scan.record(pass, histTable, tableWords, offsets);
|
|
4745
|
-
const scatterBound = this.scatter.bind({
|
|
4746
|
-
keys: src.keys,
|
|
4747
|
-
vals: src.vals,
|
|
4748
|
-
offsets,
|
|
4749
|
-
keysOut: dst.keys,
|
|
4750
|
-
valsOut: dst.vals,
|
|
4751
|
-
P: params.binding
|
|
4752
|
-
});
|
|
4753
|
-
this.scatter.dispatch(pass, scatterBound, plan, [params.offset]);
|
|
4754
|
-
dispatches += 2 + this.scan.lastDispatches;
|
|
4755
|
-
[src, dst] = [dst, src];
|
|
4756
|
-
}
|
|
4757
|
-
this.dispatches = dispatches;
|
|
4758
|
-
return src;
|
|
4759
|
-
}
|
|
4760
|
-
}
|
|
4761
|
-
function nextPow2(x2) {
|
|
7542
|
+
function nextPow2(x) {
|
|
4762
7543
|
let p = 1;
|
|
4763
|
-
while (p <
|
|
7544
|
+
while (p < x) {
|
|
4764
7545
|
p *= 2;
|
|
4765
7546
|
}
|
|
4766
7547
|
return p;
|
|
4767
7548
|
}
|
|
4768
|
-
function floorPow2$1(
|
|
7549
|
+
function floorPow2$1(x) {
|
|
4769
7550
|
let p = 1;
|
|
4770
|
-
while (p * 2 <=
|
|
7551
|
+
while (p * 2 <= x) {
|
|
4771
7552
|
p *= 2;
|
|
4772
7553
|
}
|
|
4773
7554
|
return p;
|
|
@@ -4951,15 +7732,15 @@ function resolveCenter(center) {
|
|
|
4951
7732
|
return out;
|
|
4952
7733
|
}
|
|
4953
7734
|
for (let axis = 0; axis < 3 && axis < center.length; axis++) {
|
|
4954
|
-
const
|
|
4955
|
-
if (!Number.isFinite(
|
|
7735
|
+
const v = center[axis];
|
|
7736
|
+
if (!Number.isFinite(v)) {
|
|
4956
7737
|
throw new WebGpuGraphError("E_INVALID_ARGUMENT", `center[${axis}] is not finite`, {
|
|
4957
7738
|
argument: "center",
|
|
4958
|
-
value:
|
|
7739
|
+
value: v,
|
|
4959
7740
|
expected: "finite components"
|
|
4960
7741
|
});
|
|
4961
7742
|
}
|
|
4962
|
-
out[axis] =
|
|
7743
|
+
out[axis] = v;
|
|
4963
7744
|
}
|
|
4964
7745
|
return out;
|
|
4965
7746
|
}
|
|
@@ -4998,13 +7779,13 @@ function seedPositions(s, positions, seed, dim, scale, center, range) {
|
|
|
4998
7779
|
for (let i = 0; i < n; i++) {
|
|
4999
7780
|
let finite2 = true;
|
|
5000
7781
|
for (let axis = 0; axis < dim; axis++) {
|
|
5001
|
-
const
|
|
5002
|
-
if (Number.isFinite(
|
|
5003
|
-
if (
|
|
5004
|
-
lo[axis] =
|
|
7782
|
+
const v = positions[3 * i + axis];
|
|
7783
|
+
if (Number.isFinite(v)) {
|
|
7784
|
+
if (v < lo[axis]) {
|
|
7785
|
+
lo[axis] = v;
|
|
5005
7786
|
}
|
|
5006
|
-
if (
|
|
5007
|
-
hi[axis] =
|
|
7787
|
+
if (v > hi[axis]) {
|
|
7788
|
+
hi[axis] = v;
|
|
5008
7789
|
}
|
|
5009
7790
|
} else {
|
|
5010
7791
|
finite2 = false;
|
|
@@ -5046,8 +7827,8 @@ function seedPositions(s, positions, seed, dim, scale, center, range) {
|
|
|
5046
7827
|
const at = 3 * i + axis;
|
|
5047
7828
|
if (!Number.isFinite(positions[at])) {
|
|
5048
7829
|
const u = rng.next();
|
|
5049
|
-
const
|
|
5050
|
-
positions[at] =
|
|
7830
|
+
const v = boxLo[axis] + u * (boxHi[axis] - boxLo[axis]);
|
|
7831
|
+
positions[at] = v * scale + c[axis];
|
|
5051
7832
|
}
|
|
5052
7833
|
}
|
|
5053
7834
|
if (dim === 2) {
|
|
@@ -5188,11 +7969,11 @@ function centerOf(options) {
|
|
|
5188
7969
|
return out;
|
|
5189
7970
|
}
|
|
5190
7971
|
for (let axis = 0; axis < 3 && axis < center.length; axis++) {
|
|
5191
|
-
const
|
|
5192
|
-
if (!Number.isFinite(
|
|
5193
|
-
throw invalidArgument("center",
|
|
7972
|
+
const v = center[axis];
|
|
7973
|
+
if (!Number.isFinite(v)) {
|
|
7974
|
+
throw invalidArgument("center", v, "finite components", `center[${axis}] is not finite`);
|
|
5194
7975
|
}
|
|
5195
|
-
out[axis] =
|
|
7976
|
+
out[axis] = v;
|
|
5196
7977
|
}
|
|
5197
7978
|
return out;
|
|
5198
7979
|
}
|
|
@@ -5840,12 +8621,12 @@ class ForceSimulation {
|
|
|
5840
8621
|
);
|
|
5841
8622
|
}
|
|
5842
8623
|
let unpinned = false;
|
|
5843
|
-
for (let
|
|
5844
|
-
if (this.fixedWords[
|
|
8624
|
+
for (let w = 0; w < words && !unpinned; w++) {
|
|
8625
|
+
if (this.fixedWords[w] === mask[w]) {
|
|
5845
8626
|
continue;
|
|
5846
8627
|
}
|
|
5847
|
-
const last = Math.min(n,
|
|
5848
|
-
for (let i =
|
|
8628
|
+
const last = Math.min(n, w * 32 + 32);
|
|
8629
|
+
for (let i = w * 32; i < last; i++) {
|
|
5849
8630
|
if (maskTest(this.fixedWords, i) && !maskTest(mask, i)) {
|
|
5850
8631
|
unpinned = true;
|
|
5851
8632
|
break;
|
|
@@ -5867,7 +8648,7 @@ class ForceSimulation {
|
|
|
5867
8648
|
* @param y - scene y
|
|
5868
8649
|
* @param z - scene z (ignored on the device in 2D)
|
|
5869
8650
|
*/
|
|
5870
|
-
setPosition(index,
|
|
8651
|
+
setPosition(index, x, y, z) {
|
|
5871
8652
|
this.assertLoaded();
|
|
5872
8653
|
const { n } = this;
|
|
5873
8654
|
if (!Number.isInteger(index) || index < 0 || index >= n) {
|
|
@@ -5878,10 +8659,10 @@ class ForceSimulation {
|
|
|
5878
8659
|
`setPosition(${index}): index out of range`
|
|
5879
8660
|
);
|
|
5880
8661
|
}
|
|
5881
|
-
if (!Number.isFinite(
|
|
8662
|
+
if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(z)) {
|
|
5882
8663
|
throw invalidArgument(
|
|
5883
8664
|
"position",
|
|
5884
|
-
[
|
|
8665
|
+
[x, y, z],
|
|
5885
8666
|
"finite coordinates",
|
|
5886
8667
|
"setPosition: coordinates must be finite"
|
|
5887
8668
|
);
|
|
@@ -5890,13 +8671,13 @@ class ForceSimulation {
|
|
|
5890
8671
|
if (owner === null || buffers === null) {
|
|
5891
8672
|
throw new WebGpuGraphError("E_NOT_LOADED", `${this.label}: load() first`, { state: this.stateValue });
|
|
5892
8673
|
}
|
|
5893
|
-
owner[3 * index] =
|
|
5894
|
-
owner[3 * index + 1] =
|
|
8674
|
+
owner[3 * index] = x;
|
|
8675
|
+
owner[3 * index + 1] = y;
|
|
5895
8676
|
owner[3 * index + 2] = z;
|
|
5896
8677
|
const [cx, cy, cz] = this.center;
|
|
5897
8678
|
const layout = new Float32Array([
|
|
5898
|
-
(
|
|
5899
|
-
(
|
|
8679
|
+
(x - cx) / this.scale,
|
|
8680
|
+
(y - cy) / this.scale,
|
|
5900
8681
|
this.dimValue === 2 ? 0 : (z - cz) / this.scale
|
|
5901
8682
|
]);
|
|
5902
8683
|
this.ctx.device.queue.writeBuffer(buffers.positions, 16 * index, layout);
|
|
@@ -6517,17 +9298,17 @@ class ForceSimulation {
|
|
|
6517
9298
|
const lo = [Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY];
|
|
6518
9299
|
const hi = [Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY];
|
|
6519
9300
|
for (let i = 0; i < n; i++) {
|
|
6520
|
-
const
|
|
6521
|
-
const
|
|
9301
|
+
const x = packed[4 * i];
|
|
9302
|
+
const y = packed[4 * i + 1];
|
|
6522
9303
|
const z = packed[4 * i + 2];
|
|
6523
|
-
sx +=
|
|
6524
|
-
sy +=
|
|
9304
|
+
sx += x;
|
|
9305
|
+
sy += y;
|
|
6525
9306
|
sz += z;
|
|
6526
|
-
lo[0] = Math.min(lo[0],
|
|
6527
|
-
lo[1] = Math.min(lo[1],
|
|
9307
|
+
lo[0] = Math.min(lo[0], x);
|
|
9308
|
+
lo[1] = Math.min(lo[1], y);
|
|
6528
9309
|
lo[2] = Math.min(lo[2], z);
|
|
6529
|
-
hi[0] = Math.max(hi[0],
|
|
6530
|
-
hi[1] = Math.max(hi[1],
|
|
9310
|
+
hi[0] = Math.max(hi[0], x);
|
|
9311
|
+
hi[1] = Math.max(hi[1], y);
|
|
6531
9312
|
hi[2] = Math.max(hi[2], z);
|
|
6532
9313
|
}
|
|
6533
9314
|
const mx = sx / n;
|
|
@@ -6788,14 +9569,14 @@ function invalid$1(argument, value, expected, message) {
|
|
|
6788
9569
|
}
|
|
6789
9570
|
function checkMass(mass, source) {
|
|
6790
9571
|
for (let i = 0; i < mass.length; i++) {
|
|
6791
|
-
const
|
|
6792
|
-
if (!Number.isFinite(
|
|
9572
|
+
const v = mass[i];
|
|
9573
|
+
if (!Number.isFinite(v) || v <= 0) {
|
|
6793
9574
|
throw new WebGpuGraphError(
|
|
6794
9575
|
"E_INVALID_ARGUMENT",
|
|
6795
|
-
`${source}: mass[${i}] = ${
|
|
9576
|
+
`${source}: mass[${i}] = ${v} is not a finite number > 0`,
|
|
6796
9577
|
{
|
|
6797
9578
|
argument: "nodeMass",
|
|
6798
|
-
value:
|
|
9579
|
+
value: v,
|
|
6799
9580
|
index: i,
|
|
6800
9581
|
expected: "finite masses > 0"
|
|
6801
9582
|
}
|
|
@@ -7061,13 +9842,13 @@ function pickCenter(given, fallback) {
|
|
|
7061
9842
|
if (length !== 2 && length !== 3) {
|
|
7062
9843
|
throw invalid("center", given, expected);
|
|
7063
9844
|
}
|
|
7064
|
-
const
|
|
7065
|
-
const
|
|
9845
|
+
const x = given[0];
|
|
9846
|
+
const y = given[1];
|
|
7066
9847
|
const z = length === 3 ? given[2] : 0;
|
|
7067
|
-
if (typeof
|
|
9848
|
+
if (typeof x !== "number" || typeof y !== "number" || typeof z !== "number" || !Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(z)) {
|
|
7068
9849
|
throw invalid("center", given, expected);
|
|
7069
9850
|
}
|
|
7070
|
-
return [
|
|
9851
|
+
return [x, y, z];
|
|
7071
9852
|
}
|
|
7072
9853
|
function pickSeed(given, fallback) {
|
|
7073
9854
|
if (given === void 0) {
|
|
@@ -7669,9 +10450,9 @@ function resolveForceAtlas2Options(options, previous) {
|
|
|
7669
10450
|
}
|
|
7670
10451
|
const resolved = {
|
|
7671
10452
|
maxIter: pickNumber("maxIter", o.maxIter, base.maxIter, isPositiveInteger, "an integer >= 1"),
|
|
7672
|
-
jitterTolerance: pickNumber("jitterTolerance", o.jitterTolerance, base.jitterTolerance, (
|
|
7673
|
-
scalingRatio: pickNumber("scalingRatio", o.scalingRatio, base.scalingRatio, (
|
|
7674
|
-
gravity: pickNumber("gravity", o.gravity, base.gravity, (
|
|
10453
|
+
jitterTolerance: pickNumber("jitterTolerance", o.jitterTolerance, base.jitterTolerance, (v) => v > 0, "> 0"),
|
|
10454
|
+
scalingRatio: pickNumber("scalingRatio", o.scalingRatio, base.scalingRatio, (v) => v > 0, "> 0"),
|
|
10455
|
+
gravity: pickNumber("gravity", o.gravity, base.gravity, (v) => v >= 0, ">= 0"),
|
|
7675
10456
|
strongGravity: pickBoolean("strongGravity", o.strongGravity, base.strongGravity),
|
|
7676
10457
|
distributedAction: pickBoolean("distributedAction", o.distributedAction, base.distributedAction),
|
|
7677
10458
|
linlog: pickBoolean("linlog", o.linlog, base.linlog),
|
|
@@ -7680,10 +10461,10 @@ function resolveForceAtlas2Options(options, previous) {
|
|
|
7680
10461
|
weight: o.weight === void 0 ? base.weight : o.weight,
|
|
7681
10462
|
dissuadeHubs: pickBoolean("dissuadeHubs", o.dissuadeHubs, base.dissuadeHubs),
|
|
7682
10463
|
dim: pickDim(o.dim, base.dim),
|
|
7683
|
-
scale: pickNumber("scale", o.scale, base.scale, (
|
|
10464
|
+
scale: pickNumber("scale", o.scale, base.scale, (v) => v > 0, "> 0"),
|
|
7684
10465
|
center: pickCenter(o.center, base.center),
|
|
7685
10466
|
seed: pickSeed(o.seed, base.seed),
|
|
7686
|
-
settleThreshold: pickNumber("settleThreshold", o.settleThreshold, base.settleThreshold, (
|
|
10467
|
+
settleThreshold: pickNumber("settleThreshold", o.settleThreshold, base.settleThreshold, (v) => v >= 0, ">= 0"),
|
|
7687
10468
|
settleWindow: pickNumber(
|
|
7688
10469
|
"settleWindow",
|
|
7689
10470
|
o.settleWindow,
|
|
@@ -7695,7 +10476,7 @@ function resolveForceAtlas2Options(options, previous) {
|
|
|
7695
10476
|
"iterationsPerStep",
|
|
7696
10477
|
o.iterationsPerStep,
|
|
7697
10478
|
base.iterationsPerStep,
|
|
7698
|
-
(
|
|
10479
|
+
(v) => isPositiveInteger(v) && v <= MAX_ITERATIONS_PER_STEP,
|
|
7699
10480
|
`an integer in [1, ${MAX_ITERATIONS_PER_STEP}]`
|
|
7700
10481
|
),
|
|
7701
10482
|
maxInFlight: pickNumber("maxInFlight", o.maxInFlight, base.maxInFlight, isPositiveInteger, "an integer >= 1")
|
|
@@ -7725,7 +10506,7 @@ function resolveLayoutTuning(tuning) {
|
|
|
7725
10506
|
"nearMax",
|
|
7726
10507
|
t.nearMax,
|
|
7727
10508
|
LAYOUT_TUNING_DEFAULTS.nearMax,
|
|
7728
|
-
(
|
|
10509
|
+
(v) => isPositiveInteger(v) && v >= 2,
|
|
7729
10510
|
"an integer >= 2"
|
|
7730
10511
|
),
|
|
7731
10512
|
deterministic: pickBoolean("deterministic", t.deterministic, LAYOUT_TUNING_DEFAULTS.deterministic),
|
|
@@ -7747,7 +10528,7 @@ function resolveLayoutTuning(tuning) {
|
|
|
7747
10528
|
"extentFactor",
|
|
7748
10529
|
t.extentFactor,
|
|
7749
10530
|
LAYOUT_TUNING_DEFAULTS.extentFactor,
|
|
7750
|
-
(
|
|
10531
|
+
(v) => v > 0,
|
|
7751
10532
|
"> 0"
|
|
7752
10533
|
),
|
|
7753
10534
|
compat
|
|
@@ -8374,7 +11155,7 @@ function resolveK(given, fallback) {
|
|
|
8374
11155
|
if (given === null || given === 0 || Number.isNaN(given)) {
|
|
8375
11156
|
return null;
|
|
8376
11157
|
}
|
|
8377
|
-
return pickNumber("k", given, 1, (
|
|
11158
|
+
return pickNumber("k", given, 1, (v) => v > 0, "> 0 or null");
|
|
8378
11159
|
}
|
|
8379
11160
|
function pickCooling(given, fallback) {
|
|
8380
11161
|
if (given === void 0) {
|
|
@@ -8407,16 +11188,16 @@ function resolveFruchtermanReingoldOptions(options, previous) {
|
|
|
8407
11188
|
"iterations",
|
|
8408
11189
|
o.iterations,
|
|
8409
11190
|
iterationsFallback,
|
|
8410
|
-
(
|
|
11191
|
+
(v) => Number.isInteger(v) && v >= 0,
|
|
8411
11192
|
"an integer >= 0"
|
|
8412
11193
|
),
|
|
8413
11194
|
cooling,
|
|
8414
11195
|
fixed,
|
|
8415
11196
|
dim: pickDim(o.dim, base.dim),
|
|
8416
|
-
scale: pickNumber("scale", o.scale, base.scale, (
|
|
11197
|
+
scale: pickNumber("scale", o.scale, base.scale, (v) => v > 0, "> 0"),
|
|
8417
11198
|
center: pickCenter(o.center, base.center),
|
|
8418
11199
|
seed: pickSeed(o.seed, base.seed),
|
|
8419
|
-
settleThreshold: pickNumber("settleThreshold", o.settleThreshold, base.settleThreshold, (
|
|
11200
|
+
settleThreshold: pickNumber("settleThreshold", o.settleThreshold, base.settleThreshold, (v) => v >= 0, ">= 0"),
|
|
8420
11201
|
settleWindow: pickNumber(
|
|
8421
11202
|
"settleWindow",
|
|
8422
11203
|
o.settleWindow,
|
|
@@ -8428,7 +11209,7 @@ function resolveFruchtermanReingoldOptions(options, previous) {
|
|
|
8428
11209
|
"iterationsPerStep",
|
|
8429
11210
|
o.iterationsPerStep,
|
|
8430
11211
|
base.iterationsPerStep,
|
|
8431
|
-
(
|
|
11212
|
+
(v) => isPositiveInteger(v) && v <= MAX_ITERATIONS_PER_STEP,
|
|
8432
11213
|
`an integer in [1, ${MAX_ITERATIONS_PER_STEP}]`
|
|
8433
11214
|
),
|
|
8434
11215
|
maxInFlight: pickNumber("maxInFlight", o.maxInFlight, base.maxInFlight, isPositiveInteger, "an integer >= 1")
|
|
@@ -9058,22 +11839,22 @@ function resolveSpringElectricalOptions(options, previous) {
|
|
|
9058
11839
|
);
|
|
9059
11840
|
}
|
|
9060
11841
|
const resolved = {
|
|
9061
|
-
springLength: pickNumber("springLength", o.springLength, base.springLength, (
|
|
11842
|
+
springLength: pickNumber("springLength", o.springLength, base.springLength, (v) => v > 0, "> 0"),
|
|
9062
11843
|
springCoefficient: pickNullable(
|
|
9063
11844
|
"springCoefficient",
|
|
9064
11845
|
o.springCoefficient,
|
|
9065
11846
|
base.springCoefficient,
|
|
9066
|
-
(
|
|
11847
|
+
(v) => v > 0,
|
|
9067
11848
|
"> 0"
|
|
9068
11849
|
),
|
|
9069
11850
|
gravity: pickNullable("gravity", o.gravity, base.gravity, () => true, "a finite number (negative repels)"),
|
|
9070
|
-
dragCoefficient: pickNumber("dragCoefficient", o.dragCoefficient, base.dragCoefficient, (
|
|
9071
|
-
timeStep: pickNumber("timeStep", o.timeStep, base.timeStep, (
|
|
11851
|
+
dragCoefficient: pickNumber("dragCoefficient", o.dragCoefficient, base.dragCoefficient, (v) => v >= 0, ">= 0"),
|
|
11852
|
+
timeStep: pickNumber("timeStep", o.timeStep, base.timeStep, (v) => v > 0, "> 0"),
|
|
9072
11853
|
dim: pickDim(o.dim, base.dim),
|
|
9073
|
-
scale: pickNumber("scale", o.scale, base.scale, (
|
|
11854
|
+
scale: pickNumber("scale", o.scale, base.scale, (v) => v > 0, "> 0"),
|
|
9074
11855
|
center: pickCenter(o.center, base.center),
|
|
9075
11856
|
seed: pickSeed(o.seed, base.seed),
|
|
9076
|
-
settleThreshold: pickNumber("settleThreshold", o.settleThreshold, base.settleThreshold, (
|
|
11857
|
+
settleThreshold: pickNumber("settleThreshold", o.settleThreshold, base.settleThreshold, (v) => v >= 0, ">= 0"),
|
|
9077
11858
|
settleWindow: pickNumber(
|
|
9078
11859
|
"settleWindow",
|
|
9079
11860
|
o.settleWindow,
|
|
@@ -9085,7 +11866,7 @@ function resolveSpringElectricalOptions(options, previous) {
|
|
|
9085
11866
|
"iterationsPerStep",
|
|
9086
11867
|
o.iterationsPerStep,
|
|
9087
11868
|
base.iterationsPerStep,
|
|
9088
|
-
(
|
|
11869
|
+
(v) => isPositiveInteger(v) && v <= MAX_ITERATIONS_PER_STEP,
|
|
9089
11870
|
`an integer in [1, ${MAX_ITERATIONS_PER_STEP}]`
|
|
9090
11871
|
),
|
|
9091
11872
|
maxInFlight: pickNumber("maxInFlight", o.maxInFlight, base.maxInFlight, isPositiveInteger, "an integer >= 1")
|
|
@@ -9718,6 +12499,51 @@ function createAccelerator(ctx, options) {
|
|
|
9718
12499
|
ctx.assertReady();
|
|
9719
12500
|
return await connectedComponents(ctx, gs, o);
|
|
9720
12501
|
},
|
|
12502
|
+
/**
|
|
12503
|
+
* Breadth-first search on the device (spec 8.4; P8-T13). The result is bitwise reproducible (P8 PD-14).
|
|
12504
|
+
* @param gs - the snapshot
|
|
12505
|
+
* @param source - the source node index
|
|
12506
|
+
* @param o - the seam's `BfsOptions` (`maxDepth`)
|
|
12507
|
+
* @returns depth, parent, the level-grouped order, visitedCount, levels and switches (spec 3.3 line 830)
|
|
12508
|
+
*/
|
|
12509
|
+
async breadthFirstSearch(gs, source, o) {
|
|
12510
|
+
ctx.assertReady();
|
|
12511
|
+
return await breadthFirstSearch(ctx, gs, source, o);
|
|
12512
|
+
},
|
|
12513
|
+
/**
|
|
12514
|
+
* Single-source shortest paths on the device (spec 8.4; P8-T13): the near-far queue over f32 distances, or the
|
|
12515
|
+
* breadth-first route when every weight is one.
|
|
12516
|
+
* @param gs - the snapshot
|
|
12517
|
+
* @param source - the source node index
|
|
12518
|
+
* @param o - the seam's `SsspOptions` (`cutoff`, `weights`)
|
|
12519
|
+
* @returns dist, predArc and reachedCount (spec 3.3 line 831)
|
|
12520
|
+
*/
|
|
12521
|
+
async sssp(gs, source, o) {
|
|
12522
|
+
ctx.assertReady();
|
|
12523
|
+
return await sssp(ctx, gs, source, o);
|
|
12524
|
+
},
|
|
12525
|
+
/**
|
|
12526
|
+
* Bellman-Ford on the device with negative-cycle detection (spec 8.4; P8-T13).
|
|
12527
|
+
* @param gs - the snapshot
|
|
12528
|
+
* @param source - the source node index
|
|
12529
|
+
* @param o - the seam's `SsspOptions` (`cutoff`, `weights`)
|
|
12530
|
+
* @returns dist, predArc, reachedCount and hasNegativeCycle (spec 3.3 line 832)
|
|
12531
|
+
*/
|
|
12532
|
+
async bellmanFord(gs, source, o) {
|
|
12533
|
+
ctx.assertReady();
|
|
12534
|
+
return await bellmanFord(ctx, gs, source, o);
|
|
12535
|
+
},
|
|
12536
|
+
/**
|
|
12537
|
+
* Closeness centrality on the device (spec 8.4; P8-T13): the bit-parallel multi-source sweep, or one `sssp`
|
|
12538
|
+
* per source when `weighted`. `maxIterations` / `tolerance` are refused when defined (P8 PD-25).
|
|
12539
|
+
* @param gs - the snapshot
|
|
12540
|
+
* @param o - the seam's placeholder `HitsOptionsLike` (`weighted`)
|
|
12541
|
+
* @returns the f32 scores with `precision: "f32"` (spec 9.7)
|
|
12542
|
+
*/
|
|
12543
|
+
async closenessCentrality(gs, o) {
|
|
12544
|
+
ctx.assertReady();
|
|
12545
|
+
return await closenessCentrality(ctx, gs, o);
|
|
12546
|
+
},
|
|
9721
12547
|
/**
|
|
9722
12548
|
* Destroys every device buffer recorded for the snapshot (spec 4.5); delegates to ctx.release.
|
|
9723
12549
|
* @param s - the snapshot the app is done with
|
|
@@ -9827,7 +12653,7 @@ async function calibrateLayout(ctx, options) {
|
|
|
9827
12653
|
};
|
|
9828
12654
|
}
|
|
9829
12655
|
export {
|
|
9830
|
-
|
|
12656
|
+
C as ARC_WINDOW_ALIGN,
|
|
9831
12657
|
EXACT_MAX_NODES,
|
|
9832
12658
|
FA2_DEFAULTS,
|
|
9833
12659
|
FR_DEFAULTS,
|
|
@@ -9835,12 +12661,15 @@ export {
|
|
|
9835
12661
|
LAYOUT_TUNING_DEFAULTS,
|
|
9836
12662
|
MAX_1D_ITEMS,
|
|
9837
12663
|
MAX_WORKGROUPS_PER_DIM,
|
|
9838
|
-
|
|
12664
|
+
D as PASSTHROUGH_FORMAT_CODES,
|
|
9839
12665
|
SE_DEFAULTS,
|
|
9840
|
-
|
|
9841
|
-
|
|
12666
|
+
H as STORAGE_ALIGN,
|
|
12667
|
+
J as WORKGROUP_SIZE,
|
|
9842
12668
|
WebGpuGraphError,
|
|
12669
|
+
bellmanFord,
|
|
12670
|
+
breadthFirstSearch,
|
|
9843
12671
|
calibrateLayout,
|
|
12672
|
+
closenessCentrality,
|
|
9844
12673
|
connectedComponents,
|
|
9845
12674
|
createAccelerator,
|
|
9846
12675
|
createForceAtlas2,
|
|
@@ -9850,12 +12679,13 @@ export {
|
|
|
9850
12679
|
eigenvectorCentrality,
|
|
9851
12680
|
hasErrorCode,
|
|
9852
12681
|
hits,
|
|
9853
|
-
|
|
12682
|
+
K as isSoftwareAdapter,
|
|
9854
12683
|
isWebGpuGraphError,
|
|
9855
12684
|
katzCentrality,
|
|
9856
12685
|
pageRank,
|
|
9857
12686
|
personalizedPageRank,
|
|
9858
12687
|
seedPositions,
|
|
12688
|
+
sssp,
|
|
9859
12689
|
verifyDevice
|
|
9860
12690
|
};
|
|
9861
12691
|
//# sourceMappingURL=webgpu-graph-algorithms.js.map
|