@graphty/webgpu-graph-algorithms 0.6.5 → 0.6.7

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.
Files changed (147) hide show
  1. package/README.md +38 -17
  2. package/dist/browser.js +1 -1
  3. package/dist/chunks/{context-hzGggHeM.js → context-Cezi7qpi.js} +46 -22
  4. package/dist/chunks/context-Cezi7qpi.js.map +1 -0
  5. package/dist/node.js +19 -11
  6. package/dist/node.js.map +1 -1
  7. package/dist/src/algorithms/bfs.d.ts +10 -6
  8. package/dist/src/algorithms/bfs.d.ts.map +1 -1
  9. package/dist/src/algorithms/bfs.js +32 -9
  10. package/dist/src/algorithms/bfs.js.map +1 -1
  11. package/dist/src/algorithms/pagerank.d.ts.map +1 -1
  12. package/dist/src/algorithms/pagerank.js +19 -5
  13. package/dist/src/algorithms/pagerank.js.map +1 -1
  14. package/dist/src/algorithms/power-iteration.d.ts.map +1 -1
  15. package/dist/src/algorithms/power-iteration.js +8 -2
  16. package/dist/src/algorithms/power-iteration.js.map +1 -1
  17. package/dist/src/constants.d.ts +33 -0
  18. package/dist/src/constants.d.ts.map +1 -1
  19. package/dist/src/constants.js +33 -0
  20. package/dist/src/constants.js.map +1 -1
  21. package/dist/src/kernel/dispatch.d.ts +2 -2
  22. package/dist/src/kernel/dispatch.d.ts.map +1 -1
  23. package/dist/src/kernel/kernel.d.ts +1 -1
  24. package/dist/src/kernel/kernel.js +2 -2
  25. package/dist/src/kernel/kernel.js.map +1 -1
  26. package/dist/src/kernel/prelude.d.ts.map +1 -1
  27. package/dist/src/kernel/prelude.js +2 -1
  28. package/dist/src/kernel/prelude.js.map +1 -1
  29. package/dist/src/kernels.d.ts +10 -7
  30. package/dist/src/kernels.d.ts.map +1 -1
  31. package/dist/src/kernels.js +33 -9
  32. package/dist/src/kernels.js.map +1 -1
  33. package/dist/src/layouts/forceatlas2.d.ts.map +1 -1
  34. package/dist/src/layouts/forceatlas2.js +2 -1
  35. package/dist/src/layouts/forceatlas2.js.map +1 -1
  36. package/dist/src/layouts/fruchterman-reingold.d.ts.map +1 -1
  37. package/dist/src/layouts/fruchterman-reingold.js +4 -2
  38. package/dist/src/layouts/fruchterman-reingold.js.map +1 -1
  39. package/dist/src/layouts/repulsion-exact.d.ts +16 -0
  40. package/dist/src/layouts/repulsion-exact.d.ts.map +1 -1
  41. package/dist/src/layouts/repulsion-exact.js +21 -1
  42. package/dist/src/layouts/repulsion-exact.js.map +1 -1
  43. package/dist/src/layouts/repulsion-grid.d.ts +1 -1
  44. package/dist/src/layouts/repulsion-grid.js +1 -1
  45. package/dist/src/layouts/spring-electrical.d.ts.map +1 -1
  46. package/dist/src/layouts/spring-electrical.js +6 -2
  47. package/dist/src/layouts/spring-electrical.js.map +1 -1
  48. package/dist/src/memory/residency.js +14 -4
  49. package/dist/src/memory/residency.js.map +1 -1
  50. package/dist/src/node/index.d.ts +13 -8
  51. package/dist/src/node/index.d.ts.map +1 -1
  52. package/dist/src/node/index.js +36 -17
  53. package/dist/src/node/index.js.map +1 -1
  54. package/dist/src/primitives/frontier.d.ts +1 -0
  55. package/dist/src/primitives/frontier.d.ts.map +1 -1
  56. package/dist/src/primitives/frontier.js +1 -0
  57. package/dist/src/primitives/frontier.js.map +1 -1
  58. package/dist/src/primitives/grid-pyramid.d.ts +4 -4
  59. package/dist/src/primitives/grid-pyramid.d.ts.map +1 -1
  60. package/dist/src/primitives/grid-pyramid.js +4 -3
  61. package/dist/src/primitives/grid-pyramid.js.map +1 -1
  62. package/dist/src/primitives/grid.d.ts +13 -10
  63. package/dist/src/primitives/grid.d.ts.map +1 -1
  64. package/dist/src/primitives/grid.js +10 -7
  65. package/dist/src/primitives/grid.js.map +1 -1
  66. package/dist/src/wgsl/advance-expand.wgsl.d.ts +4 -3
  67. package/dist/src/wgsl/advance-expand.wgsl.d.ts.map +1 -1
  68. package/dist/src/wgsl/advance-expand.wgsl.js +4 -3
  69. package/dist/src/wgsl/advance-expand.wgsl.js.map +1 -1
  70. package/dist/src/wgsl/bfs-bottom-up.wgsl.d.ts +4 -3
  71. package/dist/src/wgsl/bfs-bottom-up.wgsl.d.ts.map +1 -1
  72. package/dist/src/wgsl/bfs-bottom-up.wgsl.js +4 -3
  73. package/dist/src/wgsl/bfs-bottom-up.wgsl.js.map +1 -1
  74. package/dist/src/wgsl/bfs-fused.wgsl.d.ts +6 -6
  75. package/dist/src/wgsl/bfs-fused.wgsl.d.ts.map +1 -1
  76. package/dist/src/wgsl/bfs-fused.wgsl.js +6 -6
  77. package/dist/src/wgsl/bfs-next-degree.wgsl.d.ts +23 -0
  78. package/dist/src/wgsl/bfs-next-degree.wgsl.d.ts.map +1 -0
  79. package/dist/src/wgsl/bfs-next-degree.wgsl.js +34 -0
  80. package/dist/src/wgsl/bfs-next-degree.wgsl.js.map +1 -0
  81. package/dist/src/wgsl/fa2-repulsion-exact.wgsl.d.ts +4 -1
  82. package/dist/src/wgsl/fa2-repulsion-exact.wgsl.d.ts.map +1 -1
  83. package/dist/src/wgsl/fa2-repulsion-exact.wgsl.js +18 -2
  84. package/dist/src/wgsl/fa2-repulsion-exact.wgsl.js.map +1 -1
  85. package/dist/src/wgsl/fa2-stats-finalize.wgsl.d.ts +1 -1
  86. package/dist/src/wgsl/fa2-stats-finalize.wgsl.d.ts.map +1 -1
  87. package/dist/src/wgsl/fa2-stats-finalize.wgsl.js +4 -2
  88. package/dist/src/wgsl/fa2-stats-finalize.wgsl.js.map +1 -1
  89. package/dist/src/wgsl/frontier-finalize.wgsl.d.ts +21 -20
  90. package/dist/src/wgsl/frontier-finalize.wgsl.d.ts.map +1 -1
  91. package/dist/src/wgsl/frontier-finalize.wgsl.js +26 -25
  92. package/dist/src/wgsl/frontier-finalize.wgsl.js.map +1 -1
  93. package/dist/src/wgsl/grid-cell-key.wgsl.d.ts +3 -2
  94. package/dist/src/wgsl/grid-cell-key.wgsl.d.ts.map +1 -1
  95. package/dist/src/wgsl/grid-cell-key.wgsl.js +4 -2
  96. package/dist/src/wgsl/grid-cell-key.wgsl.js.map +1 -1
  97. package/dist/src/wgsl/grid-centroid.wgsl.d.ts +3 -2
  98. package/dist/src/wgsl/grid-centroid.wgsl.d.ts.map +1 -1
  99. package/dist/src/wgsl/grid-centroid.wgsl.js +3 -2
  100. package/dist/src/wgsl/grid-centroid.wgsl.js.map +1 -1
  101. package/dist/src/wgsl/grid-downsample.wgsl.d.ts +1 -1
  102. package/dist/src/wgsl/grid-downsample.wgsl.js +1 -1
  103. package/dist/src/wgsl/grid-far-field.wgsl.d.ts +6 -4
  104. package/dist/src/wgsl/grid-far-field.wgsl.d.ts.map +1 -1
  105. package/dist/src/wgsl/grid-far-field.wgsl.js +15 -8
  106. package/dist/src/wgsl/grid-far-field.wgsl.js.map +1 -1
  107. package/dist/src/wgsl/grid-near-field.wgsl.d.ts +2 -2
  108. package/dist/src/wgsl/grid-near-field.wgsl.d.ts.map +1 -1
  109. package/dist/src/wgsl/grid-near-field.wgsl.js +6 -2
  110. package/dist/src/wgsl/grid-near-field.wgsl.js.map +1 -1
  111. package/dist/src/wgsl/histogram.wgsl.d.ts +1 -1
  112. package/dist/src/wgsl/histogram.wgsl.js +1 -1
  113. package/dist/webgpu-graph-algorithms.js +144 -45
  114. package/dist/webgpu-graph-algorithms.js.map +1 -1
  115. package/package.json +3 -3
  116. package/src/algorithms/bfs.ts +33 -9
  117. package/src/algorithms/pagerank.ts +19 -5
  118. package/src/algorithms/power-iteration.ts +8 -2
  119. package/src/constants.ts +35 -0
  120. package/src/kernel/dispatch.ts +2 -2
  121. package/src/kernel/kernel.ts +2 -2
  122. package/src/kernel/prelude.ts +2 -0
  123. package/src/kernels.ts +35 -9
  124. package/src/layouts/forceatlas2.ts +2 -0
  125. package/src/layouts/fruchterman-reingold.ts +4 -1
  126. package/src/layouts/repulsion-exact.ts +29 -1
  127. package/src/layouts/repulsion-grid.ts +1 -1
  128. package/src/layouts/spring-electrical.ts +8 -1
  129. package/src/memory/residency.ts +14 -4
  130. package/src/node/index.ts +42 -18
  131. package/src/primitives/frontier.ts +2 -0
  132. package/src/primitives/grid-pyramid.ts +6 -5
  133. package/src/primitives/grid.ts +17 -12
  134. package/src/wgsl/advance-expand.wgsl.ts +4 -3
  135. package/src/wgsl/bfs-bottom-up.wgsl.ts +4 -3
  136. package/src/wgsl/bfs-fused.wgsl.ts +6 -6
  137. package/src/wgsl/bfs-next-degree.wgsl.ts +33 -0
  138. package/src/wgsl/fa2-repulsion-exact.wgsl.ts +18 -2
  139. package/src/wgsl/fa2-stats-finalize.wgsl.ts +4 -2
  140. package/src/wgsl/frontier-finalize.wgsl.ts +26 -25
  141. package/src/wgsl/grid-cell-key.wgsl.ts +4 -2
  142. package/src/wgsl/grid-centroid.wgsl.ts +3 -2
  143. package/src/wgsl/grid-downsample.wgsl.ts +1 -1
  144. package/src/wgsl/grid-far-field.wgsl.ts +15 -8
  145. package/src/wgsl/grid-near-field.wgsl.ts +6 -2
  146. package/src/wgsl/histogram.wgsl.ts +1 -1
  147. package/dist/chunks/context-hzGggHeM.js.map +0 -1
