@graphty/webgpu-graph-algorithms 0.4.1 → 0.5.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 +27 -21
- package/dist/browser.js +1 -1
- package/dist/chunks/{context-CRbw2Wyo.js → context-BR7fx3vR.js} +47 -5
- package/dist/chunks/context-BR7fx3vR.js.map +1 -0
- package/dist/node.js +1 -1
- package/dist/src/accelerator.d.ts +15 -12
- package/dist/src/accelerator.d.ts.map +1 -1
- package/dist/src/accelerator.js +35 -12
- package/dist/src/accelerator.js.map +1 -1
- package/dist/src/constants.d.ts +47 -0
- package/dist/src/constants.d.ts.map +1 -1
- package/dist/src/constants.js +47 -0
- package/dist/src/constants.js.map +1 -1
- package/dist/src/index.d.ts +8 -5
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +9 -5
- package/dist/src/index.js.map +1 -1
- package/dist/src/kernel/prelude.d.ts.map +1 -1
- package/dist/src/kernel/prelude.js +4 -1
- package/dist/src/kernel/prelude.js.map +1 -1
- package/dist/src/kernels.d.ts +3 -3
- package/dist/src/kernels.d.ts.map +1 -1
- package/dist/src/kernels.js +27 -11
- package/dist/src/kernels.js.map +1 -1
- package/dist/src/layouts/force-simulation.d.ts +3 -1
- package/dist/src/layouts/force-simulation.d.ts.map +1 -1
- package/dist/src/layouts/force-simulation.js +13 -3
- package/dist/src/layouts/force-simulation.js.map +1 -1
- package/dist/src/layouts/forceatlas2.d.ts +1 -3
- package/dist/src/layouts/forceatlas2.d.ts.map +1 -1
- package/dist/src/layouts/forceatlas2.js +2 -191
- package/dist/src/layouts/forceatlas2.js.map +1 -1
- package/dist/src/layouts/fruchterman-reingold.d.ts +195 -0
- package/dist/src/layouts/fruchterman-reingold.d.ts.map +1 -0
- package/dist/src/layouts/fruchterman-reingold.js +579 -0
- package/dist/src/layouts/fruchterman-reingold.js.map +1 -0
- package/dist/src/layouts/model-common.d.ts +102 -0
- package/dist/src/layouts/model-common.d.ts.map +1 -0
- package/dist/src/layouts/model-common.js +198 -0
- package/dist/src/layouts/model-common.js.map +1 -0
- package/dist/src/layouts/spring-electrical.d.ts +179 -0
- package/dist/src/layouts/spring-electrical.d.ts.map +1 -0
- package/dist/src/layouts/spring-electrical.js +508 -0
- package/dist/src/layouts/spring-electrical.js.map +1 -0
- package/dist/src/types/accelerator.d.ts +14 -157
- package/dist/src/types/accelerator.d.ts.map +1 -1
- package/dist/src/types/accelerator.js +4 -5
- package/dist/src/types/accelerator.js.map +1 -1
- package/dist/src/types/algorithms.d.ts +1 -1
- package/dist/src/types/algorithms.js +1 -1
- package/dist/src/types/layout.d.ts +32 -0
- package/dist/src/types/layout.d.ts.map +1 -1
- package/dist/src/types/options.d.ts +43 -1
- package/dist/src/types/options.d.ts.map +1 -1
- package/dist/src/wgsl/fa2-attraction.wgsl.d.ts +5 -2
- package/dist/src/wgsl/fa2-attraction.wgsl.d.ts.map +1 -1
- package/dist/src/wgsl/fa2-attraction.wgsl.js +6 -1
- package/dist/src/wgsl/fa2-attraction.wgsl.js.map +1 -1
- package/dist/src/wgsl/fa2-integrate.wgsl.d.ts +7 -2
- package/dist/src/wgsl/fa2-integrate.wgsl.d.ts.map +1 -1
- package/dist/src/wgsl/fa2-integrate.wgsl.js +28 -2
- package/dist/src/wgsl/fa2-integrate.wgsl.js.map +1 -1
- package/dist/src/wgsl/fa2-repulsion-exact.wgsl.d.ts +4 -2
- package/dist/src/wgsl/fa2-repulsion-exact.wgsl.d.ts.map +1 -1
- package/dist/src/wgsl/fa2-repulsion-exact.wgsl.js +14 -5
- package/dist/src/wgsl/fa2-repulsion-exact.wgsl.js.map +1 -1
- package/dist/src/wgsl/fa2-stats-finalize.wgsl.d.ts +8 -1
- package/dist/src/wgsl/fa2-stats-finalize.wgsl.d.ts.map +1 -1
- package/dist/src/wgsl/fa2-stats-finalize.wgsl.js +33 -0
- package/dist/src/wgsl/fa2-stats-finalize.wgsl.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/webgpu-graph-algorithms.js +1358 -284
- package/dist/webgpu-graph-algorithms.js.map +1 -1
- package/package.json +6 -3
- package/src/accelerator.ts +49 -14
- package/src/constants.ts +70 -0
- package/src/index.ts +19 -8
- package/src/kernel/prelude.ts +6 -0
- package/src/kernels.ts +27 -11
- package/src/layouts/force-simulation.ts +20 -3
- package/src/layouts/forceatlas2.ts +17 -220
- package/src/layouts/fruchterman-reingold.ts +737 -0
- package/src/layouts/model-common.ts +228 -0
- package/src/layouts/spring-electrical.ts +666 -0
- package/src/types/accelerator.ts +58 -174
- package/src/types/algorithms.ts +1 -1
- package/src/types/layout.ts +36 -0
- package/src/types/options.ts +45 -1
- package/src/wgsl/fa2-attraction.wgsl.ts +6 -1
- package/src/wgsl/fa2-integrate.wgsl.ts +28 -2
- package/src/wgsl/fa2-repulsion-exact.wgsl.ts +14 -5
- package/src/wgsl/fa2-stats-finalize.wgsl.ts +33 -0
- package/dist/chunks/context-CRbw2Wyo.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@graphty/webgpu-graph-algorithms",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "WebGPU-accelerated graph algorithms and layouts over the @graphty/graph-format snapshot, for Node (Dawn) and browsers",
|
|
5
5
|
"author": "Adam Powers <apowers@ato.ms>",
|
|
6
6
|
"type": "module",
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"homepage": "https://github.com/graphty-org/graphty-monorepo/tree/master/webgpu-graph-algorithms#readme",
|
|
61
61
|
"dependencies": {
|
|
62
62
|
"@webgpu/types": "^0.1.72",
|
|
63
|
-
"@graphty/graph-format": "^1.0.
|
|
63
|
+
"@graphty/graph-format": "^1.0.2"
|
|
64
64
|
},
|
|
65
65
|
"peerDependencies": {
|
|
66
66
|
"@graphty/algorithms": "^1.0.0",
|
|
@@ -84,13 +84,16 @@
|
|
|
84
84
|
"@vitest/coverage-v8": "^3.2.4",
|
|
85
85
|
"@vitest/ui": "^3.2.4",
|
|
86
86
|
"fast-check": "^4.2.0",
|
|
87
|
+
"ngraph.forcelayout": "^3.3.1",
|
|
88
|
+
"ngraph.graph": "^20.0.1",
|
|
87
89
|
"playwright": "^1.54.1",
|
|
88
90
|
"tsx": "^4.20.3",
|
|
89
91
|
"typescript": "^5.9.3",
|
|
90
92
|
"vite": "^7.0.5",
|
|
91
93
|
"vitest": "^3.2.4",
|
|
92
94
|
"webgpu": "0.4.0",
|
|
93
|
-
"@graphty/layout": "^1.
|
|
95
|
+
"@graphty/layout": "^1.8.0",
|
|
96
|
+
"@graphty/algorithms": "^1.8.1"
|
|
94
97
|
},
|
|
95
98
|
"scripts": {
|
|
96
99
|
"build": "tsc -p tsconfig.build.json",
|
package/src/accelerator.ts
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* createAccelerator (spec 3.3, 9; contract 3.14): the injectable object that satisfies the CPU packages'
|
|
3
|
-
* AlgorithmAccelerator and LayoutAccelerator interfaces (spec 9.2, 9.3
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* `forceAtlas2`, `release` and `dispose
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
3
|
+
* AlgorithmAccelerator and LayoutAccelerator interfaces (spec 9.2, 9.3). Both are the REAL declarations,
|
|
4
|
+
* `LayoutAccelerator` from `@graphty/layout` (W1b, layout half) and `AlgorithmAccelerator` from
|
|
5
|
+
* `@graphty/algorithms` (W1b, algorithms half), `import type`d by src/types/accelerator.ts, so the object built
|
|
6
|
+
* here is checked against the CPU packages' own contracts. It carries P3's `forceAtlas2`, `release` and `dispose`,
|
|
7
|
+
* P5's `fruchtermanReingold` and `springElectrical` (the two other layout members of spec 9.3, landed together once
|
|
8
|
+
* both models were green, P5 PD-19) and P7's seven algorithm members (spec 8.2, 8.3; M8b-T8, PD-14) and nothing
|
|
9
|
+
* else: the CPU-side dispatchers (`accelerated()`, `createSimulation()`) test `acc.betweennessCentrality !== undefined`
|
|
10
|
+
* / `acc.fruchtermanReingold !== undefined` and route to the CPU when the member is absent (spec 2.4 row "method
|
|
11
|
+
* missing"), so a method the GPU does not implement must not exist here -- never a throwing stub. The remaining
|
|
12
|
+
* algorithm members arrive one per shipped algorithm from P8.
|
|
12
13
|
*/
|
|
13
14
|
|
|
14
15
|
import { type F32, type F64, type GraphSnapshot } from "@graphty/graph-format";
|
|
@@ -18,6 +19,8 @@ import { pageRank, personalizedPageRank } from "./algorithms/pagerank.js";
|
|
|
18
19
|
import { eigenvectorCentrality, hits, katzCentrality } from "./algorithms/spectral.js";
|
|
19
20
|
import { type GpuContext } from "./context.js";
|
|
20
21
|
import { createForceAtlas2 } from "./layouts/forceatlas2.js";
|
|
22
|
+
import { createFruchtermanReingold } from "./layouts/fruchterman-reingold.js";
|
|
23
|
+
import { createSpringElectrical } from "./layouts/spring-electrical.js";
|
|
21
24
|
import { type AcceleratorOptions, type GpuAccelerator } from "./types/accelerator.js";
|
|
22
25
|
import {
|
|
23
26
|
type ComponentsOptions,
|
|
@@ -30,8 +33,18 @@ import {
|
|
|
30
33
|
type KatzOptions,
|
|
31
34
|
type PageRankOptions,
|
|
32
35
|
} from "./types/algorithms.js";
|
|
33
|
-
import {
|
|
34
|
-
|
|
36
|
+
import {
|
|
37
|
+
type ForceAtlas2Stats,
|
|
38
|
+
type FruchtermanReingoldStats,
|
|
39
|
+
type GpuLayoutSimulation,
|
|
40
|
+
type GpuLayoutTuning,
|
|
41
|
+
type SpringElectricalStats,
|
|
42
|
+
} from "./types/layout.js";
|
|
43
|
+
import {
|
|
44
|
+
type ForceAtlas2Options,
|
|
45
|
+
type FruchtermanReingoldOptions,
|
|
46
|
+
type SpringElectricalOptions,
|
|
47
|
+
} from "./types/options.js";
|
|
35
48
|
|
|
36
49
|
/** The `algorithms` record of AcceleratorOptions (spec 3.3), named for the copy helpers. */
|
|
37
50
|
type AlgorithmDefaults = NonNullable<AcceleratorOptions["algorithms"]>;
|
|
@@ -90,7 +103,8 @@ function freezeOptions(options: AcceleratorOptions | undefined): Readonly<Accele
|
|
|
90
103
|
|
|
91
104
|
/**
|
|
92
105
|
* Spec 3.3 createAccelerator, verbatim: the object implementing AlgorithmAccelerator & LayoutAccelerator
|
|
93
|
-
* structurally; P3's forceAtlas2, release and dispose
|
|
106
|
+
* structurally; P3's forceAtlas2, release and dispose, P5's fruchtermanReingold and springElectrical (the same
|
|
107
|
+
* `{ ...o, ...options.layout }` shape as forceAtlas2) plus P7's seven algorithm members, each a delegation to
|
|
94
108
|
* its algorithm with `ctx.assertReady()` first. The accelerator's algorithm defaults are not consulted by any of
|
|
95
109
|
* them: only `betweenness` has any, and it belongs to P9. One per call (the app creates one and injects
|
|
96
110
|
* it, spec 2.4); `kind` is "webgpu"; `options` is a frozen deep copy; `forceAtlas2(o)` is
|
|
@@ -100,8 +114,9 @@ function freezeOptions(options: AcceleratorOptions | undefined): Readonly<Accele
|
|
|
100
114
|
* @param ctx - the context every simulation the accelerator creates runs on
|
|
101
115
|
* @param options - GPU-only defaults inherited by every simulation (`layout`) and, from P9, the algorithm defaults
|
|
102
116
|
* @returns the injectable accelerator
|
|
103
|
-
* @throws E_DISPOSED / E_DEVICE_LOST from `ctx.assertReady()` (here and inside
|
|
104
|
-
* also throws what createForceAtlas2 throws (E_UNSUPPORTED for `nodeSize`, E_INVALID_ARGUMENT for a bad range)
|
|
117
|
+
* @throws E_DISPOSED / E_DEVICE_LOST from `ctx.assertReady()` (here and inside every creating member); `forceAtlas2()`
|
|
118
|
+
* also throws what createForceAtlas2 throws (E_UNSUPPORTED for `nodeSize`, E_INVALID_ARGUMENT for a bad range), and
|
|
119
|
+
* `fruchtermanReingold()` / `springElectrical()` what their factories throw (E_INVALID_ARGUMENT for a bad range)
|
|
105
120
|
*/
|
|
106
121
|
export function createAccelerator(ctx: GpuContext, options?: AcceleratorOptions): GpuAccelerator {
|
|
107
122
|
ctx.assertReady();
|
|
@@ -119,6 +134,26 @@ export function createAccelerator(ctx: GpuContext, options?: AcceleratorOptions)
|
|
|
119
134
|
ctx.assertReady();
|
|
120
135
|
return createForceAtlas2(ctx, { ...o, ...frozen.layout });
|
|
121
136
|
},
|
|
137
|
+
/**
|
|
138
|
+
* The Fruchterman-Reingold simulation with this accelerator's layout tuning (spec 3.3, 7.20; P5).
|
|
139
|
+
* @param o - the CPU option type (spec 9.3 FruchtermanReingoldOptions); GPU tuning keys come from `options.layout`
|
|
140
|
+
* @returns a fresh simulation in state "created"
|
|
141
|
+
*/
|
|
142
|
+
fruchtermanReingold(
|
|
143
|
+
o?: FruchtermanReingoldOptions,
|
|
144
|
+
): GpuLayoutSimulation<FruchtermanReingoldOptions, FruchtermanReingoldStats> {
|
|
145
|
+
ctx.assertReady();
|
|
146
|
+
return createFruchtermanReingold(ctx, { ...o, ...frozen.layout });
|
|
147
|
+
},
|
|
148
|
+
/**
|
|
149
|
+
* The spring-electrical preset with this accelerator's layout tuning (spec 3.3, 7.20; P5).
|
|
150
|
+
* @param o - the CPU option type (spec 9.3 SpringElectricalOptions, ngraph's names)
|
|
151
|
+
* @returns a fresh simulation in state "created"
|
|
152
|
+
*/
|
|
153
|
+
springElectrical(o?: SpringElectricalOptions): GpuLayoutSimulation<SpringElectricalOptions, SpringElectricalStats> {
|
|
154
|
+
ctx.assertReady();
|
|
155
|
+
return createSpringElectrical(ctx, { ...o, ...frozen.layout });
|
|
156
|
+
},
|
|
122
157
|
/**
|
|
123
158
|
* PageRank on the device (spec 8.2; contract 3.14).
|
|
124
159
|
* @param gs - the snapshot
|
package/src/constants.ts
CHANGED
|
@@ -114,3 +114,73 @@ export const FA2_DISTANCE_FLOOR_SQ = 0.0001;
|
|
|
114
114
|
export const FA2_COINCIDENT_SQ = 1e-8;
|
|
115
115
|
/** Bits of Fa2Params.flags (contract 4.4). */
|
|
116
116
|
export const FA2_FLAG_FIRST = 1;
|
|
117
|
+
/** Fa2Params.flags bit: the Fruchterman-Reingold temperature is the adaptive one in the state block, not the uniform's (the `cooling: "adaptive"` option). */
|
|
118
|
+
export const FA2_FLAG_ADAPTIVE = 2;
|
|
119
|
+
/** The Fruchterman-Reingold loop's starting temperature (spec 7.20; `layout/src/simulation/fruchterman-reingold.ts:30`). */
|
|
120
|
+
export const FR_START_TEMPERATURE = 0.1;
|
|
121
|
+
/** The reheat point of spec 7.20: the temperature index a reheat restarts at, as a fraction of `iterations`. */
|
|
122
|
+
export const FR_REHEAT_FRACTION = 0.7;
|
|
123
|
+
/** Adaptive cooling (Yifan Hu 2005, section 3.2): the temperature is multiplied by this when the force energy rose, divided by it after FR_COOLING_PATIENCE consecutive falls. */
|
|
124
|
+
export const FR_COOLING_STEP = 0.9;
|
|
125
|
+
/** Adaptive cooling: consecutive iterations of falling energy before the temperature grows. */
|
|
126
|
+
export const FR_COOLING_PATIENCE = 5;
|
|
127
|
+
/** Adaptive cooling: the iteration budget when `iterations` is not given (the schedule no longer needs one; this is the cap on a run that never settles). */
|
|
128
|
+
export const FR_ADAPTIVE_MAX_ITERATIONS = 10_000;
|
|
129
|
+
/** Fruchterman-Reingold defaults (spec 7.20, 9.3; the CPU simulation's, `layout/src/simulation/fruchterman-reingold.ts:32`): `k` null = `1 / sqrt(n)`. */
|
|
130
|
+
export const FR_DEFAULTS: Readonly<{
|
|
131
|
+
k: null;
|
|
132
|
+
iterations: 50;
|
|
133
|
+
fixed: null;
|
|
134
|
+
dim: 2;
|
|
135
|
+
scale: 1;
|
|
136
|
+
settleThreshold: 0.001;
|
|
137
|
+
settleWindow: 10;
|
|
138
|
+
iterationsPerStep: 1;
|
|
139
|
+
maxInFlight: 2;
|
|
140
|
+
cooling: "linear";
|
|
141
|
+
}> = Object.freeze({
|
|
142
|
+
k: null,
|
|
143
|
+
iterations: 50,
|
|
144
|
+
fixed: null,
|
|
145
|
+
dim: 2,
|
|
146
|
+
scale: 1,
|
|
147
|
+
settleThreshold: 0.001,
|
|
148
|
+
settleWindow: 10,
|
|
149
|
+
iterationsPerStep: 1,
|
|
150
|
+
maxInFlight: 2,
|
|
151
|
+
cooling: "linear",
|
|
152
|
+
});
|
|
153
|
+
/**
|
|
154
|
+
* Spring-electrical size scaling: when `gravity` or `springCoefficient` is left to its default, ngraph's constant is
|
|
155
|
+
* multiplied by min(1, SE_SCALE_REFERENCE_NODES / n). ngraph's values were tuned for graphs of a few hundred nodes;
|
|
156
|
+
* on tens of thousands the per-node forces are so large that every node moves at the unit speed clamp and the layout
|
|
157
|
+
* never comes to rest (measured on the 58k-node Brightkite graph: 3,350 iterations to the settle rule with the
|
|
158
|
+
* kinetic energy still at the clamp; a uniform 1/1000 factor settled in 1,000 with the energy decayed 25x).
|
|
159
|
+
*/
|
|
160
|
+
export const SE_SCALE_REFERENCE_NODES = 300;
|
|
161
|
+
/** Spring-electrical defaults: ngraph.forcelayout 3.3.1's (`lib/createPhysicsSimulator.js:29,34,40,54,59`; spec 7.20), plus the shared simulation defaults. `gravity` is ngraph's Coulomb constant: negative repels. */
|
|
162
|
+
export const SE_DEFAULTS: Readonly<{
|
|
163
|
+
springLength: 10;
|
|
164
|
+
springCoefficient: 0.8;
|
|
165
|
+
gravity: -12;
|
|
166
|
+
dragCoefficient: 0.9;
|
|
167
|
+
timeStep: 0.5;
|
|
168
|
+
dim: 2;
|
|
169
|
+
scale: 1;
|
|
170
|
+
settleThreshold: 0.001;
|
|
171
|
+
settleWindow: 10;
|
|
172
|
+
iterationsPerStep: 1;
|
|
173
|
+
maxInFlight: 2;
|
|
174
|
+
}> = Object.freeze({
|
|
175
|
+
springLength: 10,
|
|
176
|
+
springCoefficient: 0.8,
|
|
177
|
+
gravity: -12,
|
|
178
|
+
dragCoefficient: 0.9,
|
|
179
|
+
timeStep: 0.5,
|
|
180
|
+
dim: 2,
|
|
181
|
+
scale: 1,
|
|
182
|
+
settleThreshold: 0.001,
|
|
183
|
+
settleWindow: 10,
|
|
184
|
+
iterationsPerStep: 1,
|
|
185
|
+
maxInFlight: 2,
|
|
186
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -7,20 +7,24 @@
|
|
|
7
7
|
* ForceSimulation, ForceAtlas2Model: tests import them from their files). P0: the error class, the constants and
|
|
8
8
|
* isSoftwareAdapter. P1 adds GpuContext and degree (values) and the context / run / profiler types. P2 adds nothing
|
|
9
9
|
* (Lease, CommandBatch, UniformRing are internal). P3 adds the layout factory, the accelerator, the two default
|
|
10
|
-
* tables, the seeder and the layout / accelerator types.
|
|
11
|
-
* test/types/public-api.test-d.ts the type list; P4+
|
|
10
|
+
* tables, the seeder and the layout / accelerator types. P5 adds the two factories, the two default tables and the
|
|
11
|
+
* two stats records. test/index.test.ts pins the value list and test/types/public-api.test-d.ts the type list; P4+
|
|
12
|
+
* extends both. This comment must never spell the internal
|
|
12
13
|
* JSDoc tag: it is the leading comment of the first export statement, and stripInternal would drop that statement
|
|
13
14
|
* from the emitted declarations.
|
|
14
15
|
*/
|
|
15
16
|
|
|
16
|
-
// ==================== constants and errors (P0; FA2_DEFAULTS / LAYOUT_TUNING_DEFAULTS public from P3
|
|
17
|
+
// ==================== constants and errors (P0; FA2_DEFAULTS / LAYOUT_TUNING_DEFAULTS public from P3, FR_DEFAULTS /
|
|
18
|
+
// SE_DEFAULTS from P5)
|
|
17
19
|
export {
|
|
18
20
|
ARC_WINDOW_ALIGN,
|
|
19
21
|
EXACT_MAX_NODES,
|
|
20
22
|
FA2_DEFAULTS,
|
|
23
|
+
FR_DEFAULTS,
|
|
21
24
|
LAYOUT_TUNING_DEFAULTS,
|
|
22
25
|
MAX_1D_ITEMS,
|
|
23
26
|
MAX_WORKGROUPS_PER_DIM,
|
|
27
|
+
SE_DEFAULTS,
|
|
24
28
|
STORAGE_ALIGN,
|
|
25
29
|
WORKGROUP_SIZE,
|
|
26
30
|
} from "./constants.js";
|
|
@@ -45,24 +49,27 @@ export { connectedComponents } from "./algorithms/components.js";
|
|
|
45
49
|
export { pageRank, personalizedPageRank } from "./algorithms/pagerank.js";
|
|
46
50
|
export { eigenvectorCentrality, hits, katzCentrality } from "./algorithms/spectral.js";
|
|
47
51
|
|
|
48
|
-
// ==================== layouts and the accelerator (P3)
|
|
52
|
+
// ==================== layouts and the accelerator (P3; the two P5 factories)
|
|
49
53
|
export { createAccelerator } from "./accelerator.js";
|
|
50
54
|
export { createForceAtlas2 } from "./layouts/forceatlas2.js";
|
|
55
|
+
export { createFruchtermanReingold } from "./layouts/fruchterman-reingold.js";
|
|
51
56
|
export { seedPositions } from "./layouts/seed.js";
|
|
57
|
+
export { createSpringElectrical } from "./layouts/spring-electrical.js";
|
|
52
58
|
|
|
53
|
-
// ==================== types: the accelerator surface
|
|
54
|
-
// @graphty/algorithms
|
|
59
|
+
// ==================== types: the accelerator surface and the re-exported declarations of @graphty/layout (spec 9.3)
|
|
60
|
+
// and @graphty/algorithms (spec 9.2), both `import type` since W1b (D27)
|
|
55
61
|
export type {
|
|
56
62
|
AcceleratorOptions,
|
|
57
63
|
AlgorithmAccelerator,
|
|
58
64
|
ApspResultLike,
|
|
59
65
|
BellmanFordResultLike,
|
|
66
|
+
BetweennessAcceleratorOptions,
|
|
60
67
|
BfsResultLike,
|
|
61
68
|
CommunityResultLike,
|
|
62
69
|
CorenessResultLike,
|
|
63
|
-
CpuAlgorithmOptions,
|
|
64
70
|
EdgeScoresResultLike,
|
|
65
71
|
GpuAccelerator,
|
|
72
|
+
HitsOptionsLike,
|
|
66
73
|
HitsResultLike,
|
|
67
74
|
LabelResultLike,
|
|
68
75
|
LayoutAccelerator,
|
|
@@ -100,14 +107,18 @@ export type {
|
|
|
100
107
|
RaisableLimit,
|
|
101
108
|
} from "./types/context.js";
|
|
102
109
|
|
|
103
|
-
// ==================== types: layouts (P3)
|
|
110
|
+
// ==================== types: layouts (P3; the P5 stats and trace records)
|
|
104
111
|
export type {
|
|
105
112
|
ForceAtlas2Stats,
|
|
106
113
|
ForceAtlas2TraceRecord,
|
|
114
|
+
FruchtermanReingoldStats,
|
|
115
|
+
FruchtermanReingoldTraceRecord,
|
|
107
116
|
GpuLayoutSimulation,
|
|
108
117
|
GpuLayoutTuning,
|
|
109
118
|
LayoutStatsBase,
|
|
110
119
|
RunOptions,
|
|
120
|
+
SpringElectricalStats,
|
|
121
|
+
SpringElectricalTraceRecord,
|
|
111
122
|
} from "./types/layout.js";
|
|
112
123
|
export type {
|
|
113
124
|
CommonLayoutOptions,
|
package/src/kernel/prelude.ts
CHANGED
|
@@ -15,7 +15,10 @@ import {
|
|
|
15
15
|
FA2_COINCIDENT_SQ,
|
|
16
16
|
FA2_DISTANCE_FLOOR,
|
|
17
17
|
FA2_DISTANCE_FLOOR_SQ,
|
|
18
|
+
FA2_FLAG_ADAPTIVE,
|
|
18
19
|
FA2_FLAG_FIRST,
|
|
20
|
+
FR_COOLING_PATIENCE,
|
|
21
|
+
FR_COOLING_STEP,
|
|
19
22
|
MAX_WORKGROUPS_PER_DIM,
|
|
20
23
|
U32_MAX,
|
|
21
24
|
WORKGROUP_SIZE,
|
|
@@ -49,6 +52,9 @@ const FA2_DIST_FLOOR: f32 = ${wgslF32Literal(FA2_DISTANCE_FLOOR)};
|
|
|
49
52
|
const FA2_DIST_FLOOR_SQ: f32 = ${wgslF32Literal(FA2_DISTANCE_FLOOR_SQ)};
|
|
50
53
|
const FA2_COINCIDENT_SQ: f32 = ${wgslF32Literal(FA2_COINCIDENT_SQ)};
|
|
51
54
|
const FA2_FLAG_FIRST: u32 = ${FA2_FLAG_FIRST}u;
|
|
55
|
+
const FA2_FLAG_ADAPTIVE: u32 = ${FA2_FLAG_ADAPTIVE}u;
|
|
56
|
+
const FR_COOLING_STEP: f32 = ${wgslF32Literal(FR_COOLING_STEP)};
|
|
57
|
+
const FR_COOLING_PATIENCE: u32 = ${FR_COOLING_PATIENCE}u;
|
|
52
58
|
const F32_MAX: f32 = 0x1.fffffep+127;
|
|
53
59
|
override WG: u32 = ${WORKGROUP_SIZE}u;
|
|
54
60
|
override USE_PERM: bool = false;
|
package/src/kernels.ts
CHANGED
|
@@ -101,7 +101,7 @@ export const FILL_PARAMS: UniformBlock = UniformBlock.define("FillParams", [
|
|
|
101
101
|
["pad0", "u32"],
|
|
102
102
|
]);
|
|
103
103
|
|
|
104
|
-
/** `Fa2Params` (uniform,
|
|
104
|
+
/** `Fa2Params` (uniform, 128 B; spec 7.3): the per-iteration ForceAtlas2 parameters -- `n` @0, `dim` @4, `flags` @8 (bit 0 = FA2_FLAG_FIRST), `tierStart` @12, `tierEnd` @16, `iterationIndex` @20, `seed` @24, `nearMax` @28, `scalingRatio` @32, `gravity` @36, `jitterTolerance` @40, `scale` @44, `center` @48 (xyz, w 0), `settleThreshold` @64, `extentFactor` @68, `gridMax` @72, `levels` @76, `pad` @80 (reserved for the P4 GridSpec); the P5 model fields (PD-3): `frK` @96 (the FR optimal distance), `temperature` @100 (the FR temperature of this iteration), `springLength` @104, `springCoefficient` @108, `coulomb` @112 (ngraph's `gravity`, negative repels), `dragCoefficient` @116, `timeStep` @120, `pad1` @124; 128 B. */
|
|
105
105
|
export const FA2_PARAMS: UniformBlock = UniformBlock.define("Fa2Params", [
|
|
106
106
|
["n", "u32"],
|
|
107
107
|
["dim", "u32"],
|
|
@@ -121,9 +121,17 @@ export const FA2_PARAMS: UniformBlock = UniformBlock.define("Fa2Params", [
|
|
|
121
121
|
["gridMax", "u32"],
|
|
122
122
|
["levels", "u32"],
|
|
123
123
|
["pad", "vec4f"],
|
|
124
|
+
["frK", "f32"],
|
|
125
|
+
["temperature", "f32"],
|
|
126
|
+
["springLength", "f32"],
|
|
127
|
+
["springCoefficient", "f32"],
|
|
128
|
+
["coulomb", "f32"],
|
|
129
|
+
["dragCoefficient", "f32"],
|
|
130
|
+
["timeStep", "f32"],
|
|
131
|
+
["pad1", "f32"],
|
|
124
132
|
]);
|
|
125
133
|
|
|
126
|
-
/** `Fa2State` (storage, padded to STATE_HEADER_BYTES = 256; spec 7.3): the device-resident controller state the finalize kernels write and the host reads back for stats -- `speed` @0, `speedEfficiency` @4, `swing` @8, `traction` @12, `centroid` @16, `rmsRadius` @32, `radius` @36, `meanDisplacement` @40, `iteration` @44, `min` @48, `max` @64, `gridMin` @80 (P4), `eps` @96 (P4), `settledCount` @100, `outsideGrid` @104 (P4), `maxCellOccupancy` @108 (P4), `reserved0` .. `reserved8` @
|
|
134
|
+
/** `Fa2State` (storage, padded to STATE_HEADER_BYTES = 256; spec 7.3): the device-resident controller state the finalize kernels write and the host reads back for stats -- `speed` @0, `speedEfficiency` @4, `swing` @8, `traction` @12, `centroid` @16, `rmsRadius` @32, `radius` @36, `meanDisplacement` @40, `iteration` @44, `min` @48, `max` @64, `gridMin` @80 (P4), `eps` @96 (P4), `settledCount` @100, `outsideGrid` @104 (P4), `maxCellOccupancy` @108 (P4), `temperature` @112 (FR, written by K1 under STATS_MODE 1), `kineticEnergy` @116 (the preset, K1 under STATS_MODE 2), `reserved0` @120 (vec2f), `reserved1` .. `reserved8` @128 .. @240. */
|
|
127
135
|
export const FA2_STATE: UniformBlock = UniformBlock.define(
|
|
128
136
|
"Fa2State",
|
|
129
137
|
[
|
|
@@ -143,7 +151,10 @@ export const FA2_STATE: UniformBlock = UniformBlock.define(
|
|
|
143
151
|
["settledCount", "u32"],
|
|
144
152
|
["outsideGrid", "u32"],
|
|
145
153
|
["maxCellOccupancy", "u32"],
|
|
146
|
-
["
|
|
154
|
+
["temperature", "f32"],
|
|
155
|
+
["kineticEnergy", "f32"],
|
|
156
|
+
["frEnergy", "f32"],
|
|
157
|
+
["frProgress", "u32"],
|
|
147
158
|
["reserved1", "vec4f"],
|
|
148
159
|
["reserved2", "vec4f"],
|
|
149
160
|
["reserved3", "vec4f"],
|
|
@@ -156,7 +167,7 @@ export const FA2_STATE: UniformBlock = UniformBlock.define(
|
|
|
156
167
|
{ layout: "storage", padTo: STATE_HEADER_BYTES },
|
|
157
168
|
);
|
|
158
169
|
|
|
159
|
-
/** `Fa2Trace` (storage record, 32 B; spec 7.3): one per-iteration trace record -- `swing` @0, `traction` @4, `speed` @8, `speedEfficiency` @12 (written by K4), `meanDisplacement` @16, `settledCount` @20, `iteration` @24 (written by K1), `
|
|
170
|
+
/** `Fa2Trace` (storage record, 32 B; spec 7.3): one per-iteration trace record -- `swing` @0, `traction` @4, `speed` @8, `speedEfficiency` @12 (written by K4), `meanDisplacement` @16, `settledCount` @20, `iteration` @24 (written by K1), `modelScalar` @28 (K1: the temperature under STATS_MODE 1, the kinetic energy under 2, 0 under 0); the trace region is `array<Fa2Trace>` at byte offset STATE_HEADER_BYTES of the state buffer. */
|
|
160
171
|
export const FA2_TRACE: UniformBlock = UniformBlock.define(
|
|
161
172
|
"Fa2Trace",
|
|
162
173
|
[
|
|
@@ -167,7 +178,7 @@ export const FA2_TRACE: UniformBlock = UniformBlock.define(
|
|
|
167
178
|
["meanDisplacement", "f32"],
|
|
168
179
|
["settledCount", "u32"],
|
|
169
180
|
["iteration", "u32"],
|
|
170
|
-
["
|
|
181
|
+
["modelScalar", "f32"],
|
|
171
182
|
],
|
|
172
183
|
{ layout: "storage" },
|
|
173
184
|
);
|
|
@@ -335,7 +346,7 @@ const SEGMENTED_REDUCE: KernelEntry = {
|
|
|
335
346
|
phase: "P2",
|
|
336
347
|
};
|
|
337
348
|
|
|
338
|
-
/** `fa2-stats-finalize` (K1, 3.10.1): the one-workgroup fold of the previous integrate's partials into the state block and the K1 half of the trace record; 3 storage bindings; calls the reduction helpers. */
|
|
349
|
+
/** `fa2-stats-finalize` (K1, 3.10.1): the one-workgroup fold of the previous integrate's partials into the state block and the K1 half of the trace record; STATS_MODE 0 FA2 / 1 FR temperature / 2 kinetic energy (P5); 3 storage bindings; calls the reduction helpers. */
|
|
339
350
|
const FA2_STATS_FINALIZE: KernelEntry = {
|
|
340
351
|
id: "fa2-stats-finalize",
|
|
341
352
|
body: fa2StatsFinalizeWgsl,
|
|
@@ -346,14 +357,14 @@ const FA2_STATS_FINALIZE: KernelEntry = {
|
|
|
346
357
|
decl(1, 2, "T", "storage", "array<Fa2Trace>"),
|
|
347
358
|
decl(2, 0, "P", "uniform", "Fa2Params"),
|
|
348
359
|
],
|
|
349
|
-
overrideDecls: [],
|
|
360
|
+
overrideDecls: [{ name: "STATS_MODE", type: "u32", default: 0 }],
|
|
350
361
|
uniforms: [FA2_PARAMS, FA2_STATE, FA2_TRACE, FA2_PARTIAL],
|
|
351
362
|
needs: ["subgroups"],
|
|
352
363
|
snippetSlots: [],
|
|
353
364
|
phase: "P3",
|
|
354
365
|
};
|
|
355
366
|
|
|
356
|
-
/** `fa2-attraction` (K2, 3.10.1): the thread-per-row attraction gather over the CSR rows (the first writer of `force` each iteration); LINLOG / DISTRIBUTED / TIER 0 plus the standard USE_PERM / HAS_WEIGHTS; 6 storage bindings. */
|
|
367
|
+
/** `fa2-attraction` (K2, 3.10.1): the thread-per-row attraction gather over the CSR rows (the first writer of `force` each iteration); LINLOG / DISTRIBUTED / TIER 0 plus the standard USE_PERM / HAS_WEIGHTS; LAW 0 FA2 / 1 FR / 2 spring (P5); 6 storage bindings. */
|
|
357
368
|
const FA2_ATTRACTION: KernelEntry = {
|
|
358
369
|
id: "fa2-attraction",
|
|
359
370
|
body: fa2AttractionWgsl,
|
|
@@ -367,6 +378,7 @@ const FA2_ATTRACTION: KernelEntry = {
|
|
|
367
378
|
{ name: "LINLOG", type: "bool", default: false },
|
|
368
379
|
{ name: "DISTRIBUTED", type: "bool", default: false },
|
|
369
380
|
{ name: "TIER", type: "u32", default: 0 },
|
|
381
|
+
{ name: "LAW", type: "u32", default: 0 },
|
|
370
382
|
],
|
|
371
383
|
uniforms: [FA2_PARAMS],
|
|
372
384
|
needs: [],
|
|
@@ -374,7 +386,7 @@ const FA2_ATTRACTION: KernelEntry = {
|
|
|
374
386
|
phase: "P3",
|
|
375
387
|
};
|
|
376
388
|
|
|
377
|
-
/** `fa2-repulsion-exact` (K3, 3.10.1): the tiled all-pairs repulsion with the gravity and swing / traction epilogue; 6 storage bindings (`oldForce` read-only: it only calls load_old); calls the reduction helpers. */
|
|
389
|
+
/** `fa2-repulsion-exact` (K3, 3.10.1): the tiled all-pairs repulsion with the gravity and swing / traction epilogue; LAW 0 FA2 / 1 FR / 2 coulomb (P5); 6 storage bindings (`oldForce` read-only: it only calls load_old); calls the reduction helpers. */
|
|
378
390
|
const FA2_REPULSION_EXACT: KernelEntry = {
|
|
379
391
|
id: "fa2-repulsion-exact",
|
|
380
392
|
body: fa2RepulsionExactWgsl,
|
|
@@ -392,6 +404,7 @@ const FA2_REPULSION_EXACT: KernelEntry = {
|
|
|
392
404
|
{ name: "SWING_MODE", type: "u32", default: 0 },
|
|
393
405
|
{ name: "STRONG_GRAVITY", type: "bool", default: false },
|
|
394
406
|
{ name: "GRAVITY_CENTER", type: "u32", default: 0 },
|
|
407
|
+
{ name: "LAW", type: "u32", default: 0 },
|
|
395
408
|
],
|
|
396
409
|
uniforms: [FA2_PARAMS, FA2_STATE, FA2_PARTIAL],
|
|
397
410
|
needs: ["subgroups"],
|
|
@@ -417,7 +430,7 @@ const FA2_SPEED_FINALIZE: KernelEntry = {
|
|
|
417
430
|
phase: "P1",
|
|
418
431
|
};
|
|
419
432
|
|
|
420
|
-
/** `fa2-integrate` (K5, 3.10.1): the per-node speed factor and position update (no clamp, D25), `oldForce` stored in SWING_MODE 0, and the partials A / C of the next K1; 6 storage bindings; calls the reduction helpers. */
|
|
433
|
+
/** `fa2-integrate` (K5, 3.10.1): the per-node speed factor and position update (no clamp, D25), `oldForce` stored in SWING_MODE 0, and the partials A / C of the next K1; APPLY 0 FA2 / 1 FR temperature cap / 2 ngraph Euler (P5); 6 storage bindings; calls the reduction helpers. */
|
|
421
434
|
const FA2_INTEGRATE: KernelEntry = {
|
|
422
435
|
id: "fa2-integrate",
|
|
423
436
|
body: fa2IntegrateWgsl,
|
|
@@ -431,7 +444,10 @@ const FA2_INTEGRATE: KernelEntry = {
|
|
|
431
444
|
decl(1, 5, "partials", "storage", "array<Fa2Partial>"),
|
|
432
445
|
decl(2, 0, "P", "uniform", "Fa2Params"),
|
|
433
446
|
],
|
|
434
|
-
overrideDecls: [
|
|
447
|
+
overrideDecls: [
|
|
448
|
+
{ name: "SWING_MODE", type: "u32", default: 0 },
|
|
449
|
+
{ name: "APPLY", type: "u32", default: 0 },
|
|
450
|
+
],
|
|
435
451
|
uniforms: [FA2_PARAMS, FA2_STATE, FA2_PARTIAL],
|
|
436
452
|
needs: ["subgroups"],
|
|
437
453
|
snippetSlots: [],
|
|
@@ -84,6 +84,8 @@ export interface ModelResources {
|
|
|
84
84
|
export interface ModelInputs {
|
|
85
85
|
readonly mass: F32;
|
|
86
86
|
readonly weights: ResolvedWeights;
|
|
87
|
+
/** A mask to apply at load (the FR `fixed` option, PD-6); validated against `ceil(n / 32)` words; absent / null leaves the words as they are (kept on a same-size reload, cleared on a resize, spec 7.12). */
|
|
88
|
+
readonly fixed?: NodeMask | null | undefined;
|
|
87
89
|
}
|
|
88
90
|
|
|
89
91
|
/**
|
|
@@ -930,6 +932,15 @@ export class ForceSimulation<
|
|
|
930
932
|
`the model resolved ${inputs.mass.length} masses for ${n} nodes`,
|
|
931
933
|
);
|
|
932
934
|
}
|
|
935
|
+
const fixedWords = Math.ceil(n / 32);
|
|
936
|
+
if (inputs.fixed !== undefined && inputs.fixed !== null && inputs.fixed.length < fixedWords) {
|
|
937
|
+
throw invalidArgument(
|
|
938
|
+
"fixed",
|
|
939
|
+
inputs.fixed.length,
|
|
940
|
+
fixedWords,
|
|
941
|
+
`the model resolved a fixed mask of ${inputs.fixed.length} words, ${fixedWords} needed for ${n} nodes`,
|
|
942
|
+
);
|
|
943
|
+
}
|
|
933
944
|
}
|
|
934
945
|
|
|
935
946
|
// ---- every check passed: mutate
|
|
@@ -956,6 +967,10 @@ export class ForceSimulation<
|
|
|
956
967
|
this.fixedWords = makeMask(n);
|
|
957
968
|
this.fixedDirty = false;
|
|
958
969
|
}
|
|
970
|
+
if (inputs !== null && inputs.fixed !== undefined && inputs.fixed !== null) {
|
|
971
|
+
this.fixedWords.set(inputs.fixed.subarray(0, Math.ceil(n / 32)));
|
|
972
|
+
this.fixedDirty = true;
|
|
973
|
+
}
|
|
959
974
|
if (n === 0 || core === null || inputs === null) {
|
|
960
975
|
// PLAN DECISION 10: an empty graph loads with no GPU work
|
|
961
976
|
this.weightsUpload = null;
|
|
@@ -1542,22 +1557,24 @@ export class ForceSimulation<
|
|
|
1542
1557
|
* The uniform values of one iteration slot: the model's values with the shared fields on top (PLAN DECISION 6).
|
|
1543
1558
|
* @param global - the global iteration index (iterationsSubmitted + i)
|
|
1544
1559
|
* @param index - the slot index within the batch (the trace slot)
|
|
1545
|
-
* @param flags - FA2_FLAG_FIRST for the first iteration after load(), else 0
|
|
1560
|
+
* @param flags - FA2_FLAG_FIRST for the first iteration after load(), else 0; the model's own flag bits are OR-ed in
|
|
1546
1561
|
* @returns the values
|
|
1547
1562
|
*/
|
|
1548
1563
|
private paramsForSlot(global: number, index: number, flags: number): UniformValues {
|
|
1549
1564
|
const [cx, cy, cz] = this.center;
|
|
1565
|
+
const model = this.model.paramsFor(global, this.optionsValue);
|
|
1566
|
+
const modelFlags = typeof model.flags === "number" ? model.flags : 0;
|
|
1550
1567
|
const shared: UniformValues = {
|
|
1551
1568
|
n: this.n,
|
|
1552
1569
|
dim: this.dimValue,
|
|
1553
|
-
flags,
|
|
1570
|
+
flags: flags | modelFlags,
|
|
1554
1571
|
iterationIndex: index,
|
|
1555
1572
|
seed: this.seedU32(),
|
|
1556
1573
|
scale: this.scale,
|
|
1557
1574
|
center: [cx, cy, cz, 0],
|
|
1558
1575
|
settleThreshold: this.settleThreshold(),
|
|
1559
1576
|
};
|
|
1560
|
-
return { ...
|
|
1577
|
+
return { ...model, ...shared };
|
|
1561
1578
|
}
|
|
1562
1579
|
|
|
1563
1580
|
/**
|