@graphty/webgpu-graph-algorithms 0.5.0 → 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 +12 -10
- package/dist/src/accelerator.d.ts.map +1 -1
- package/dist/src/accelerator.js +32 -10
- 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 +7 -4
- 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 +7 -3
- package/dist/src/types/accelerator.d.ts.map +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 -4
- package/src/accelerator.ts +46 -12
- package/src/constants.ts +70 -0
- package/src/index.ts +15 -5
- 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 +15 -3
- 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/src/types/accelerator.ts
CHANGED
|
@@ -37,8 +37,14 @@ import type {
|
|
|
37
37
|
KatzOptions,
|
|
38
38
|
PageRankOptions,
|
|
39
39
|
} from "./algorithms.js";
|
|
40
|
-
import type {
|
|
41
|
-
|
|
40
|
+
import type {
|
|
41
|
+
ForceAtlas2Stats,
|
|
42
|
+
FruchtermanReingoldStats,
|
|
43
|
+
GpuLayoutSimulation,
|
|
44
|
+
GpuLayoutTuning,
|
|
45
|
+
SpringElectricalStats,
|
|
46
|
+
} from "./layout.js";
|
|
47
|
+
import type { ForceAtlas2Options, FruchtermanReingoldOptions, SpringElectricalOptions } from "./options.js";
|
|
42
48
|
|
|
43
49
|
// ---- the real @graphty/layout interfaces (spec 9.3, D27): imported at W1b, re-exported so the package's public
|
|
44
50
|
// surface is unchanged and src/types/layout.ts keeps resolving them from here. `export type`, never a bare
|
|
@@ -86,7 +92,9 @@ export interface AcceleratorOptions {
|
|
|
86
92
|
}
|
|
87
93
|
|
|
88
94
|
/**
|
|
89
|
-
* The injectable object (spec 3.3): P3's forceAtlas2, release and dispose,
|
|
95
|
+
* The injectable object (spec 3.3): P3's forceAtlas2, release and dispose, P5's fruchtermanReingold and
|
|
96
|
+
* springElectrical (the two other optional members of the real LayoutAccelerator, spec 9.3; the CPU option types in,
|
|
97
|
+
* the GPU simulations out), plus P7's seven algorithm members
|
|
90
98
|
* (spec 8.2, 8.3; M8b-T8), non-optional here and returning the `Gpu*Result` shapes, which satisfy the `*ResultLike`
|
|
91
99
|
* mirrors (spec 9.7: `precision` is an extra field, `F32` is a `NumericVector`). `connectedComponents` and
|
|
92
100
|
* `weaklyConnectedComponents` are the same algorithm (spec 3.3: WCC semantics on directed input) under both names
|
|
@@ -100,6 +108,10 @@ export interface GpuAccelerator extends AlgorithmAccelerator, LayoutAccelerator
|
|
|
100
108
|
readonly ctx: GpuContext;
|
|
101
109
|
readonly options: Readonly<AcceleratorOptions>;
|
|
102
110
|
forceAtlas2(options?: ForceAtlas2Options): GpuLayoutSimulation<ForceAtlas2Options, ForceAtlas2Stats>;
|
|
111
|
+
fruchtermanReingold(
|
|
112
|
+
options?: FruchtermanReingoldOptions,
|
|
113
|
+
): GpuLayoutSimulation<FruchtermanReingoldOptions, FruchtermanReingoldStats>;
|
|
114
|
+
springElectrical(options?: SpringElectricalOptions): GpuLayoutSimulation<SpringElectricalOptions, SpringElectricalStats>;
|
|
103
115
|
pageRank(s: GraphSnapshot, options?: PageRankOptions): Promise<GpuPageRankResult>;
|
|
104
116
|
personalizedPageRank(
|
|
105
117
|
s: GraphSnapshot,
|
package/src/types/layout.ts
CHANGED
|
@@ -43,6 +43,42 @@ export interface ForceAtlas2Stats extends LayoutStatsBase {
|
|
|
43
43
|
readonly trace: ReadonlyArray<ForceAtlas2TraceRecord>;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
/**
|
|
47
|
+
* One per-iteration trace record of the last completed batch of a Fruchterman-Reingold simulation (spec 3.3
|
|
48
|
+
* FruchtermanReingoldStats.trace element): K1 writes `temperature` from the iteration's uniform slot (spec 7.20).
|
|
49
|
+
* @public
|
|
50
|
+
*/
|
|
51
|
+
export interface FruchtermanReingoldTraceRecord {
|
|
52
|
+
readonly temperature: number;
|
|
53
|
+
readonly meanDisplacement: number;
|
|
54
|
+
readonly settledCount: number;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Spec 3.3 FruchtermanReingoldStats, verbatim: the cooling schedule's value replaces the controller fields. */
|
|
58
|
+
export interface FruchtermanReingoldStats extends LayoutStatsBase {
|
|
59
|
+
readonly temperature: number;
|
|
60
|
+
readonly trace: ReadonlyArray<FruchtermanReingoldTraceRecord>;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* One per-iteration trace record of the last completed batch of a spring-electrical simulation (spec 3.3
|
|
65
|
+
* SpringElectricalStats.trace element): `kineticEnergy` is `0.5 * sum m |v|^2` over the free nodes after the
|
|
66
|
+
* PREVIOUS iteration's integrate: K5 writes it into partials B and the NEXT iteration's K1 folds it (PD-4), so the
|
|
67
|
+
* first record after load() carries 0 and record i carries the energy of iteration i - 1.
|
|
68
|
+
* @public
|
|
69
|
+
*/
|
|
70
|
+
export interface SpringElectricalTraceRecord {
|
|
71
|
+
readonly kineticEnergy: number;
|
|
72
|
+
readonly meanDisplacement: number;
|
|
73
|
+
readonly settledCount: number;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Spec 3.3 SpringElectricalStats, verbatim; `kineticEnergy` is the last folded value, one iteration behind the last integrate (PD-4). */
|
|
77
|
+
export interface SpringElectricalStats extends LayoutStatsBase {
|
|
78
|
+
readonly kineticEnergy: number;
|
|
79
|
+
readonly trace: ReadonlyArray<SpringElectricalTraceRecord>;
|
|
80
|
+
}
|
|
81
|
+
|
|
46
82
|
/** Options of GpuLayoutSimulation.run (spec 3.3). */
|
|
47
83
|
export interface RunOptions {
|
|
48
84
|
readonly maxIter?: number | undefined;
|
package/src/types/options.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* import.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import type { F32, NodeId } from "@graphty/graph-format";
|
|
9
|
+
import type { F32, NodeId, NodeMask } from "@graphty/graph-format";
|
|
10
10
|
import type {
|
|
11
11
|
CommonLayoutOptions,
|
|
12
12
|
ForceAtlas2Options,
|
|
@@ -51,3 +51,47 @@ export interface ResolvedForceAtlas2Options {
|
|
|
51
51
|
readonly iterationsPerStep: number;
|
|
52
52
|
readonly maxInFlight: number;
|
|
53
53
|
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The resolved (defaults applied) Fruchterman-Reingold option record (spec 7.20, 9.3): `k` null means `1 / sqrt(n)`
|
|
57
|
+
* at load; `fixed` is applied at load through ModelInputs.fixed (PD-6). Exported for src/layouts/fruchterman-reingold.ts
|
|
58
|
+
* and the option tests.
|
|
59
|
+
* @public
|
|
60
|
+
*/
|
|
61
|
+
export interface ResolvedFruchtermanReingoldOptions {
|
|
62
|
+
readonly k: number | null;
|
|
63
|
+
readonly iterations: number;
|
|
64
|
+
readonly cooling: "linear" | "adaptive";
|
|
65
|
+
readonly fixed: NodeMask | string | null;
|
|
66
|
+
readonly dim: 2 | 3;
|
|
67
|
+
readonly scale: number;
|
|
68
|
+
readonly center: readonly [number, number, number];
|
|
69
|
+
readonly seed: number | null;
|
|
70
|
+
readonly settleThreshold: number;
|
|
71
|
+
readonly settleWindow: number;
|
|
72
|
+
readonly iterationsPerStep: number;
|
|
73
|
+
readonly maxInFlight: number;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* The resolved spring-electrical option record (spec 7.20, 9.3; ngraph's names): `gravity` is the Coulomb constant
|
|
78
|
+
* (negative repels), never FA2's centre gravity. `gravity` and `springCoefficient` are null when left to their
|
|
79
|
+
* defaults: ngraph's constant scaled by min(1, SE_SCALE_REFERENCE_NODES / n) once n is known at load. Exported for
|
|
80
|
+
* src/layouts/spring-electrical.ts and the option tests.
|
|
81
|
+
* @public
|
|
82
|
+
*/
|
|
83
|
+
export interface ResolvedSpringElectricalOptions {
|
|
84
|
+
readonly springLength: number;
|
|
85
|
+
readonly springCoefficient: number | null;
|
|
86
|
+
readonly gravity: number | null;
|
|
87
|
+
readonly dragCoefficient: number;
|
|
88
|
+
readonly timeStep: number;
|
|
89
|
+
readonly dim: 2 | 3;
|
|
90
|
+
readonly scale: number;
|
|
91
|
+
readonly center: readonly [number, number, number];
|
|
92
|
+
readonly seed: number | null;
|
|
93
|
+
readonly settleThreshold: number;
|
|
94
|
+
readonly settleWindow: number;
|
|
95
|
+
readonly iterationsPerStep: number;
|
|
96
|
+
readonly maxInFlight: number;
|
|
97
|
+
}
|
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
* attraction force over the undirected CSR rows (both arcs present, so the sum is symmetric with no atomics), the
|
|
4
4
|
* linear or linlog law, the optional weights, the distributed-action division by the mass in `pos.w`, written as
|
|
5
5
|
* the FIRST writer of `force` each iteration. P3 ships the thread-per-row tier over `[tierStart, tierEnd)` = `[0, n)`
|
|
6
|
-
* with `USE_PERM = false`; the subgroup / workgroup tiers arrive with P4 (`TIER`).
|
|
6
|
+
* with `USE_PERM = false`; the subgroup / workgroup tiers arrive with P4 (`TIER`). `LAW` (P5, spec 7.20) picks the
|
|
7
|
+
* pair law: 0 = the FA2 text, 1 = Fruchterman-Reingold `d^2 / k` (unfloored), 2 = ngraph's Hooke spring
|
|
8
|
+
* `k_s (d - L)`; under 1 / 2 the models compile `LINLOG = false` and `HAS_WEIGHTS = false`, and the law overwrites
|
|
9
|
+
* `w` so weights are ignored either way.
|
|
7
10
|
*
|
|
8
11
|
* Body only (spec 3.5, D9); normative text (contract 4.5); the K2 sabotage mutations (P3-T5) are textual edits of it.
|
|
9
12
|
*/
|
|
@@ -29,6 +32,8 @@ fn attraction(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_i
|
|
|
29
32
|
if (HAS_WEIGHTS) { w = weights[a]; }
|
|
30
33
|
let d = pos[j].xyz - pi.xyz; // toward j
|
|
31
34
|
let len = max(length(d), FA2_DIST_FLOOR);
|
|
35
|
+
if (LAW == 1u) { w = length(d) / P.frK; } // LAW 1 (FR, 7.20): |F| = d^2 / k along d / d, unfloored; the linear select below applies w as is
|
|
36
|
+
if (LAW == 2u) { w = P.springCoefficient * (len - P.springLength) / len; } // LAW 2 (spring, ngraph generateCreateSpringForce.js:33-36): Hooke k_s (d - L) toward j
|
|
32
37
|
let mag = select(w, w * log(1.0 + len) / len, LINLOG); // linear: |F| = w len; linlog: |F| = w log(1 + len)
|
|
33
38
|
f = f + d * mag;
|
|
34
39
|
}
|
|
@@ -4,7 +4,12 @@
|
|
|
4
4
|
* networkx mode `m |F|`, contract 4.6), the position update with no displacement clamp (D25), z never integrated
|
|
5
5
|
* in 2D (spec 7.13), `oldForce` stored in paper mode for fixed nodes too (spec 7.11), and the partials A
|
|
6
6
|
* (sum p, sum |p - c|^2, min, max with max |p - c|^2 in `max.w`) and C (sum |dp| over free rows, free count) in
|
|
7
|
-
* uniform control flow.
|
|
7
|
+
* uniform control flow. `APPLY` (P5, spec 7.20) picks the integrator: 0 = the FA2 text, 1 = the Fruchterman-Reingold
|
|
8
|
+
* temperature cap `min(|F|, t)` along F, 2 = ngraph's semi-implicit Euler step with drag and the unit speed clamp
|
|
9
|
+
* over the velocity that lives in the `oldForce` slot (PD-2), which also folds the free kinetic energy into
|
|
10
|
+
* `partials.swingTraction.x` (PD-4); mode 1 folds the free force energy `sum |F|^2` into the same slot for K1's
|
|
11
|
+
* adaptive cooling and takes its temperature from the state block when `FA2_FLAG_ADAPTIVE` is set. The energy
|
|
12
|
+
* reduction runs under every mode; only its write is conditional.
|
|
8
13
|
*
|
|
9
14
|
* Body only (spec 3.5, D9); normative text (contract 4.5); the K5 sabotage mutations (P3-T5) are textual edits of it.
|
|
10
15
|
*/
|
|
@@ -25,6 +30,7 @@ fn integrate(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id
|
|
|
25
30
|
var p = vec4f(0.0);
|
|
26
31
|
var free = false;
|
|
27
32
|
var valid = false;
|
|
33
|
+
var ke = 0.0;
|
|
28
34
|
if (i < P.n) {
|
|
29
35
|
valid = true;
|
|
30
36
|
let f = load_force(i);
|
|
@@ -33,7 +39,25 @@ fn integrate(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id
|
|
|
33
39
|
if (SWING_MODE == 0u) { swing_i = p.w * length(f - load_old(i)); } // paper: m |F(t) - F(t-1)|, recomputed inline (7.2)
|
|
34
40
|
let factor = S.speed / (1.0 + sqrt(S.speed * swing_i));
|
|
35
41
|
let fixed = mask_bit(fixedMask[i >> 5u], i);
|
|
36
|
-
dp = select(f * factor, vec3f(0.0), fixed); // no clamp on dp (D25)
|
|
42
|
+
dp = select(f * factor, vec3f(0.0), fixed); // APPLY 0 (FA2): no clamp on dp (D25)
|
|
43
|
+
if (APPLY == 1u) { // APPLY 1 (FR, 7.20): move along F by min(|F|, t); a fixed node stays
|
|
44
|
+
let mag = length(f);
|
|
45
|
+
let t = select(P.temperature, S.temperature, (P.flags & FA2_FLAG_ADAPTIVE) != 0u); // adaptive cooling: K1's state temperature
|
|
46
|
+
dp = vec3f(0.0);
|
|
47
|
+
if (mag > 0.0 && !fixed) { dp = f * (min(mag, t) / mag); }
|
|
48
|
+
ke = select(0.0, dot(f, f), !fixed); // the force energy of Hu's step control, folded like the spring preset's kinetic energy
|
|
49
|
+
}
|
|
50
|
+
if (APPLY == 2u) { // APPLY 2 (spring-electrical): ngraph's Euler step over the velocity in the oldForce slot (PD-2)
|
|
51
|
+
var v = load_old(i);
|
|
52
|
+
let fd = f - P.dragCoefficient * v; // drag (generateCreateDragForce.js:18)
|
|
53
|
+
v = v + (P.timeStep / p.w) * fd; // v += (dt / m) F (generateIntegrator.js:27-29)
|
|
54
|
+
let sp = length(v);
|
|
55
|
+
if (sp > 1.0) { v = v / sp; } // the unit speed clamp (generateIntegrator.js:33-37)
|
|
56
|
+
if (P.dim == 2u) { v.z = 0.0; }
|
|
57
|
+
dp = select(P.timeStep * v, vec3f(0.0), fixed); // dp = dt v; a pinned body is skipped (generateIntegrator.js:21, 39-41)
|
|
58
|
+
if (!fixed) { store_old(i, v); }
|
|
59
|
+
ke = select(0.0, 0.5 * p.w * dot(v, v), !fixed); // partials B under APPLY 2 (PD-4)
|
|
60
|
+
}
|
|
37
61
|
if (P.dim == 2u) { dp.z = 0.0; } // 2D never integrates z (7.13)
|
|
38
62
|
p = vec4f(p.xyz + dp, p.w);
|
|
39
63
|
pos[i] = p;
|
|
@@ -59,11 +83,13 @@ fn integrate(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id
|
|
|
59
83
|
let tHi = wg_reduce_vec4(hi, lid.x, 2u);
|
|
60
84
|
let tDl = wg_reduce_f32(dl, lid.x, 0u);
|
|
61
85
|
let tFr = wg_reduce_u32(fr, lid.x, 0u);
|
|
86
|
+
let tKe = wg_reduce_f32(ke, lid.x, 0u);
|
|
62
87
|
if (lid.x == 0u) {
|
|
63
88
|
let g = group_id(wid);
|
|
64
89
|
partials[g].sum = tSum;
|
|
65
90
|
partials[g].min = tLo;
|
|
66
91
|
partials[g].max = tHi;
|
|
67
92
|
partials[g].dispFree = vec2f(tDl, f32(tFr));
|
|
93
|
+
if (APPLY != 0u) { partials[g].swingTraction = vec2f(tKe, 0.0); } // overwrites K3's epilogue: K4 never runs under FR or the preset (PD-4)
|
|
68
94
|
}
|
|
69
95
|
}`;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The `fa2-repulsion-exact` kernel body (K3; contract 4.5; spec 7.6, 7.9, 7.10): the tiled all-pairs repulsion
|
|
3
|
-
* `|F| = k m_i m_j / d` with the 0.01 distance floor and the antisymmetric coincident kick
|
|
3
|
+
* `|F| = k m_i m_j / d` with the 0.01 distance floor and the antisymmetric coincident kick (`LAW` 0; P5's `LAW` 1
|
|
4
|
+
* is Fruchterman-Reingold `k^2 / d` and `LAW` 2 ngraph's Coulomb `-g m_i m_j / d^2`, both unfloored, spec 7.20,
|
|
5
|
+
* with the kick's magnitude taken from the law at d = 0.01, PD-10), the gravity epilogue
|
|
4
6
|
* (GRAVITY_CENTER 0 = centroid, 1 = origin; STRONG_GRAVITY) added under the `valid` guard, `force += f`, and the
|
|
5
7
|
* swing / traction workgroup reduction in uniform control flow (SWING_MODE 0 = paper: free nodes only against
|
|
6
8
|
* `oldForce`; 1 = NetworkX: positions and forces mixed, every node) whose lane 0 writes
|
|
@@ -18,6 +20,11 @@ fn store_force(i: u32, f: vec3f) {
|
|
|
18
20
|
force[3u * i + 2u] = f.z;
|
|
19
21
|
}
|
|
20
22
|
fn load_old(i: u32) -> vec3f { return vec3f(oldForce[3u * i], oldForce[3u * i + 1u], oldForce[3u * i + 2u]); }
|
|
23
|
+
fn kick_magnitude(mi: f32, mj: f32) -> f32 { // the law's magnitude at d = FA2_DIST_FLOOR (PD-10)
|
|
24
|
+
if (LAW == 1u) { return P.frK * P.frK / FA2_DIST_FLOOR; }
|
|
25
|
+
if (LAW == 2u) { return -P.coulomb * mi * mj / FA2_DIST_FLOOR_SQ; }
|
|
26
|
+
return P.scalingRatio * mi * mj / FA2_DIST_FLOOR;
|
|
27
|
+
}
|
|
21
28
|
fn gravity_force(pi: vec4f) -> vec3f { // spec 7.9: centroid (GRAVITY_CENTER 0) or origin (1); regular or strong
|
|
22
29
|
var q = pi.xyz;
|
|
23
30
|
if (GRAVITY_CENTER == 0u) { q = pi.xyz - S.centroid.xyz; }
|
|
@@ -45,13 +52,15 @@ fn repulsion(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id
|
|
|
45
52
|
if (o.w > 0.0 && jj != i) { // mass > 0 for every real node, 0 for the pad
|
|
46
53
|
let d = pi.xyz - o.xyz;
|
|
47
54
|
var d2 = dot(d, d);
|
|
48
|
-
if (d2 < FA2_COINCIDENT_SQ) { // coincident: antisymmetric unit kick of magnitude
|
|
49
|
-
f = f + kick_dir(i, jj, P.dim) * (
|
|
55
|
+
if (d2 < FA2_COINCIDENT_SQ) { // coincident: antisymmetric unit kick of the law's magnitude at d = 0.01 (7.2; PD-10)
|
|
56
|
+
f = f + kick_dir(i, jj, P.dim) * kick_magnitude(pi.w, o.w);
|
|
50
57
|
continue;
|
|
51
58
|
}
|
|
52
|
-
d2 = max(d2, FA2_DIST_FLOOR_SQ);
|
|
59
|
+
if (LAW == 0u) { d2 = max(d2, FA2_DIST_FLOOR_SQ); } // FA2 alone floors d >= 0.01 (7.2); FR and coulomb are unfloored (7.20)
|
|
53
60
|
let k = P.scalingRatio * pi.w * o.w;
|
|
54
|
-
f = f + d * (k / d2);
|
|
61
|
+
if (LAW == 0u) { f = f + d * (k / d2); } // LAW 0 (FA2): |F| = k m_i m_j / d along d / d
|
|
62
|
+
if (LAW == 1u) { f = f + d * (P.frK * P.frK / d2); } // LAW 1 (FR, 7.20): |F| = k^2 / d, mass ignored
|
|
63
|
+
if (LAW == 2u) { f = f + d * (-P.coulomb * pi.w * o.w / (d2 * sqrt(d2))); } // LAW 2 (coulomb, ngraph generateQuadTree.js:131-132): |F| = -g m_i m_j / d^2
|
|
55
64
|
}
|
|
56
65
|
}
|
|
57
66
|
workgroupBarrier();
|
|
@@ -4,6 +4,13 @@
|
|
|
4
4
|
* exact spec 3.3 value from `partials.max.w`), bounding box, mean displacement over free nodes (0 when every node
|
|
5
5
|
* is fixed), the settle counter -- increments the iteration counter and writes the K1 half of the trace record.
|
|
6
6
|
* On the first iteration after load() (`FA2_FLAG_FIRST`) it folds nothing and keeps the host-written state.
|
|
7
|
+
* `STATS_MODE` (P5, spec 7.20) adds the model statistic: 0 = the FA2 text, 1 = the Fruchterman-Reingold temperature
|
|
8
|
+
* of this iteration into `S.temperature` and the trace's `modelScalar` -- the uniform's under the linear schedule,
|
|
9
|
+
* or, with `FA2_FLAG_ADAPTIVE` set, the adaptive one: the previous iteration's force energy (K5 folds sum |F|^2 over
|
|
10
|
+
* free nodes into `partials.swingTraction.x`) against `S.frEnergy` grows the temperature by 1 / FR_COOLING_STEP after
|
|
11
|
+
* FR_COOLING_PATIENCE consecutive falls and shrinks it by FR_COOLING_STEP on a rise (Yifan Hu 2005, section 3.2);
|
|
12
|
+
* 2 = the spring-electrical kinetic energy K5 folded into `partials.swingTraction.x` (PD-4) into `S.kineticEnergy`
|
|
13
|
+
* and the trace.
|
|
7
14
|
*
|
|
8
15
|
* This file holds the kernel BODY only (spec 3.5, D9): no bind-group lines and no `override` lines -- the composer
|
|
9
16
|
* emits them from the registry entry in src/kernels.ts (contract 3.10.1). The text is normative (contract 4.5) and
|
|
@@ -21,6 +28,7 @@ fn stats_finalize(@builtin(local_invocation_id) lid: vec3<u32>) {
|
|
|
21
28
|
var hi = vec4f(-F32_MAX);
|
|
22
29
|
var disp = 0.0;
|
|
23
30
|
var free = 0u;
|
|
31
|
+
var ke = 0.0;
|
|
24
32
|
if (fold) {
|
|
25
33
|
for (var g = lid.x; g < groups; g = g + WG) { // sequential per lane in index order: deterministic
|
|
26
34
|
let q = partials[g];
|
|
@@ -29,6 +37,7 @@ fn stats_finalize(@builtin(local_invocation_id) lid: vec3<u32>) {
|
|
|
29
37
|
hi = max(hi, q.max);
|
|
30
38
|
disp = disp + q.dispFree.x;
|
|
31
39
|
free = free + u32(q.dispFree.y);
|
|
40
|
+
ke = ke + q.swingTraction.x;
|
|
32
41
|
}
|
|
33
42
|
}
|
|
34
43
|
let tSum = wg_reduce_vec4(sum, lid.x, 0u);
|
|
@@ -36,6 +45,7 @@ fn stats_finalize(@builtin(local_invocation_id) lid: vec3<u32>) {
|
|
|
36
45
|
let tHi = wg_reduce_vec4(hi, lid.x, 2u);
|
|
37
46
|
let tDisp = wg_reduce_f32(disp, lid.x, 0u);
|
|
38
47
|
let tFree = wg_reduce_u32(free, lid.x, 0u);
|
|
48
|
+
let tKe = wg_reduce_f32(ke, lid.x, 0u);
|
|
39
49
|
if (lid.x == 0u) {
|
|
40
50
|
if (fold) {
|
|
41
51
|
let n = f32(P.n);
|
|
@@ -53,5 +63,28 @@ fn stats_finalize(@builtin(local_invocation_id) lid: vec3<u32>) {
|
|
|
53
63
|
T[P.iterationIndex].meanDisplacement = S.meanDisplacement;
|
|
54
64
|
T[P.iterationIndex].settledCount = S.settledCount;
|
|
55
65
|
T[P.iterationIndex].iteration = S.iteration;
|
|
66
|
+
if (STATS_MODE == 1u) { // FR: this iteration's temperature (7.20) into the state and the trace
|
|
67
|
+
if ((P.flags & FA2_FLAG_ADAPTIVE) != 0u) { // adaptive cooling (Yifan Hu 2005 3.2): tKe is the previous iteration's sum |F|^2 over free nodes
|
|
68
|
+
if (fold) {
|
|
69
|
+
var t = S.temperature;
|
|
70
|
+
if (tKe < S.frEnergy) {
|
|
71
|
+
S.frProgress = S.frProgress + 1u;
|
|
72
|
+
if (S.frProgress >= FR_COOLING_PATIENCE) { S.frProgress = 0u; t = t / FR_COOLING_STEP; }
|
|
73
|
+
} else {
|
|
74
|
+
S.frProgress = 0u;
|
|
75
|
+
t = t * FR_COOLING_STEP;
|
|
76
|
+
}
|
|
77
|
+
S.frEnergy = tKe;
|
|
78
|
+
S.temperature = t;
|
|
79
|
+
}
|
|
80
|
+
} else {
|
|
81
|
+
S.temperature = P.temperature;
|
|
82
|
+
}
|
|
83
|
+
T[P.iterationIndex].modelScalar = S.temperature;
|
|
84
|
+
}
|
|
85
|
+
if (STATS_MODE == 2u) { // spring-electrical: the kinetic energy K5 folded into partials B (PD-4); 0 on the first iteration after load()
|
|
86
|
+
S.kineticEnergy = tKe;
|
|
87
|
+
T[P.iterationIndex].modelScalar = tKe;
|
|
88
|
+
}
|
|
56
89
|
}
|
|
57
90
|
}`;
|