@@ -3,7 +3,7 @@
3
3
  * exact pair law of K3 (`LAW` 0: `|F| = k m_i m_j / d` with the 0.01 floor; `LAW` 1: FR's unfloored `k^2 / d`;
4
4
  * `LAW` 2: the unfloored coulomb `-g m_i m_j / d^2`; the antisymmetric coincident kick at the law's magnitude at
5
5
  * d = 0.01, PD-22) over the 9 (27) finest
6
- * cells around its own, or over the outside pseudo-cell alone for an outside node; a cell above `nearMax` entries
6
+ * cells around its own, or over the 2^dim outside pseudo-cells (one per orthant, issue #90) for an outside node; a cell above `nearMax` entries
7
7
  * is sampled by `nearMax` INDEPENDENT draws with replacement, draw `k` reading the slot
8
8
  * `lowbias32(((c ^ (iteration * 0x9E3779B9)) ^ seed) ^ (k * 0x85EBCA6B)) % count` (every slot's inclusion
9
9
  * probability is `nearMax / count` whatever its position in the sorted order, so a duplicated draw is counted twice
@@ -15,5 +15,5 @@
15
15
  * The helpers `load_force`, `store_force`, `load_old`, `kick_magnitude`, `gravity_force` and the epilogue are K3's
16
16
  * text. Body only; normative text.
17
17
  */
18
- export declare const gridNearFieldWgsl = "\nfn load_force(i: u32) -> vec3f { return vec3f(force[3u * i], force[3u * i + 1u], force[3u * i + 2u]); }\nfn store_force(i: u32, f: vec3f) {\n force[3u * i] = f.x;\n force[3u * i + 1u] = f.y;\n force[3u * i + 2u] = f.z;\n}\nfn load_old(i: u32) -> vec3f { return vec3f(oldForce[3u * i], oldForce[3u * i + 1u], oldForce[3u * i + 2u]); }\nfn gravity_force(pi: vec4f) -> vec3f { // spec 7.9: centroid (GRAVITY_CENTER 0) or origin (1); regular or strong\n var q = pi.xyz;\n if (GRAVITY_CENTER == 0u) { q = pi.xyz - S.centroid.xyz; }\n if (STRONG_GRAVITY) { return -P.gravity * pi.w * q; }\n let d = length(q);\n if (d > FA2_DIST_FLOOR) { return -P.gravity * pi.w * q / d; }\n return vec3f(0.0);\n}\nfn grid_cells() -> u32 { return P.gridMax * P.gridMax * select(1u, P.gridMax, P.dim == 3u); }\nfn kick_magnitude(mi: f32, mj: f32) -> f32 { // the law's magnitude at d = FA2_DIST_FLOOR (the P5 plan's PD-10)\n if (LAW == 1u) { return P.frK * P.frK / FA2_DIST_FLOOR; }\n if (LAW == 2u) { return -P.coulomb * mi * mj / FA2_DIST_FLOOR_SQ; }\n return P.scalingRatio * mi * mj / FA2_DIST_FLOOR;\n}\nfn pair_force(i: u32, pi: vec4f, jj: u32, o: vec4f) -> vec3f { // the exact pair law of K3 (7.6, 7.20): the floor (FA2 only), the coincident kick\n let d = pi.xyz - o.xyz;\n var d2 = dot(d, d);\n if (d2 < FA2_COINCIDENT_SQ) { return kick_dir(i, jj, P.dim) * kick_magnitude(pi.w, o.w); }\n if (LAW == 0u) { d2 = max(d2, FA2_DIST_FLOOR_SQ); }\n let k = P.scalingRatio * pi.w * o.w;\n if (LAW == 1u) { return d * (P.frK * P.frK / d2); }\n if (LAW == 2u) { return d * (-P.coulomb * pi.w * o.w / (d2 * sqrt(d2))); }\n return d * (k / d2);\n}\nfn cell_sum(i: u32, pi: vec4f, c: u32, own: bool) -> vec3f { // one finest cell: exact below nearMax entries, Horvitz-Thompson above (PD-15)\n let start = cellStart[c];\n let count = cellStart[c + 1u] - start;\n var f = vec3f(0.0);\n if (count <= P.nearMax) {\n for (var k = start; k < start + count; k = k + 1u) {\n let jj = sortedIdx[k];\n if (jj != i) { f = f + pair_force(i, pi, jj, pos[jj]); }\n }\n return f;\n }\n let base = (c ^ (P.iterationIndex * 0x9E3779B9u)) ^ P.seed; // the per-iteration draw seed (7.16)\n var sampled = 0u;\n for (var k = 0u; k < P.nearMax; k = k + 1u) {\n let jj = sortedIdx[start + (lowbias32(base ^ (k * 0x85EBCA6Bu)) % count)]; // draw k: independent inclusion, with replacement (PD-15)\n if (jj == i) { continue; }\n f = f + pair_force(i, pi, jj, pos[jj]);\n sampled = sampled + 1u;\n }\n if (sampled == 0u) { return vec3f(0.0); }\n let others = select(count, count - 1u, own);\n return f * (f32(others) / f32(sampled)); // others / sampled over the realised sample (DEP-P4-K)\n}\n\n@compute @workgroup_size(WG)\nfn grid_near_field(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {\n let t = linear_id(wid, lid.x);\n let valid = t < P.n;\n var i = 0u;\n var pi = vec4f(0.0);\n var f = vec3f(0.0);\n if (valid) {\n i = sortedIdx[t]; // sorted order (D24)\n pi = pos[i];\n let gf = f32(P.gridMax);\n let q = (pi.xyz - S.gridMin.xyz) * S.invCellSize;\n var c0 = vec3<i32>(floor(clamp(q, vec3f(-1.0), vec3f(gf + 1.0))));\n if (P.dim == 2u) { c0.z = 0; } // 2D: the one z plane\n let g = i32(P.gridMax);\n var inside = c0.x >= 0 && c0.x < g && c0.y >= 0 && c0.y < g;\n if (P.dim == 3u) { inside = inside && c0.z >= 0 && c0.z < g; }\n if (inside) {\n let zr = select(0, 1, P.dim == 3u);\n for (var dz = -zr; dz <= zr; dz = dz + 1) {\n for (var dy = -1; dy <= 1; dy = dy + 1) {\n for (var dx = -1; dx <= 1; dx = dx + 1) {\n let cx = c0.x + dx;\n let cy = c0.y + dy;\n let cz = c0.z + dz;\n if (cx < 0 || cx >= g || cy < 0 || cy >= g || cz < 0 || cz >= g) { continue; }\n let c = u32(cx) + P.gridMax * (u32(cy) + select(0u, P.gridMax * u32(cz), P.dim == 3u));\n f = f + cell_sum(i, pi, c, dx == 0 && dy == 0 && dz == 0);\n }\n }\n }\n } else {\n f = cell_sum(i, pi, grid_cells(), true); // an outside node: the pseudo-cell alone\n }\n }\n // epilogue (7.9, 7.10): gravity and force += under the guard, the swing / traction reduction outside it (K3's text)\n var sw = 0.0;\n var tr = 0.0;\n if (valid) {\n f = f + gravity_force(pi);\n let fnew = load_force(i) + f;\n store_force(i, fnew);\n if (SWING_MODE == 1u) { // NetworkX: positions and forces mixed, every node (7.2)\n sw = pi.w * length(pi.xyz - fnew);\n tr = 0.5 * pi.w * length(pi.xyz + fnew);\n } else if (!mask_bit(fixedMask[i >> 5u], i)) { // paper: free nodes only\n let fold = load_old(i);\n sw = pi.w * length(fnew - fold);\n tr = 0.5 * pi.w * length(fnew + fold);\n }\n }\n let tt = wg_reduce_vec4(vec4f(sw, tr, 0.0, 0.0), lid.x, 0u); // uniform control flow: 256 -> 1\n if (lid.x == 0u) { partials[group_id(wid)].swingTraction = tt.xy; }\n}\n";
18
+ export declare const gridNearFieldWgsl = "\nfn load_force(i: u32) -> vec3f { return vec3f(force[3u * i], force[3u * i + 1u], force[3u * i + 2u]); }\nfn store_force(i: u32, f: vec3f) {\n force[3u * i] = f.x;\n force[3u * i + 1u] = f.y;\n force[3u * i + 2u] = f.z;\n}\nfn load_old(i: u32) -> vec3f { return vec3f(oldForce[3u * i], oldForce[3u * i + 1u], oldForce[3u * i + 2u]); }\nfn gravity_force(pi: vec4f) -> vec3f { // spec 7.9: centroid (GRAVITY_CENTER 0) or origin (1); regular or strong\n var q = pi.xyz;\n if (GRAVITY_CENTER == 0u) { q = pi.xyz - S.centroid.xyz; }\n if (STRONG_GRAVITY) { return -P.gravity * pi.w * q; }\n let d = length(q);\n if (d > FA2_DIST_FLOOR) { return -P.gravity * pi.w * q / d; }\n return vec3f(0.0);\n}\nfn grid_cells() -> u32 { return P.gridMax * P.gridMax * select(1u, P.gridMax, P.dim == 3u); }\nfn kick_magnitude(mi: f32, mj: f32) -> f32 { // the law's magnitude at d = FA2_DIST_FLOOR (the P5 plan's PD-10)\n if (LAW == 1u) { return P.frK * P.frK / FA2_DIST_FLOOR; }\n if (LAW == 2u) { return -P.coulomb * mi * mj / FA2_DIST_FLOOR_SQ; }\n return P.scalingRatio * mi * mj / FA2_DIST_FLOOR;\n}\nfn pair_force(i: u32, pi: vec4f, jj: u32, o: vec4f) -> vec3f { // the exact pair law of K3 (7.6, 7.20): the floor (FA2 only), the coincident kick\n let d = pi.xyz - o.xyz;\n var d2 = dot(d, d);\n if (d2 < FA2_COINCIDENT_SQ) { return kick_dir(i, jj, P.dim) * kick_magnitude(pi.w, o.w); }\n if (LAW == 0u) { d2 = max(d2, FA2_DIST_FLOOR_SQ); }\n let k = P.scalingRatio * pi.w * o.w;\n if (LAW == 1u) { return d * (P.frK * P.frK / d2); }\n if (LAW == 2u) { return d * (-P.coulomb * pi.w * o.w / (d2 * sqrt(d2))); }\n return d * (k / d2);\n}\nfn cell_sum(i: u32, pi: vec4f, c: u32, own: bool) -> vec3f { // one finest cell: exact below nearMax entries, Horvitz-Thompson above (PD-15)\n let start = cellStart[c];\n let count = cellStart[c + 1u] - start;\n var f = vec3f(0.0);\n if (count <= P.nearMax) {\n for (var k = start; k < start + count; k = k + 1u) {\n let jj = sortedIdx[k];\n if (jj != i) { f = f + pair_force(i, pi, jj, pos[jj]); }\n }\n return f;\n }\n let base = (c ^ (P.iterationIndex * 0x9E3779B9u)) ^ P.seed; // the per-iteration draw seed (7.16)\n var sampled = 0u;\n for (var k = 0u; k < P.nearMax; k = k + 1u) {\n let jj = sortedIdx[start + (lowbias32(base ^ (k * 0x85EBCA6Bu)) % count)]; // draw k: independent inclusion, with replacement (PD-15)\n if (jj == i) { continue; }\n f = f + pair_force(i, pi, jj, pos[jj]);\n sampled = sampled + 1u;\n }\n if (sampled == 0u) { return vec3f(0.0); }\n let others = select(count, count - 1u, own);\n return f * (f32(others) / f32(sampled)); // others / sampled over the realised sample (DEP-P4-K)\n}\n\n@compute @workgroup_size(WG)\nfn grid_near_field(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {\n let t = linear_id(wid, lid.x);\n let valid = t < P.n;\n var i = 0u;\n var pi = vec4f(0.0);\n var f = vec3f(0.0);\n if (valid) {\n i = sortedIdx[t]; // sorted order (D24)\n pi = pos[i];\n let gf = f32(P.gridMax);\n let q = (pi.xyz - S.gridMin.xyz) * S.invCellSize;\n var c0 = vec3<i32>(floor(clamp(q, vec3f(-1.0), vec3f(gf + 1.0))));\n if (P.dim == 2u) { c0.z = 0; } // 2D: the one z plane\n let g = i32(P.gridMax);\n var inside = c0.x >= 0 && c0.x < g && c0.y >= 0 && c0.y < g;\n if (P.dim == 3u) { inside = inside && c0.z >= 0 && c0.z < g; }\n if (inside) {\n let zr = select(0, 1, P.dim == 3u);\n for (var dz = -zr; dz <= zr; dz = dz + 1) {\n for (var dy = -1; dy <= 1; dy = dy + 1) {\n for (var dx = -1; dx <= 1; dx = dx + 1) {\n let cx = c0.x + dx;\n let cy = c0.y + dy;\n let cz = c0.z + dz;\n if (cx < 0 || cx >= g || cy < 0 || cy >= g || cz < 0 || cz >= g) { continue; }\n let c = u32(cx) + P.gridMax * (u32(cy) + select(0u, P.gridMax * u32(cz), P.dim == 3u));\n f = f + cell_sum(i, pi, c, dx == 0 && dy == 0 && dz == 0);\n }\n }\n }\n } else {\n var own = grid_cells() + select(0u, 1u, c0.x >= g / 2) + select(0u, 2u, c0.y >= g / 2); // G1's orthant key\n if (P.dim == 3u) { own = own + select(0u, 4u, c0.z >= g / 2); }\n for (var o = grid_cells(); o < grid_cells() + select(4u, 8u, P.dim == 3u); o = o + 1u) { // an outside node: every outside pseudo-cell\n f = f + cell_sum(i, pi, o, o == own);\n }\n }\n }\n // epilogue (7.9, 7.10): gravity and force += under the guard, the swing / traction reduction outside it (K3's text)\n var sw = 0.0;\n var tr = 0.0;\n if (valid) {\n f = f + gravity_force(pi);\n let fnew = load_force(i) + f;\n store_force(i, fnew);\n if (SWING_MODE == 1u) { // NetworkX: positions and forces mixed, every node (7.2)\n sw = pi.w * length(pi.xyz - fnew);\n tr = 0.5 * pi.w * length(pi.xyz + fnew);\n } else if (!mask_bit(fixedMask[i >> 5u], i)) { // paper: free nodes only\n let fold = load_old(i);\n sw = pi.w * length(fnew - fold);\n tr = 0.5 * pi.w * length(fnew + fold);\n }\n }\n let tt = wg_reduce_vec4(vec4f(sw, tr, 0.0, 0.0), lid.x, 0u); // uniform control flow: 256 -> 1\n if (lid.x == 0u) { partials[group_id(wid)].swingTraction = tt.xy; }\n}\n";
19
19
  //# sourceMappingURL=grid-near-field.wgsl.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"grid-near-field.wgsl.d.ts","sourceRoot":"","sources":["../../../src/wgsl/grid-near-field.wgsl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,iBAAiB,27KA8G7B,CAAC"}
1
+ {"version":3,"file":"grid-near-field.wgsl.d.ts","sourceRoot":"","sources":["../../../src/wgsl/grid-near-field.wgsl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,iBAAiB,ivLAkH7B,CAAC"}
@@ -3,7 +3,7 @@
3
3
  * exact pair law of K3 (`LAW` 0: `|F| = k m_i m_j / d` with the 0.01 floor; `LAW` 1: FR's unfloored `k^2 / d`;
4
4
  * `LAW` 2: the unfloored coulomb `-g m_i m_j / d^2`; the antisymmetric coincident kick at the law's magnitude at
5
5
  * d = 0.01, PD-22) over the 9 (27) finest
6
- * cells around its own, or over the outside pseudo-cell alone for an outside node; a cell above `nearMax` entries
6
+ * cells around its own, or over the 2^dim outside pseudo-cells (one per orthant, issue #90) for an outside node; a cell above `nearMax` entries
7
7
  * is sampled by `nearMax` INDEPENDENT draws with replacement, draw `k` reading the slot
8
8
  * `lowbias32(((c ^ (iteration * 0x9E3779B9)) ^ seed) ^ (k * 0x85EBCA6B)) % count` (every slot's inclusion
9
9
  * probability is `nearMax / count` whatever its position in the sorted order, so a duplicated draw is counted twice
@@ -103,7 +103,11 @@ fn grid_near_field(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocat
103
103
  }
104
104
  }
105
105
  } else {
106
- f = cell_sum(i, pi, grid_cells(), true); // an outside node: the pseudo-cell alone
106
+ var own = grid_cells() + select(0u, 1u, c0.x >= g / 2) + select(0u, 2u, c0.y >= g / 2); // G1's orthant key
107
+ if (P.dim == 3u) { own = own + select(0u, 4u, c0.z >= g / 2); }
108
+ for (var o = grid_cells(); o < grid_cells() + select(4u, 8u, P.dim == 3u); o = o + 1u) { // an outside node: every outside pseudo-cell
109
+ f = f + cell_sum(i, pi, o, o == own);
110
+ }
107
111
  }
108
112
  }
109
113
  // epilogue (7.9, 7.10): gravity and force += under the guard, the swing / traction reduction outside it (K3's text)
@@ -1 +1 @@
1
- {"version":3,"file":"grid-near-field.wgsl.js","sourceRoot":"","sources":["../../../src/wgsl/grid-near-field.wgsl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8G3C,CAAC"}
1
+ {"version":3,"file":"grid-near-field.wgsl.js","sourceRoot":"","sources":["../../../src/wgsl/grid-near-field.wgsl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkH3C,CAAC"}
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * The `histogram` kernel body (spec 6 row 5; P4-T3): one atomicAdd per key into the global `hist` (zeroed by a fill
3
3
  * dispatch earlier in the pass, PD-4). Order-independent, hence deterministic. A key >= P.bins is not counted (the
4
- * caller's contract; the grid's keys are always < cells + 1). Body only; normative text.
4
+ * caller's contract; the grid's keys are always < cells + 2^dim). Body only; normative text.
5
5
  */
6
6
  export declare const histogramWgsl = "\n@compute @workgroup_size(WG)\nfn histogram(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {\n let i = linear_id(wid, lid.x);\n if (i >= P.count) { return; } // no barrier follows\n let k = keys[i];\n if (k < P.bins) { atomicAdd(&hist[k], 1u); } // order-independent: the count is the same whatever the schedule\n}\n";
7
7
  //# sourceMappingURL=histogram.wgsl.d.ts.map
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * The `histogram` kernel body (spec 6 row 5; P4-T3): one atomicAdd per key into the global `hist` (zeroed by a fill
3
3
  * dispatch earlier in the pass, PD-4). Order-independent, hence deterministic. A key >= P.bins is not counted (the
4
- * caller's contract; the grid's keys are always < cells + 1). Body only; normative text.
4
+ * caller's contract; the grid's keys are always < cells + 2^dim). Body only; normative text.
5
5
  */
6
6
  export const histogramWgsl = /* wgsl */ `
7
7
  @compute @workgroup_size(WG)