@graphty/webgpu-graph-algorithms 0.5.1 → 0.6.1
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 +459 -58
- package/dist/browser.js +1 -1
- package/dist/chunks/{context-BR7fx3vR.js → context-BXqgCifx.js} +190 -40
- package/dist/chunks/context-BXqgCifx.js.map +1 -0
- package/dist/node.js +1 -1
- package/dist/src/algorithms/components.d.ts.map +1 -1
- package/dist/src/algorithms/components.js +12 -13
- package/dist/src/algorithms/components.js.map +1 -1
- package/dist/src/algorithms/degree.d.ts +6 -8
- package/dist/src/algorithms/degree.d.ts.map +1 -1
- package/dist/src/algorithms/degree.js +58 -35
- package/dist/src/algorithms/degree.js.map +1 -1
- package/dist/src/algorithms/pagerank.d.ts.map +1 -1
- package/dist/src/algorithms/pagerank.js +16 -14
- package/dist/src/algorithms/pagerank.js.map +1 -1
- package/dist/src/algorithms/power-iteration.d.ts +2 -2
- package/dist/src/algorithms/power-iteration.d.ts.map +1 -1
- package/dist/src/algorithms/power-iteration.js +17 -14
- package/dist/src/algorithms/power-iteration.js.map +1 -1
- package/dist/src/constants.d.ts +38 -8
- package/dist/src/constants.d.ts.map +1 -1
- package/dist/src/constants.js +38 -8
- package/dist/src/constants.js.map +1 -1
- package/dist/src/errors.d.ts +3 -2
- package/dist/src/errors.d.ts.map +1 -1
- package/dist/src/errors.js +2 -1
- package/dist/src/errors.js.map +1 -1
- package/dist/src/index.d.ts +6 -4
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +8 -3
- package/dist/src/index.js.map +1 -1
- package/dist/src/kernel/dispatch.d.ts +8 -3
- package/dist/src/kernel/dispatch.d.ts.map +1 -1
- package/dist/src/kernel/dispatch.js +18 -7
- package/dist/src/kernel/dispatch.js.map +1 -1
- package/dist/src/kernel/kernel.d.ts +30 -1
- package/dist/src/kernel/kernel.d.ts.map +1 -1
- package/dist/src/kernel/kernel.js +49 -5
- package/dist/src/kernel/kernel.js.map +1 -1
- package/dist/src/kernel/prelude.d.ts.map +1 -1
- package/dist/src/kernel/prelude.js +6 -1
- package/dist/src/kernel/prelude.js.map +1 -1
- package/dist/src/kernel/profiler.d.ts +15 -3
- package/dist/src/kernel/profiler.d.ts.map +1 -1
- package/dist/src/kernel/profiler.js +27 -4
- package/dist/src/kernel/profiler.js.map +1 -1
- package/dist/src/kernels.d.ts +17 -7
- package/dist/src/kernels.d.ts.map +1 -1
- package/dist/src/kernels.js +323 -16
- package/dist/src/kernels.js.map +1 -1
- package/dist/src/layouts/calibrate.d.ts +51 -0
- package/dist/src/layouts/calibrate.d.ts.map +1 -0
- package/dist/src/layouts/calibrate.js +172 -0
- package/dist/src/layouts/calibrate.js.map +1 -0
- package/dist/src/layouts/force-simulation.d.ts +39 -4
- package/dist/src/layouts/force-simulation.d.ts.map +1 -1
- package/dist/src/layouts/force-simulation.js +71 -19
- package/dist/src/layouts/force-simulation.js.map +1 -1
- package/dist/src/layouts/forceatlas2.d.ts +107 -36
- package/dist/src/layouts/forceatlas2.d.ts.map +1 -1
- package/dist/src/layouts/forceatlas2.js +296 -100
- package/dist/src/layouts/forceatlas2.js.map +1 -1
- package/dist/src/layouts/fruchterman-reingold.d.ts +73 -27
- package/dist/src/layouts/fruchterman-reingold.d.ts.map +1 -1
- package/dist/src/layouts/fruchterman-reingold.js +230 -70
- package/dist/src/layouts/fruchterman-reingold.js.map +1 -1
- package/dist/src/layouts/model-common.d.ts +41 -3
- package/dist/src/layouts/model-common.d.ts.map +1 -1
- package/dist/src/layouts/model-common.js +74 -3
- package/dist/src/layouts/model-common.js.map +1 -1
- package/dist/src/layouts/repulsion-grid.d.ts +152 -0
- package/dist/src/layouts/repulsion-grid.d.ts.map +1 -0
- package/dist/src/layouts/repulsion-grid.js +318 -0
- package/dist/src/layouts/repulsion-grid.js.map +1 -0
- package/dist/src/layouts/spring-electrical.d.ts +75 -30
- package/dist/src/layouts/spring-electrical.d.ts.map +1 -1
- package/dist/src/layouts/spring-electrical.js +231 -74
- package/dist/src/layouts/spring-electrical.js.map +1 -1
- package/dist/src/memory/residency.d.ts +6 -2
- package/dist/src/memory/residency.d.ts.map +1 -1
- package/dist/src/memory/residency.js +84 -14
- package/dist/src/memory/residency.js.map +1 -1
- package/dist/src/primitives/core-shape.d.ts +38 -2
- package/dist/src/primitives/core-shape.d.ts.map +1 -1
- package/dist/src/primitives/core-shape.js +71 -3
- package/dist/src/primitives/core-shape.js.map +1 -1
- package/dist/src/primitives/grid-pyramid.d.ts +71 -0
- package/dist/src/primitives/grid-pyramid.d.ts.map +1 -0
- package/dist/src/primitives/grid-pyramid.js +143 -0
- package/dist/src/primitives/grid-pyramid.js.map +1 -0
- package/dist/src/primitives/grid.d.ts +118 -0
- package/dist/src/primitives/grid.d.ts.map +1 -0
- package/dist/src/primitives/grid.js +225 -0
- package/dist/src/primitives/grid.js.map +1 -0
- package/dist/src/primitives/histogram.d.ts +67 -0
- package/dist/src/primitives/histogram.d.ts.map +1 -0
- package/dist/src/primitives/histogram.js +190 -0
- package/dist/src/primitives/histogram.js.map +1 -0
- package/dist/src/primitives/radix-sort.d.ts +75 -0
- package/dist/src/primitives/radix-sort.d.ts.map +1 -0
- package/dist/src/primitives/radix-sort.js +168 -0
- package/dist/src/primitives/radix-sort.js.map +1 -0
- package/dist/src/primitives/scan.d.ts +44 -0
- package/dist/src/primitives/scan.d.ts.map +1 -0
- package/dist/src/primitives/scan.js +151 -0
- package/dist/src/primitives/scan.js.map +1 -0
- package/dist/src/primitives/segmented-reduce.d.ts +25 -17
- package/dist/src/primitives/segmented-reduce.d.ts.map +1 -1
- package/dist/src/primitives/segmented-reduce.js +166 -47
- package/dist/src/primitives/segmented-reduce.js.map +1 -1
- package/dist/src/primitives/spmv.d.ts +18 -14
- package/dist/src/primitives/spmv.d.ts.map +1 -1
- package/dist/src/primitives/spmv.js +94 -58
- package/dist/src/primitives/spmv.js.map +1 -1
- package/dist/src/primitives/verify.d.ts +49 -0
- package/dist/src/primitives/verify.d.ts.map +1 -0
- package/dist/src/primitives/verify.js +229 -0
- package/dist/src/primitives/verify.js.map +1 -0
- package/dist/src/types/context.d.ts +53 -0
- package/dist/src/types/context.d.ts.map +1 -1
- package/dist/src/types/layout.d.ts +20 -0
- package/dist/src/types/layout.d.ts.map +1 -1
- package/dist/src/wgsl/counting-scatter.wgsl.d.ts +8 -0
- package/dist/src/wgsl/counting-scatter.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/counting-scatter.wgsl.js +17 -0
- package/dist/src/wgsl/counting-scatter.wgsl.js.map +1 -0
- package/dist/src/wgsl/fa2-attraction.wgsl.d.ts +23 -11
- package/dist/src/wgsl/fa2-attraction.wgsl.d.ts.map +1 -1
- package/dist/src/wgsl/fa2-attraction.wgsl.js +98 -20
- package/dist/src/wgsl/fa2-attraction.wgsl.js.map +1 -1
- package/dist/src/wgsl/fa2-stats-finalize.wgsl.d.ts +6 -2
- package/dist/src/wgsl/fa2-stats-finalize.wgsl.d.ts.map +1 -1
- package/dist/src/wgsl/fa2-stats-finalize.wgsl.js +22 -1
- package/dist/src/wgsl/fa2-stats-finalize.wgsl.js.map +1 -1
- package/dist/src/wgsl/grid-cell-key.wgsl.d.ts +8 -0
- package/dist/src/wgsl/grid-cell-key.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/grid-cell-key.wgsl.js +30 -0
- package/dist/src/wgsl/grid-cell-key.wgsl.js.map +1 -0
- package/dist/src/wgsl/grid-centroid-hub.wgsl.d.ts +8 -0
- package/dist/src/wgsl/grid-centroid-hub.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/grid-centroid-hub.wgsl.js +29 -0
- package/dist/src/wgsl/grid-centroid-hub.wgsl.js.map +1 -0
- package/dist/src/wgsl/grid-centroid.wgsl.d.ts +8 -0
- package/dist/src/wgsl/grid-centroid.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/grid-centroid.wgsl.js +29 -0
- package/dist/src/wgsl/grid-centroid.wgsl.js.map +1 -0
- package/dist/src/wgsl/grid-downsample.wgsl.d.ts +7 -0
- package/dist/src/wgsl/grid-downsample.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/grid-downsample.wgsl.js +28 -0
- package/dist/src/wgsl/grid-downsample.wgsl.js.map +1 -0
- package/dist/src/wgsl/grid-far-field.wgsl.d.ts +13 -0
- package/dist/src/wgsl/grid-far-field.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/grid-far-field.wgsl.js +98 -0
- package/dist/src/wgsl/grid-far-field.wgsl.js.map +1 -0
- package/dist/src/wgsl/grid-near-field.wgsl.d.ts +19 -0
- package/dist/src/wgsl/grid-near-field.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/grid-near-field.wgsl.js +129 -0
- package/dist/src/wgsl/grid-near-field.wgsl.js.map +1 -0
- package/dist/src/wgsl/histogram.wgsl.d.ts +7 -0
- package/dist/src/wgsl/histogram.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/histogram.wgsl.js +15 -0
- package/dist/src/wgsl/histogram.wgsl.js.map +1 -0
- package/dist/src/wgsl/indirect-finalize.wgsl.d.ts +8 -0
- package/dist/src/wgsl/indirect-finalize.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/indirect-finalize.wgsl.js +26 -0
- package/dist/src/wgsl/indirect-finalize.wgsl.js.map +1 -0
- package/dist/src/wgsl/radix-hist.wgsl.d.ts +9 -0
- package/dist/src/wgsl/radix-hist.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/radix-hist.wgsl.js +31 -0
- package/dist/src/wgsl/radix-hist.wgsl.js.map +1 -0
- package/dist/src/wgsl/radix-scatter.wgsl.d.ts +9 -0
- package/dist/src/wgsl/radix-scatter.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/radix-scatter.wgsl.js +40 -0
- package/dist/src/wgsl/radix-scatter.wgsl.js.map +1 -0
- package/dist/src/wgsl/scan-add.wgsl.d.ts +6 -0
- package/dist/src/wgsl/scan-add.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/scan-add.wgsl.js +14 -0
- package/dist/src/wgsl/scan-add.wgsl.js.map +1 -0
- package/dist/src/wgsl/scan-block.wgsl.d.ts +8 -0
- package/dist/src/wgsl/scan-block.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/scan-block.wgsl.js +30 -0
- package/dist/src/wgsl/scan-block.wgsl.js.map +1 -0
- package/dist/src/wgsl/segmented-reduce.wgsl.d.ts +22 -8
- package/dist/src/wgsl/segmented-reduce.wgsl.d.ts.map +1 -1
- package/dist/src/wgsl/segmented-reduce.wgsl.js +84 -15
- package/dist/src/wgsl/segmented-reduce.wgsl.js.map +1 -1
- package/dist/src/wgsl/spmv-pull.wgsl.d.ts +22 -11
- package/dist/src/wgsl/spmv-pull.wgsl.d.ts.map +1 -1
- package/dist/src/wgsl/spmv-pull.wgsl.js +110 -36
- package/dist/src/wgsl/spmv-pull.wgsl.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/webgpu-graph-algorithms.js +3815 -1003
- package/dist/webgpu-graph-algorithms.js.map +1 -1
- package/package.json +9 -8
- package/src/algorithms/components.ts +12 -16
- package/src/algorithms/degree.ts +58 -43
- package/src/algorithms/pagerank.ts +20 -18
- package/src/algorithms/power-iteration.ts +19 -18
- package/src/constants.ts +38 -8
- package/src/errors.ts +3 -1
- package/src/index.ts +14 -4
- package/src/kernel/dispatch.ts +18 -7
- package/src/kernel/kernel.ts +59 -5
- package/src/kernel/prelude.ts +9 -0
- package/src/kernel/profiler.ts +28 -4
- package/src/kernels.ts +356 -18
- package/src/layouts/calibrate.ts +187 -0
- package/src/layouts/force-simulation.ts +91 -23
- package/src/layouts/forceatlas2.ts +331 -106
- package/src/layouts/fruchterman-reingold.ts +255 -74
- package/src/layouts/model-common.ts +98 -3
- package/src/layouts/repulsion-grid.ts +451 -0
- package/src/layouts/spring-electrical.ts +257 -78
- package/src/memory/residency.ts +126 -20
- package/src/primitives/core-shape.ts +91 -4
- package/src/primitives/grid-pyramid.ts +221 -0
- package/src/primitives/grid.ts +349 -0
- package/src/primitives/histogram.ts +273 -0
- package/src/primitives/radix-sort.ts +246 -0
- package/src/primitives/scan.ts +197 -0
- package/src/primitives/segmented-reduce.ts +214 -56
- package/src/primitives/spmv.ts +125 -65
- package/src/primitives/verify.ts +249 -0
- package/src/types/context.ts +56 -0
- package/src/types/layout.ts +22 -0
- package/src/wgsl/counting-scatter.wgsl.ts +16 -0
- package/src/wgsl/fa2-attraction.wgsl.ts +98 -20
- package/src/wgsl/fa2-stats-finalize.wgsl.ts +22 -1
- package/src/wgsl/grid-cell-key.wgsl.ts +29 -0
- package/src/wgsl/grid-centroid-hub.wgsl.ts +28 -0
- package/src/wgsl/grid-centroid.wgsl.ts +28 -0
- package/src/wgsl/grid-downsample.wgsl.ts +27 -0
- package/src/wgsl/grid-far-field.wgsl.ts +97 -0
- package/src/wgsl/grid-near-field.wgsl.ts +128 -0
- package/src/wgsl/histogram.wgsl.ts +14 -0
- package/src/wgsl/indirect-finalize.wgsl.ts +25 -0
- package/src/wgsl/radix-hist.wgsl.ts +30 -0
- package/src/wgsl/radix-scatter.wgsl.ts +39 -0
- package/src/wgsl/scan-add.wgsl.ts +13 -0
- package/src/wgsl/scan-block.wgsl.ts +29 -0
- package/src/wgsl/segmented-reduce.wgsl.ts +84 -15
- package/src/wgsl/spmv-pull.wgsl.ts +110 -36
- package/dist/chunks/context-BR7fx3vR.js.map +0 -1
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Fruchterman-Reingold on the exact repulsion tier (spec 7.20; contract 3.13): the ForceModel that
|
|
3
|
-
* drives -- the K1 K2 K3 K5 sequence per iteration
|
|
4
|
-
*
|
|
2
|
+
* Fruchterman-Reingold on the exact or the grid repulsion tier (spec 7.20, 7.8; contract 3.13): the ForceModel that
|
|
3
|
+
* ForceSimulation drives -- the K1 K2 K3 K5 sequence per iteration on the exact tier (K1 K2 G1..G7 K5 on the grid
|
|
4
|
+
* tier, P4-T13) and toScene per batch (no K4: no speed controller), the constant override set (`LAW` 1 on K2 / K3
|
|
5
|
+
* and on G6 / G7, `APPLY` 1 on K5, `STATS_MODE` 1 on K1, PD-1), the per-iteration Fa2Params values
|
|
5
6
|
* with the cooling schedule's temperature (PD-5), the controller resets and the stats decoder -- plus the option
|
|
6
7
|
* resolver and `createFruchtermanReingold`. The model shares FA2's four kernels, buffers and blocks: `oldForce` is
|
|
7
8
|
* allocated, bound and never read (K5 compiles `SWING_MODE = 1`, PD-2 / PD-20); the mass lane is 1 for every node
|
|
@@ -15,6 +16,11 @@
|
|
|
15
16
|
* the CPU's reheat() does. The temperature is `max(0, 0.1 - dt * (global - tempOrigin))`, dt = 0.1 / (iterations + 1);
|
|
16
17
|
* at 0 nothing moves and the settle window closes the run (DEP-P5-C: the budget restarts at 0, the temperature does
|
|
17
18
|
* not).
|
|
19
|
+
*
|
|
20
|
+
* The grid tier (P4-T13, PD-22) is FA2's: `RepulsionGrid` with `LAW` 1 when `tierFor(tuning, n)` says so (PD-18),
|
|
21
|
+
* the grid buffers from `buffers()`, K1's grid block under `gridMax > 0` (PD-14) over the model's own `hubCounters`,
|
|
22
|
+
* the three passes `fr-k1` / `fr-attraction` / `fr-grid` before `fa2-to-scene` (PD-16), and the union stage list
|
|
23
|
+
* (PD-17: `upTo` stops after the last stage recorded at or before its position; K4 is never recorded).
|
|
18
24
|
*/
|
|
19
25
|
|
|
20
26
|
import { type GraphSnapshot, type NodeMask } from "@graphty/graph-format";
|
|
@@ -37,7 +43,9 @@ import { type DispatchPlan, plan1d } from "../kernel/dispatch.js";
|
|
|
37
43
|
import { type BoundKernel, type Kernel } from "../kernel/kernel.js";
|
|
38
44
|
import { type UniformBlock, type UniformValues } from "../kernel/struct-block.js";
|
|
39
45
|
import { type WgslModuleSpec } from "../kernel/wgsl.js";
|
|
40
|
-
import { FA2_PARAMS, FA2_STATE, FA2_TRACE, FILL_PARAMS,
|
|
46
|
+
import { FA2_PARAMS, FA2_STATE, FA2_TRACE, FILL_PARAMS, kernelSpec } from "../kernels.js";
|
|
47
|
+
import { arcCountOf } from "../primitives/core-shape.js";
|
|
48
|
+
import { type GridSpec, gridSpecFor } from "../primitives/grid.js";
|
|
41
49
|
import {
|
|
42
50
|
type FruchtermanReingoldStats,
|
|
43
51
|
type FruchtermanReingoldTraceRecord,
|
|
@@ -54,9 +62,12 @@ import {
|
|
|
54
62
|
type ModelInputs,
|
|
55
63
|
type ModelResources,
|
|
56
64
|
type StateWriter,
|
|
65
|
+
tierFor,
|
|
57
66
|
} from "./force-simulation.js";
|
|
58
|
-
import { resolveLayoutTuning } from "./forceatlas2.js";
|
|
67
|
+
import { resolveLayoutTuning, writeGridFrame } from "./forceatlas2.js";
|
|
59
68
|
import {
|
|
69
|
+
type AttractionBound,
|
|
70
|
+
bindAttraction,
|
|
60
71
|
describeValue,
|
|
61
72
|
FILL_PARAMS_BUFFER,
|
|
62
73
|
FORCE_BYTES_PER_NODE,
|
|
@@ -67,16 +78,27 @@ import {
|
|
|
67
78
|
pickDim,
|
|
68
79
|
pickNumber,
|
|
69
80
|
pickSeed,
|
|
81
|
+
recordAttraction,
|
|
70
82
|
scalar,
|
|
71
83
|
seedWord,
|
|
72
84
|
subset,
|
|
73
85
|
vector,
|
|
74
86
|
} from "./model-common.js";
|
|
87
|
+
import { type GridStage, RepulsionGrid, type RepulsionGridOverrides } from "./repulsion-grid.js";
|
|
75
88
|
|
|
76
89
|
// ============================================================ constants
|
|
77
90
|
|
|
78
|
-
/** The stage names of
|
|
79
|
-
const FR_STAGES = ["K1", "K2", "K3", "K5", "toScene"] as const;
|
|
91
|
+
/** The stage names of both tiers in dispatch order plus the per-batch toScene (spec 7.20; P4 PD-17: the union list; the exact tier records K1 K2 K3 K5, the grid tier K1 K2 G1..G7 K5, K4 never). */
|
|
92
|
+
const FR_STAGES = ["K1", "K2", "K3", "G1", "G2", "G3", "G4", "G5", "G6", "G7", "K4", "K5", "toScene"] as const;
|
|
93
|
+
|
|
94
|
+
/** The FR_STAGES index of the first grid stage, of K4, of K5 and of toScene. */
|
|
95
|
+
const STAGE_G1 = 3;
|
|
96
|
+
const STAGE_K4 = 10;
|
|
97
|
+
const STAGE_K5 = 11;
|
|
98
|
+
const STAGE_TO_SCENE = 12;
|
|
99
|
+
|
|
100
|
+
/** The name of the model-owned hub-counter buffer K1 binds on every tier (P4 PD-14). */
|
|
101
|
+
const HUB_COUNTERS_BUFFER = "hubCounters";
|
|
80
102
|
|
|
81
103
|
/** The one-workgroup dispatch of K1 (spec 7.4). */
|
|
82
104
|
const ONE_WORKGROUP: DispatchPlan = { x: 1, y: 1, z: 1, items: 1, stride: null };
|
|
@@ -94,6 +116,14 @@ const FR_OVERRIDES: Overrides = Object.freeze({
|
|
|
94
116
|
STATS_MODE: 1,
|
|
95
117
|
});
|
|
96
118
|
|
|
119
|
+
/** The grid stage's override set (G6 / G7 / K4; P4-T13, PD-22): K3's constant three and the FR law. */
|
|
120
|
+
const FR_GRID_OVERRIDES: RepulsionGridOverrides = Object.freeze({
|
|
121
|
+
SWING_MODE: 1,
|
|
122
|
+
STRONG_GRAVITY: false,
|
|
123
|
+
GRAVITY_CENTER: 0,
|
|
124
|
+
LAW: 1,
|
|
125
|
+
});
|
|
126
|
+
|
|
97
127
|
/** Every override each kernel accepts, with its default (the names its registry entry declares). */
|
|
98
128
|
const K1_DEFAULTS: Overrides = { STATS_MODE: 0 };
|
|
99
129
|
const K2_DEFAULTS: Overrides = {
|
|
@@ -267,11 +297,13 @@ interface BoundModel {
|
|
|
267
297
|
readonly fillPlan: DispatchPlan;
|
|
268
298
|
readonly k1: Kernel;
|
|
269
299
|
readonly k1Bound: BoundKernel;
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
readonly k3: Kernel;
|
|
274
|
-
readonly k3Bound: BoundKernel;
|
|
300
|
+
/** The K2 tier dispatches (P4 PD-7); null when arcCount === 0 (K2 is not recorded; the fill below zeroes force instead, spec 7.5). */
|
|
301
|
+
readonly attraction: AttractionBound | null;
|
|
302
|
+
/** K3 and its bind group, or null on the grid tier (P4 PD-18): one tier's kernels compile per load. */
|
|
303
|
+
readonly k3: Kernel | null;
|
|
304
|
+
readonly k3Bound: BoundKernel | null;
|
|
305
|
+
/** The grid-tier stage (G1-G7), or null on the exact tier (P4 PD-18). */
|
|
306
|
+
readonly grid: RepulsionGrid | null;
|
|
275
307
|
readonly k5: Kernel;
|
|
276
308
|
readonly k5Bound: BoundKernel;
|
|
277
309
|
readonly toScene: Kernel;
|
|
@@ -281,12 +313,12 @@ interface BoundModel {
|
|
|
281
313
|
readonly fillForceBound: BoundKernel | null;
|
|
282
314
|
}
|
|
283
315
|
|
|
284
|
-
/** The Fruchterman-Reingold model (spec 7.20: K1 K2 K3 K5 per iteration; toScene once per batch). Stages:
|
|
316
|
+
/** The Fruchterman-Reingold model (spec 7.20: K1 K2 K3 K5 per iteration on the exact tier, K1 K2 G1..G7 K5 on the grid tier; toScene once per batch). Stages: the union list of PD-17. */
|
|
285
317
|
export class FruchtermanReingoldModel implements ForceModel<FruchtermanReingoldOptions, FruchtermanReingoldStats> {
|
|
286
318
|
/** The model kind of spec 7.19. */
|
|
287
319
|
readonly kind = "fruchtermanReingold";
|
|
288
320
|
/** The stage names in dispatch order (the `upTo` vocabulary of recordIteration and debugRunStages). */
|
|
289
|
-
readonly stages:
|
|
321
|
+
readonly stages: typeof FR_STAGES = FR_STAGES;
|
|
290
322
|
/** Fa2Params: the per-iteration uniform block (the simulation writes the shared fields into it). */
|
|
291
323
|
readonly params: UniformBlock = FA2_PARAMS;
|
|
292
324
|
/** Fa2State: the state header block. */
|
|
@@ -304,6 +336,8 @@ export class FruchtermanReingoldModel implements ForceModel<FruchtermanReingoldO
|
|
|
304
336
|
private pendingReheat = false;
|
|
305
337
|
private resources: ModelResources | null = null;
|
|
306
338
|
private bound: BoundModel | null = null;
|
|
339
|
+
/** The grid of the load inputs() last resolved (null on the exact tier): onLoad() writes its frame, specs() lists its kernels. */
|
|
340
|
+
private nextGrid: GridSpec | null = null;
|
|
307
341
|
/** The K1-K5 compute pass of the batch being recorded, keyed by CommandBatch.id (one pass per batch, contract 4.4). */
|
|
308
342
|
private openPass: { readonly id: number; readonly pass: GPUComputePassEncoder } | null = null;
|
|
309
343
|
|
|
@@ -318,15 +352,19 @@ export class FruchtermanReingoldModel implements ForceModel<FruchtermanReingoldO
|
|
|
318
352
|
}
|
|
319
353
|
|
|
320
354
|
/**
|
|
321
|
-
* force 12n and oldForce 12n (zeroed; bound and never read, PD-2)
|
|
322
|
-
* fill dispatch reads
|
|
355
|
+
* force 12n and oldForce 12n (zeroed; bound and never read, PD-2), the 256-byte FillParams uniform buffer the
|
|
356
|
+
* fill dispatch reads, the 16-byte `hubCounters` K1 binds on every tier (P4 PD-14), and the grid buffers of
|
|
357
|
+
* `RepulsionGrid.buffers` exactly when `tierFor(tuning, n)` is the grid tier (PD-18). n = 0 reports one node's
|
|
358
|
+
* worth of bytes so no zero-length buffer is ever created.
|
|
323
359
|
* @param n - the node count
|
|
324
|
-
* @param
|
|
325
|
-
* @returns the
|
|
360
|
+
* @param dim - the layout dimension (the force arrays are stride 3 in both; the grid's geometry differs)
|
|
361
|
+
* @returns the model-owned buffer specs
|
|
326
362
|
*/
|
|
327
|
-
buffers(n: number,
|
|
363
|
+
buffers(n: number, dim: 2 | 3): readonly BufferSpec[] {
|
|
328
364
|
const bytes = Math.max(1, n) * FORCE_BYTES_PER_NODE;
|
|
329
365
|
const usage = BufferUsage.STORAGE | BufferUsage.COPY_SRC | BufferUsage.COPY_DST;
|
|
366
|
+
const grid =
|
|
367
|
+
tierFor(this.tuning, n) === "grid" ? RepulsionGrid.buffers(n, gridSpecFor(n, dim, this.tuning)) : [];
|
|
330
368
|
return [
|
|
331
369
|
{ name: "force", byteLength: bytes, usage, zero: true },
|
|
332
370
|
{ name: "oldForce", byteLength: bytes, usage, zero: true },
|
|
@@ -336,24 +374,30 @@ export class FruchtermanReingoldModel implements ForceModel<FruchtermanReingoldO
|
|
|
336
374
|
usage: BufferUsage.UNIFORM | BufferUsage.COPY_DST,
|
|
337
375
|
zero: false,
|
|
338
376
|
},
|
|
377
|
+
{ name: HUB_COUNTERS_BUFFER, byteLength: 16, usage, zero: true },
|
|
378
|
+
...grid,
|
|
339
379
|
];
|
|
340
380
|
}
|
|
341
381
|
|
|
342
382
|
/**
|
|
343
383
|
* Mass 1 for every node and no weights (PD-11: FR has no mass and ignores weights), the fixed mask of the
|
|
344
|
-
* `fixed` option applied at load (PD-6).
|
|
345
|
-
*
|
|
384
|
+
* `fixed` option applied at load (PD-6). Also remembers the grid of this load (`tierFor(tuning, n)`, spec 7.8)
|
|
385
|
+
* for onLoad() and specs(): the simulation calls inputs() first, then onLoad() before bind().
|
|
346
386
|
* @param s - the snapshot being loaded
|
|
347
387
|
* @param options - the simulation's current option record
|
|
348
388
|
* @returns the per-load inputs
|
|
349
389
|
*/
|
|
350
390
|
inputs(s: GraphSnapshot, options: FruchtermanReingoldOptions): ModelInputs {
|
|
351
391
|
const resolved = resolveFruchtermanReingoldOptions(options, this.current);
|
|
352
|
-
|
|
392
|
+
// resolve first: a throwing mask resolution leaves the remembered grid of the previous load intact
|
|
393
|
+
const inputs: ModelInputs = {
|
|
353
394
|
mass: new Float32Array(s.nodeCount).fill(1),
|
|
354
395
|
weights: { data: null, source: "none", column: null },
|
|
355
396
|
fixed: resolveFixed(s, resolved.fixed),
|
|
356
397
|
};
|
|
398
|
+
const n = s.nodeCount;
|
|
399
|
+
this.nextGrid = tierFor(this.tuning, n) === "grid" ? gridSpecFor(n, resolved.dim, this.tuning) : null;
|
|
400
|
+
return inputs;
|
|
357
401
|
}
|
|
358
402
|
|
|
359
403
|
/**
|
|
@@ -367,12 +411,15 @@ export class FruchtermanReingoldModel implements ForceModel<FruchtermanReingoldO
|
|
|
367
411
|
|
|
368
412
|
/**
|
|
369
413
|
* The six module specs of an override set in dispatch order -- K1, K2, K3, K5, toScene, fill -- each with only the
|
|
370
|
-
* override names its entry declares (K2 also USE_PERM / HAS_WEIGHTS), for warm() and the compile matrix
|
|
414
|
+
* override names its entry declares (K2 also USE_PERM / HAS_WEIGHTS), for warm() and the compile matrix,
|
|
415
|
+
* followed by the grid tier's specs (`RepulsionGrid.specs` under FR_GRID_OVERRIDES) when the load inputs() last
|
|
416
|
+
* resolved is a grid load (the pipeline key carries no geometry).
|
|
371
417
|
* @param overrides - the merged override set (the model's plus USE_PERM / HAS_WEIGHTS)
|
|
372
418
|
* @param _subgroups - accepted for the ForceModel interface and unused (the composer picks the twin from caps)
|
|
373
419
|
* @returns the specs
|
|
374
420
|
*/
|
|
375
421
|
specs(overrides: Overrides, _subgroups: boolean): readonly WgslModuleSpec[] {
|
|
422
|
+
const grid = this.nextGrid === null ? [] : RepulsionGrid.specs(FR_GRID_OVERRIDES, this.nextGrid);
|
|
376
423
|
return [
|
|
377
424
|
kernelSpec("fa2-stats-finalize", subset(overrides, K1_DEFAULTS)),
|
|
378
425
|
kernelSpec("fa2-attraction", subset(overrides, K2_DEFAULTS)),
|
|
@@ -380,44 +427,63 @@ export class FruchtermanReingoldModel implements ForceModel<FruchtermanReingoldO
|
|
|
380
427
|
kernelSpec("fa2-integrate", subset(overrides, K5_DEFAULTS)),
|
|
381
428
|
kernelSpec("fa2-to-scene"),
|
|
382
429
|
kernelSpec("fill"),
|
|
430
|
+
...grid,
|
|
383
431
|
];
|
|
384
432
|
}
|
|
385
433
|
|
|
386
434
|
/**
|
|
387
|
-
* Compiles (through the cache) and binds every kernel against the buffers of this load(): K1, K2
|
|
388
|
-
* force when arcCount === 0), K3
|
|
389
|
-
*
|
|
435
|
+
* Compiles (through the cache) and binds every kernel against the buffers of this load(): K1, K2 over the degree
|
|
436
|
+
* tiers through bindAttraction (or the fill of force when arcCount === 0), K3 on the exact tier or G1-G7 through
|
|
437
|
+
* RepulsionGrid on the grid tier (PD-18), K5, toScene; writes the FillParams { count: 3n, value: 0, mode: 0 }
|
|
438
|
+
* into the model's uniform buffer. With n === 0 nothing is bound. The K2 TIER 1 / 2 pipelines compile on the
|
|
439
|
+
* first load whose degrees need them (P4 PD-7).
|
|
390
440
|
* @param resources - the graph, the shared and model buffers, the ring and the cache
|
|
391
441
|
* @param overrides - the merged override set
|
|
392
442
|
*/
|
|
393
443
|
async bind(resources: ModelResources, overrides: Overrides): Promise<void> {
|
|
394
444
|
this.dropBound();
|
|
395
445
|
this.resources = resources;
|
|
396
|
-
const { n, pipelines, caps, core,
|
|
446
|
+
const { n, pipelines, caps, core, ring, device } = resources;
|
|
397
447
|
if (n === 0) {
|
|
398
448
|
return;
|
|
399
449
|
}
|
|
450
|
+
const pos = resources.buffer("positions");
|
|
451
|
+
const force = resources.buffer("force");
|
|
452
|
+
const params = ring.binding(FA2_PARAMS);
|
|
453
|
+
const hasArcs = core.colIdx !== null;
|
|
400
454
|
// sequential on purpose: PipelineCache.get compiles inside a validation scope, one stack per device
|
|
401
455
|
const k1 = await pipelines.kernel(kernelSpec("fa2-stats-finalize", subset(overrides, K1_DEFAULTS)));
|
|
402
|
-
const
|
|
403
|
-
|
|
456
|
+
const attraction = hasArcs
|
|
457
|
+
? await bindAttraction(resources, subset(overrides, K2_DEFAULTS), { pos, force, params })
|
|
458
|
+
: null;
|
|
459
|
+
const k3 =
|
|
460
|
+
resources.tier === "grid"
|
|
461
|
+
? null
|
|
462
|
+
: await pipelines.kernel(kernelSpec("fa2-repulsion-exact", subset(overrides, K3_DEFAULTS)));
|
|
404
463
|
const k5 = await pipelines.kernel(kernelSpec("fa2-integrate", subset(overrides, K5_DEFAULTS)));
|
|
405
464
|
const toScene = await pipelines.kernel(kernelSpec("fa2-to-scene"));
|
|
406
465
|
const fill = await pipelines.kernel(kernelSpec("fill"));
|
|
466
|
+
const grid =
|
|
467
|
+
resources.tier === "grid"
|
|
468
|
+
? await RepulsionGrid.create(
|
|
469
|
+
resources,
|
|
470
|
+
k1.workgroupSize,
|
|
471
|
+
FR_GRID_OVERRIDES,
|
|
472
|
+
gridSpecFor(n, resources.dim, this.tuning),
|
|
473
|
+
)
|
|
474
|
+
: null;
|
|
407
475
|
if (this.resources !== resources) {
|
|
408
476
|
// a newer bind() superseded this one while the pipelines compiled; its own bind groups stand
|
|
477
|
+
grid?.dispose();
|
|
409
478
|
return;
|
|
410
479
|
}
|
|
411
|
-
const pos = resources.buffer("positions");
|
|
412
480
|
const scene = resources.buffer("scenePositions");
|
|
413
481
|
const fixed = resources.buffer("fixed");
|
|
414
482
|
const partials = resources.buffer("partials");
|
|
415
483
|
const state = resources.buffer("state");
|
|
416
484
|
const trace = resources.buffer("trace");
|
|
417
|
-
const force = resources.buffer("force");
|
|
418
485
|
const oldForce = resources.buffer("oldForce");
|
|
419
486
|
const fillParamsBuffer = resources.buffer(FILL_PARAMS_BUFFER);
|
|
420
|
-
const params = ring.binding(FA2_PARAMS);
|
|
421
487
|
const fillParams: Binding = {
|
|
422
488
|
buffer: fillParamsBuffer.buffer,
|
|
423
489
|
offset: fillParamsBuffer.offset,
|
|
@@ -427,20 +493,47 @@ export class FruchtermanReingoldModel implements ForceModel<FruchtermanReingoldO
|
|
|
427
493
|
const fillBytes = new ArrayBuffer(FILL_PARAMS.byteLength);
|
|
428
494
|
FILL_PARAMS.write(new DataView(fillBytes), { count: 3 * n, value: 0, mode: 0 });
|
|
429
495
|
device.queue.writeBuffer(fillParamsBuffer.buffer, fillParamsBuffer.offset, fillBytes);
|
|
430
|
-
const
|
|
496
|
+
const hubCounters = resources.buffer(HUB_COUNTERS_BUFFER);
|
|
497
|
+
grid?.bind({
|
|
498
|
+
pos,
|
|
499
|
+
state,
|
|
500
|
+
trace,
|
|
501
|
+
force,
|
|
502
|
+
oldForce,
|
|
503
|
+
fixedMask: fixed,
|
|
504
|
+
partials,
|
|
505
|
+
params,
|
|
506
|
+
cellKey: resources.buffer("cellKey"),
|
|
507
|
+
cellVal: resources.buffer("cellVal"),
|
|
508
|
+
sortedKey: resources.buffer("sortedKey"),
|
|
509
|
+
sortedIdx: resources.buffer("sortedIdx"),
|
|
510
|
+
cellHist: resources.buffer("cellHist"),
|
|
511
|
+
cellStart: resources.buffer("cellStart"),
|
|
512
|
+
hubList: resources.buffer("hubList"),
|
|
513
|
+
hubCounters,
|
|
514
|
+
hubArgs: resources.buffer("hubArgs"),
|
|
515
|
+
pyramid: resources.buffer("pyramid"),
|
|
516
|
+
});
|
|
431
517
|
const wg = k1.workgroupSize;
|
|
432
518
|
this.bound = {
|
|
433
519
|
n,
|
|
434
520
|
plan: plan1d(n, wg, caps),
|
|
435
521
|
fillPlan: plan1d(3 * n, wg, caps),
|
|
436
522
|
k1,
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
:
|
|
523
|
+
// PD-14: on the exact tier K1's cellHist slot takes a dummy (partials, both read-only) and the block is
|
|
524
|
+
// dead under gridMax 0; hubCounters is the model's 16-byte buffer on every tier
|
|
525
|
+
k1Bound: k1.bind({
|
|
526
|
+
partials,
|
|
527
|
+
S: state,
|
|
528
|
+
T: trace,
|
|
529
|
+
cellHist: grid === null ? partials : resources.buffer("cellHist"),
|
|
530
|
+
hubCounters,
|
|
531
|
+
P: params,
|
|
532
|
+
}),
|
|
533
|
+
attraction,
|
|
442
534
|
k3,
|
|
443
|
-
k3Bound: k3
|
|
535
|
+
k3Bound: k3?.bind({ pos, S: state, force, oldForce, fixedMask: fixed, partials, P: params }) ?? null,
|
|
536
|
+
grid,
|
|
444
537
|
k5,
|
|
445
538
|
k5Bound: k5.bind({ force, oldForce, fixedMask: fixed, S: state, pos, partials, P: params }),
|
|
446
539
|
toScene,
|
|
@@ -461,15 +554,20 @@ export class FruchtermanReingoldModel implements ForceModel<FruchtermanReingoldO
|
|
|
461
554
|
* @returns the uniform values
|
|
462
555
|
*/
|
|
463
556
|
paramsFor(iteration: number, options: FruchtermanReingoldOptions): UniformValues {
|
|
464
|
-
const { n } = this.requireResources();
|
|
557
|
+
const { n, core, tiers, tier, dim } = this.requireResources();
|
|
465
558
|
const resolved = resolveFruchtermanReingoldOptions(options, this.current);
|
|
466
559
|
const { nearMax, extentFactor } = this.tuning;
|
|
467
560
|
const adaptive = resolved.cooling === "adaptive";
|
|
561
|
+
const grid = tier === "grid" ? gridSpecFor(n, dim, this.tuning) : null;
|
|
562
|
+
// P4 PD-7: TIER 2 reads [0, hiEnd), TIER 1 [hiEnd, midEnd), TIER 0 [tierStart, tierEnd) = [midEnd, n)
|
|
563
|
+
const so = tiers?.segmentOffsets;
|
|
564
|
+
const hiEnd = so?.[1] ?? 0;
|
|
565
|
+
const midEnd = so?.[2] ?? 0;
|
|
468
566
|
return {
|
|
469
567
|
n,
|
|
470
568
|
dim: resolved.dim,
|
|
471
569
|
flags: adaptive ? FA2_FLAG_ADAPTIVE : 0,
|
|
472
|
-
tierStart:
|
|
570
|
+
tierStart: midEnd,
|
|
473
571
|
tierEnd: n,
|
|
474
572
|
iterationIndex: iteration,
|
|
475
573
|
seed: seedWord(resolved.seed),
|
|
@@ -481,33 +579,35 @@ export class FruchtermanReingoldModel implements ForceModel<FruchtermanReingoldO
|
|
|
481
579
|
center: [resolved.center[0], resolved.center[1], resolved.center[2], 0],
|
|
482
580
|
settleThreshold: resolved.settleThreshold,
|
|
483
581
|
extentFactor,
|
|
484
|
-
gridMax: 0,
|
|
485
|
-
levels: 0,
|
|
486
|
-
|
|
582
|
+
gridMax: grid?.g ?? 0,
|
|
583
|
+
levels: grid?.levels ?? 0,
|
|
584
|
+
arcBase: 0,
|
|
585
|
+
arcEnd: arcCountOf(core),
|
|
586
|
+
accumulate: 0,
|
|
587
|
+
hiEnd,
|
|
588
|
+
midEnd,
|
|
487
589
|
frK: resolved.k ?? 1 / Math.sqrt(n),
|
|
488
590
|
temperature: adaptive ? FR_START_TEMPERATURE : this.temperatureAt(iteration, resolved),
|
|
489
591
|
};
|
|
490
592
|
}
|
|
491
593
|
|
|
492
594
|
/**
|
|
493
|
-
* Records one iteration into the batch
|
|
494
|
-
*
|
|
495
|
-
*
|
|
496
|
-
*
|
|
595
|
+
* Records one iteration into the batch, stopping after stage `upTo` when given (PD-17: `upTo` names a position
|
|
596
|
+
* in the union list and the recording stops after the last stage recorded at or before it, so "K3" on the grid
|
|
597
|
+
* tier stops after K2 and "G5" on the exact tier after K3). The exact tier: K1, K2 (or the fill of force when
|
|
598
|
+
* arcCount === 0), K3, K5 in the batch's ONE compute pass (opened by the first call of a batch and reused by
|
|
599
|
+
* every later call with the same batch.id), then toScene in a second pass that ends it. The grid tier (PD-16):
|
|
600
|
+
* the passes `fr-k1` (K1), `fr-attraction` (K2's tiers) and `fr-grid` (G1-G7, K5) per iteration, then
|
|
601
|
+
* `fa2-to-scene`. With n === 0 nothing is recorded; a call before bind() completed is E_NOT_LOADED (never a
|
|
602
|
+
* silent no-op).
|
|
497
603
|
* @param batch - the batch being recorded
|
|
498
604
|
* @param slot - the UniformRing slot holding this iteration's Fa2Params
|
|
499
|
-
* @param tier -
|
|
605
|
+
* @param tier - the tier the simulation resolved at load() (the same rule bind() applied, PD-18)
|
|
500
606
|
* @param upTo - a stage name to stop after; undefined records every stage including toScene
|
|
501
607
|
*/
|
|
502
608
|
recordIteration(batch: CommandBatch, slot: number, tier: "exact" | "grid", upTo?: string): void {
|
|
503
|
-
if (tier === "grid") {
|
|
504
|
-
throw new WebGpuGraphError("E_UNSUPPORTED", "the grid repulsion tier lands in P4", {
|
|
505
|
-
feature: "repulsion.grid",
|
|
506
|
-
hint: 'pass repulsion: "exact"',
|
|
507
|
-
});
|
|
508
|
-
}
|
|
509
609
|
const resources = this.requireResources();
|
|
510
|
-
const stop = upTo === undefined ?
|
|
610
|
+
const stop = upTo === undefined ? STAGE_TO_SCENE : this.stageIndex(upTo);
|
|
511
611
|
const { bound } = this;
|
|
512
612
|
if (bound === null) {
|
|
513
613
|
if (resources.n === 0) {
|
|
@@ -520,28 +620,94 @@ export class FruchtermanReingoldModel implements ForceModel<FruchtermanReingoldO
|
|
|
520
620
|
);
|
|
521
621
|
}
|
|
522
622
|
const offset = resources.ring.offsetOf(slot);
|
|
623
|
+
if (tier === "grid") {
|
|
624
|
+
this.recordGridIteration(batch, bound, offset, stop);
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
const { k3, k3Bound } = bound;
|
|
628
|
+
if (k3 === null || k3Bound === null) {
|
|
629
|
+
throw new WebGpuGraphError("E_NOT_LOADED", "the Fruchterman-Reingold model was bound on the grid tier", {
|
|
630
|
+
state: "loaded",
|
|
631
|
+
});
|
|
632
|
+
}
|
|
523
633
|
const pass = this.openPass !== null && this.openPass.id === batch.id ? this.openPass.pass : batch.pass("fr");
|
|
524
634
|
this.openPass = { id: batch.id, pass };
|
|
525
635
|
bound.k1.dispatch(pass, bound.k1Bound, ONE_WORKGROUP, [offset]);
|
|
526
636
|
if (stop < 1) {
|
|
527
637
|
return;
|
|
528
638
|
}
|
|
529
|
-
|
|
530
|
-
bound.k2.dispatch(pass, bound.k2Bound, bound.plan, [offset]);
|
|
531
|
-
} else if (bound.fillForceBound !== null) {
|
|
532
|
-
bound.fill.dispatch(pass, bound.fillForceBound, bound.fillPlan, [0]);
|
|
533
|
-
}
|
|
639
|
+
this.recordK2(pass, bound, offset);
|
|
534
640
|
if (stop < 2) {
|
|
535
641
|
return;
|
|
536
642
|
}
|
|
537
|
-
|
|
538
|
-
if (stop <
|
|
643
|
+
k3.dispatch(pass, k3Bound, bound.plan, [offset]);
|
|
644
|
+
if (stop < STAGE_K5) {
|
|
539
645
|
return;
|
|
540
646
|
}
|
|
541
647
|
bound.k5.dispatch(pass, bound.k5Bound, bound.plan, [offset]);
|
|
542
|
-
if (stop <
|
|
648
|
+
if (stop < STAGE_TO_SCENE) {
|
|
543
649
|
return;
|
|
544
650
|
}
|
|
651
|
+
this.recordToScene(batch, bound, offset);
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* The grid tier's iteration (PD-16): three compute passes before toScene; no K4.
|
|
656
|
+
* @param batch - the batch being recorded
|
|
657
|
+
* @param bound - the bound model
|
|
658
|
+
* @param offset - the Fa2Params dynamic offset of the iteration
|
|
659
|
+
* @param stop - the FR_STAGES index to stop after
|
|
660
|
+
*/
|
|
661
|
+
private recordGridIteration(batch: CommandBatch, bound: BoundModel, offset: number, stop: number): void {
|
|
662
|
+
const { grid } = bound;
|
|
663
|
+
if (grid === null) {
|
|
664
|
+
throw new WebGpuGraphError("E_NOT_LOADED", "the Fruchterman-Reingold model was bound on the exact tier", {
|
|
665
|
+
state: "loaded",
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
this.openPass = null;
|
|
669
|
+
bound.k1.dispatch(batch.pass("fr-k1"), bound.k1Bound, ONE_WORKGROUP, [offset]);
|
|
670
|
+
if (stop < 1) {
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
this.recordK2(batch.pass("fr-attraction"), bound, offset);
|
|
674
|
+
if (stop < STAGE_G1) {
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
const pass = batch.pass("fr-grid");
|
|
678
|
+
const gridStop = stop < STAGE_K4 ? (FR_STAGES[stop] as GridStage) : undefined;
|
|
679
|
+
grid.recordRepulsion(pass, bound.n, offset, gridStop);
|
|
680
|
+
if (stop < STAGE_K5) {
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
bound.k5.dispatch(pass, bound.k5Bound, bound.plan, [offset]);
|
|
684
|
+
if (stop < STAGE_TO_SCENE) {
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
this.recordToScene(batch, bound, offset);
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
/**
|
|
691
|
+
* K2's tier dispatches, or the fill of force when the graph has no arcs (spec 7.5).
|
|
692
|
+
* @param pass - the open compute pass
|
|
693
|
+
* @param bound - the bound model
|
|
694
|
+
* @param offset - the Fa2Params dynamic offset
|
|
695
|
+
*/
|
|
696
|
+
private recordK2(pass: GPUComputePassEncoder, bound: BoundModel, offset: number): void {
|
|
697
|
+
if (bound.attraction !== null) {
|
|
698
|
+
recordAttraction(pass, bound.attraction, offset);
|
|
699
|
+
} else if (bound.fillForceBound !== null) {
|
|
700
|
+
bound.fill.dispatch(pass, bound.fillForceBound, bound.fillPlan, [0]);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
/**
|
|
705
|
+
* The toScene pass that ends the iteration's pass; the batch is complete after it, so nothing reuses the pass.
|
|
706
|
+
* @param batch - the batch
|
|
707
|
+
* @param bound - the bound model
|
|
708
|
+
* @param offset - the Fa2Params dynamic offset
|
|
709
|
+
*/
|
|
710
|
+
private recordToScene(batch: CommandBatch, bound: BoundModel, offset: number): void {
|
|
545
711
|
this.openPass = null;
|
|
546
712
|
const scenePass = batch.pass("fa2-to-scene");
|
|
547
713
|
bound.toScene.dispatch(scenePass, bound.toSceneBound, bound.plan, [offset]);
|
|
@@ -549,7 +715,7 @@ export class FruchtermanReingoldModel implements ForceModel<FruchtermanReingoldO
|
|
|
549
715
|
|
|
550
716
|
/**
|
|
551
717
|
* temperature = 0.1 (what stats reads before the first batch lands), kineticEnergy = 0; the temperature index
|
|
552
|
-
* restarts at 0 (PD-5).
|
|
718
|
+
* restarts at 0 (PD-5). On a grid load the frame of the first build (K1 folds nothing on the first iteration).
|
|
553
719
|
* @param state - the state writer of the simulation
|
|
554
720
|
*/
|
|
555
721
|
onLoad(state: StateWriter): void {
|
|
@@ -557,6 +723,9 @@ export class FruchtermanReingoldModel implements ForceModel<FruchtermanReingoldO
|
|
|
557
723
|
state.set("kineticEnergy", 0);
|
|
558
724
|
this.tempOrigin = 0;
|
|
559
725
|
this.pendingReheat = false;
|
|
726
|
+
if (this.nextGrid !== null) {
|
|
727
|
+
writeGridFrame(state, this.nextGrid, this.tuning.extentFactor);
|
|
728
|
+
}
|
|
560
729
|
}
|
|
561
730
|
|
|
562
731
|
/**
|
|
@@ -594,8 +763,9 @@ export class FruchtermanReingoldModel implements ForceModel<FruchtermanReingoldO
|
|
|
594
763
|
}
|
|
595
764
|
|
|
596
765
|
/**
|
|
597
|
-
* Decodes the state header and the k trace records of a completed batch into FruchtermanReingoldStats:
|
|
598
|
-
* tier
|
|
766
|
+
* Decodes the state header and the k trace records of a completed batch into FruchtermanReingoldStats:
|
|
767
|
+
* `repulsionTier` is the bound tier, the grid fields are the header's on the grid tier and null on the exact
|
|
768
|
+
* tier, msPerIteration null (the simulation owns the clock), the temperature K1 wrote.
|
|
599
769
|
* @param state - a DataView over the 256-byte state header
|
|
600
770
|
* @param trace - a DataView over the k Fa2Trace records of the batch
|
|
601
771
|
* @returns the stats
|
|
@@ -613,15 +783,16 @@ export class FruchtermanReingoldModel implements ForceModel<FruchtermanReingoldO
|
|
|
613
783
|
settledCount: scalar(record, "settledCount"),
|
|
614
784
|
});
|
|
615
785
|
}
|
|
786
|
+
const grid = this.resources?.tier === "grid";
|
|
616
787
|
return {
|
|
617
788
|
iteration: scalar(header, "iteration"),
|
|
618
789
|
meanDisplacement: scalar(header, "meanDisplacement"),
|
|
619
790
|
rmsRadius: scalar(header, "rmsRadius"),
|
|
620
791
|
layoutRadius: scalar(header, "radius"),
|
|
621
792
|
centroid: [centroid[0], centroid[1], centroid[2]],
|
|
622
|
-
repulsionTier: "exact",
|
|
623
|
-
maxCellOccupancy: null,
|
|
624
|
-
outsideGrid: null,
|
|
793
|
+
repulsionTier: grid ? "grid" : "exact",
|
|
794
|
+
maxCellOccupancy: grid ? scalar(header, "maxCellOccupancy") : null,
|
|
795
|
+
outsideGrid: grid ? scalar(header, "outsideGrid") : null,
|
|
625
796
|
msPerIteration: null,
|
|
626
797
|
temperature: scalar(header, "temperature"),
|
|
627
798
|
trace: records,
|
|
@@ -673,16 +844,26 @@ export class FruchtermanReingoldModel implements ForceModel<FruchtermanReingoldO
|
|
|
673
844
|
throw invalid("upTo", upTo, FR_STAGES.join(" | "));
|
|
674
845
|
}
|
|
675
846
|
|
|
676
|
-
/**
|
|
847
|
+
/** Releases the grid stage's lease and the bind groups (the simulation calls it from dispose() once every in-flight batch has settled). */
|
|
848
|
+
dispose(): void {
|
|
849
|
+
this.dropBound();
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
/** Drops the bind groups of the previous bind() (the buffers changed), releases the grid stage's lease and forgets the pass of a batch recorded before the rebind. */
|
|
677
853
|
private dropBound(): void {
|
|
678
854
|
this.openPass = null;
|
|
679
855
|
const { bound } = this;
|
|
680
856
|
if (bound === null) {
|
|
681
857
|
return;
|
|
682
858
|
}
|
|
683
|
-
for (const kernel of [bound.k1, bound.
|
|
859
|
+
for (const kernel of [bound.k1, bound.k5, bound.toScene, bound.fill]) {
|
|
860
|
+
kernel.invalidate();
|
|
861
|
+
}
|
|
862
|
+
bound.k3?.invalidate();
|
|
863
|
+
for (const [kernel] of bound.attraction?.kernels ?? []) {
|
|
684
864
|
kernel.invalidate();
|
|
685
865
|
}
|
|
866
|
+
bound.grid?.dispose();
|
|
686
867
|
this.bound = null;
|
|
687
868
|
}
|
|
688
869
|
}
|
|
@@ -713,8 +894,8 @@ function resolvePatch(
|
|
|
713
894
|
}
|
|
714
895
|
|
|
715
896
|
/**
|
|
716
|
-
* Spec 3.3 createFruchtermanReingold, verbatim: a GpuLayoutSimulation running Fruchterman-Reingold on the exact
|
|
717
|
-
* repulsion tier with the option defaults of spec 7.20 and the GPU-only tuning of GpuLayoutTuning.
|
|
897
|
+
* Spec 3.3 createFruchtermanReingold, verbatim: a GpuLayoutSimulation running Fruchterman-Reingold on the exact or
|
|
898
|
+
* the grid repulsion tier (spec 7.8) with the option defaults of spec 7.20 and the GPU-only tuning of GpuLayoutTuning.
|
|
718
899
|
* @param ctx - the context (E_DISPOSED / E_DEVICE_LOST through assertReady)
|
|
719
900
|
* @param options - the Fruchterman-Reingold options and the GPU-only tuning knobs in one record
|
|
720
901
|
* @returns the simulation in state "created"; load() next
|