@graphty/webgpu-graph-algorithms 0.5.0 → 0.6.0

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 (264) hide show
  1. package/README.md +104 -52
  2. package/dist/browser.js +1 -1
  3. package/dist/chunks/{context-CRbw2Wyo.js → context-BXqgCifx.js} +225 -33
  4. package/dist/chunks/context-BXqgCifx.js.map +1 -0
  5. package/dist/node.js +1 -1
  6. package/dist/src/accelerator.d.ts +12 -10
  7. package/dist/src/accelerator.d.ts.map +1 -1
  8. package/dist/src/accelerator.js +32 -10
  9. package/dist/src/accelerator.js.map +1 -1
  10. package/dist/src/algorithms/components.d.ts.map +1 -1
  11. package/dist/src/algorithms/components.js +12 -13
  12. package/dist/src/algorithms/components.js.map +1 -1
  13. package/dist/src/algorithms/degree.d.ts +6 -8
  14. package/dist/src/algorithms/degree.d.ts.map +1 -1
  15. package/dist/src/algorithms/degree.js +58 -35
  16. package/dist/src/algorithms/degree.js.map +1 -1
  17. package/dist/src/algorithms/pagerank.d.ts.map +1 -1
  18. package/dist/src/algorithms/pagerank.js +16 -14
  19. package/dist/src/algorithms/pagerank.js.map +1 -1
  20. package/dist/src/algorithms/power-iteration.d.ts +2 -2
  21. package/dist/src/algorithms/power-iteration.d.ts.map +1 -1
  22. package/dist/src/algorithms/power-iteration.js +17 -14
  23. package/dist/src/algorithms/power-iteration.js.map +1 -1
  24. package/dist/src/constants.d.ts +85 -8
  25. package/dist/src/constants.d.ts.map +1 -1
  26. package/dist/src/constants.js +85 -8
  27. package/dist/src/constants.js.map +1 -1
  28. package/dist/src/errors.d.ts +3 -2
  29. package/dist/src/errors.d.ts.map +1 -1
  30. package/dist/src/errors.js +2 -1
  31. package/dist/src/errors.js.map +1 -1
  32. package/dist/src/index.d.ts +10 -5
  33. package/dist/src/index.d.ts.map +1 -1
  34. package/dist/src/index.js +14 -5
  35. package/dist/src/index.js.map +1 -1
  36. package/dist/src/kernel/dispatch.d.ts +8 -3
  37. package/dist/src/kernel/dispatch.d.ts.map +1 -1
  38. package/dist/src/kernel/dispatch.js +18 -7
  39. package/dist/src/kernel/dispatch.js.map +1 -1
  40. package/dist/src/kernel/kernel.d.ts +30 -1
  41. package/dist/src/kernel/kernel.d.ts.map +1 -1
  42. package/dist/src/kernel/kernel.js +49 -5
  43. package/dist/src/kernel/kernel.js.map +1 -1
  44. package/dist/src/kernel/prelude.d.ts.map +1 -1
  45. package/dist/src/kernel/prelude.js +9 -1
  46. package/dist/src/kernel/prelude.js.map +1 -1
  47. package/dist/src/kernel/profiler.d.ts +15 -3
  48. package/dist/src/kernel/profiler.d.ts.map +1 -1
  49. package/dist/src/kernel/profiler.js +27 -4
  50. package/dist/src/kernel/profiler.js.map +1 -1
  51. package/dist/src/kernels.d.ts +18 -8
  52. package/dist/src/kernels.d.ts.map +1 -1
  53. package/dist/src/kernels.js +345 -22
  54. package/dist/src/kernels.js.map +1 -1
  55. package/dist/src/layouts/calibrate.d.ts +51 -0
  56. package/dist/src/layouts/calibrate.d.ts.map +1 -0
  57. package/dist/src/layouts/calibrate.js +172 -0
  58. package/dist/src/layouts/calibrate.js.map +1 -0
  59. package/dist/src/layouts/force-simulation.d.ts +42 -5
  60. package/dist/src/layouts/force-simulation.d.ts.map +1 -1
  61. package/dist/src/layouts/force-simulation.js +84 -22
  62. package/dist/src/layouts/force-simulation.js.map +1 -1
  63. package/dist/src/layouts/forceatlas2.d.ts +107 -38
  64. package/dist/src/layouts/forceatlas2.d.ts.map +1 -1
  65. package/dist/src/layouts/forceatlas2.js +297 -290
  66. package/dist/src/layouts/forceatlas2.js.map +1 -1
  67. package/dist/src/layouts/fruchterman-reingold.d.ts +241 -0
  68. package/dist/src/layouts/fruchterman-reingold.d.ts.map +1 -0
  69. package/dist/src/layouts/fruchterman-reingold.js +739 -0
  70. package/dist/src/layouts/fruchterman-reingold.js.map +1 -0
  71. package/dist/src/layouts/model-common.d.ts +140 -0
  72. package/dist/src/layouts/model-common.d.ts.map +1 -0
  73. package/dist/src/layouts/model-common.js +269 -0
  74. package/dist/src/layouts/model-common.js.map +1 -0
  75. package/dist/src/layouts/repulsion-grid.d.ts +152 -0
  76. package/dist/src/layouts/repulsion-grid.d.ts.map +1 -0
  77. package/dist/src/layouts/repulsion-grid.js +318 -0
  78. package/dist/src/layouts/repulsion-grid.js.map +1 -0
  79. package/dist/src/layouts/spring-electrical.d.ts +224 -0
  80. package/dist/src/layouts/spring-electrical.d.ts.map +1 -0
  81. package/dist/src/layouts/spring-electrical.js +665 -0
  82. package/dist/src/layouts/spring-electrical.js.map +1 -0
  83. package/dist/src/memory/residency.d.ts +6 -2
  84. package/dist/src/memory/residency.d.ts.map +1 -1
  85. package/dist/src/memory/residency.js +84 -14
  86. package/dist/src/memory/residency.js.map +1 -1
  87. package/dist/src/primitives/core-shape.d.ts +38 -2
  88. package/dist/src/primitives/core-shape.d.ts.map +1 -1
  89. package/dist/src/primitives/core-shape.js +71 -3
  90. package/dist/src/primitives/core-shape.js.map +1 -1
  91. package/dist/src/primitives/grid-pyramid.d.ts +71 -0
  92. package/dist/src/primitives/grid-pyramid.d.ts.map +1 -0
  93. package/dist/src/primitives/grid-pyramid.js +143 -0
  94. package/dist/src/primitives/grid-pyramid.js.map +1 -0
  95. package/dist/src/primitives/grid.d.ts +118 -0
  96. package/dist/src/primitives/grid.d.ts.map +1 -0
  97. package/dist/src/primitives/grid.js +225 -0
  98. package/dist/src/primitives/grid.js.map +1 -0
  99. package/dist/src/primitives/histogram.d.ts +67 -0
  100. package/dist/src/primitives/histogram.d.ts.map +1 -0
  101. package/dist/src/primitives/histogram.js +190 -0
  102. package/dist/src/primitives/histogram.js.map +1 -0
  103. package/dist/src/primitives/radix-sort.d.ts +75 -0
  104. package/dist/src/primitives/radix-sort.d.ts.map +1 -0
  105. package/dist/src/primitives/radix-sort.js +168 -0
  106. package/dist/src/primitives/radix-sort.js.map +1 -0
  107. package/dist/src/primitives/scan.d.ts +44 -0
  108. package/dist/src/primitives/scan.d.ts.map +1 -0
  109. package/dist/src/primitives/scan.js +151 -0
  110. package/dist/src/primitives/scan.js.map +1 -0
  111. package/dist/src/primitives/segmented-reduce.d.ts +25 -17
  112. package/dist/src/primitives/segmented-reduce.d.ts.map +1 -1
  113. package/dist/src/primitives/segmented-reduce.js +166 -47
  114. package/dist/src/primitives/segmented-reduce.js.map +1 -1
  115. package/dist/src/primitives/spmv.d.ts +18 -14
  116. package/dist/src/primitives/spmv.d.ts.map +1 -1
  117. package/dist/src/primitives/spmv.js +94 -58
  118. package/dist/src/primitives/spmv.js.map +1 -1
  119. package/dist/src/primitives/verify.d.ts +49 -0
  120. package/dist/src/primitives/verify.d.ts.map +1 -0
  121. package/dist/src/primitives/verify.js +229 -0
  122. package/dist/src/primitives/verify.js.map +1 -0
  123. package/dist/src/types/accelerator.d.ts +7 -3
  124. package/dist/src/types/accelerator.d.ts.map +1 -1
  125. package/dist/src/types/context.d.ts +53 -0
  126. package/dist/src/types/context.d.ts.map +1 -1
  127. package/dist/src/types/layout.d.ts +52 -0
  128. package/dist/src/types/layout.d.ts.map +1 -1
  129. package/dist/src/types/options.d.ts +43 -1
  130. package/dist/src/types/options.d.ts.map +1 -1
  131. package/dist/src/wgsl/counting-scatter.wgsl.d.ts +8 -0
  132. package/dist/src/wgsl/counting-scatter.wgsl.d.ts.map +1 -0
  133. package/dist/src/wgsl/counting-scatter.wgsl.js +17 -0
  134. package/dist/src/wgsl/counting-scatter.wgsl.js.map +1 -0
  135. package/dist/src/wgsl/fa2-attraction.wgsl.d.ts +23 -8
  136. package/dist/src/wgsl/fa2-attraction.wgsl.d.ts.map +1 -1
  137. package/dist/src/wgsl/fa2-attraction.wgsl.js +100 -17
  138. package/dist/src/wgsl/fa2-attraction.wgsl.js.map +1 -1
  139. package/dist/src/wgsl/fa2-integrate.wgsl.d.ts +7 -2
  140. package/dist/src/wgsl/fa2-integrate.wgsl.d.ts.map +1 -1
  141. package/dist/src/wgsl/fa2-integrate.wgsl.js +28 -2
  142. package/dist/src/wgsl/fa2-integrate.wgsl.js.map +1 -1
  143. package/dist/src/wgsl/fa2-repulsion-exact.wgsl.d.ts +4 -2
  144. package/dist/src/wgsl/fa2-repulsion-exact.wgsl.d.ts.map +1 -1
  145. package/dist/src/wgsl/fa2-repulsion-exact.wgsl.js +14 -5
  146. package/dist/src/wgsl/fa2-repulsion-exact.wgsl.js.map +1 -1
  147. package/dist/src/wgsl/fa2-stats-finalize.wgsl.d.ts +12 -1
  148. package/dist/src/wgsl/fa2-stats-finalize.wgsl.d.ts.map +1 -1
  149. package/dist/src/wgsl/fa2-stats-finalize.wgsl.js +54 -0
  150. package/dist/src/wgsl/fa2-stats-finalize.wgsl.js.map +1 -1
  151. package/dist/src/wgsl/grid-cell-key.wgsl.d.ts +8 -0
  152. package/dist/src/wgsl/grid-cell-key.wgsl.d.ts.map +1 -0
  153. package/dist/src/wgsl/grid-cell-key.wgsl.js +30 -0
  154. package/dist/src/wgsl/grid-cell-key.wgsl.js.map +1 -0
  155. package/dist/src/wgsl/grid-centroid-hub.wgsl.d.ts +8 -0
  156. package/dist/src/wgsl/grid-centroid-hub.wgsl.d.ts.map +1 -0
  157. package/dist/src/wgsl/grid-centroid-hub.wgsl.js +29 -0
  158. package/dist/src/wgsl/grid-centroid-hub.wgsl.js.map +1 -0
  159. package/dist/src/wgsl/grid-centroid.wgsl.d.ts +8 -0
  160. package/dist/src/wgsl/grid-centroid.wgsl.d.ts.map +1 -0
  161. package/dist/src/wgsl/grid-centroid.wgsl.js +29 -0
  162. package/dist/src/wgsl/grid-centroid.wgsl.js.map +1 -0
  163. package/dist/src/wgsl/grid-downsample.wgsl.d.ts +7 -0
  164. package/dist/src/wgsl/grid-downsample.wgsl.d.ts.map +1 -0
  165. package/dist/src/wgsl/grid-downsample.wgsl.js +28 -0
  166. package/dist/src/wgsl/grid-downsample.wgsl.js.map +1 -0
  167. package/dist/src/wgsl/grid-far-field.wgsl.d.ts +13 -0
  168. package/dist/src/wgsl/grid-far-field.wgsl.d.ts.map +1 -0
  169. package/dist/src/wgsl/grid-far-field.wgsl.js +98 -0
  170. package/dist/src/wgsl/grid-far-field.wgsl.js.map +1 -0
  171. package/dist/src/wgsl/grid-near-field.wgsl.d.ts +19 -0
  172. package/dist/src/wgsl/grid-near-field.wgsl.d.ts.map +1 -0
  173. package/dist/src/wgsl/grid-near-field.wgsl.js +129 -0
  174. package/dist/src/wgsl/grid-near-field.wgsl.js.map +1 -0
  175. package/dist/src/wgsl/histogram.wgsl.d.ts +7 -0
  176. package/dist/src/wgsl/histogram.wgsl.d.ts.map +1 -0
  177. package/dist/src/wgsl/histogram.wgsl.js +15 -0
  178. package/dist/src/wgsl/histogram.wgsl.js.map +1 -0
  179. package/dist/src/wgsl/indirect-finalize.wgsl.d.ts +8 -0
  180. package/dist/src/wgsl/indirect-finalize.wgsl.d.ts.map +1 -0
  181. package/dist/src/wgsl/indirect-finalize.wgsl.js +26 -0
  182. package/dist/src/wgsl/indirect-finalize.wgsl.js.map +1 -0
  183. package/dist/src/wgsl/radix-hist.wgsl.d.ts +9 -0
  184. package/dist/src/wgsl/radix-hist.wgsl.d.ts.map +1 -0
  185. package/dist/src/wgsl/radix-hist.wgsl.js +31 -0
  186. package/dist/src/wgsl/radix-hist.wgsl.js.map +1 -0
  187. package/dist/src/wgsl/radix-scatter.wgsl.d.ts +9 -0
  188. package/dist/src/wgsl/radix-scatter.wgsl.d.ts.map +1 -0
  189. package/dist/src/wgsl/radix-scatter.wgsl.js +40 -0
  190. package/dist/src/wgsl/radix-scatter.wgsl.js.map +1 -0
  191. package/dist/src/wgsl/scan-add.wgsl.d.ts +6 -0
  192. package/dist/src/wgsl/scan-add.wgsl.d.ts.map +1 -0
  193. package/dist/src/wgsl/scan-add.wgsl.js +14 -0
  194. package/dist/src/wgsl/scan-add.wgsl.js.map +1 -0
  195. package/dist/src/wgsl/scan-block.wgsl.d.ts +8 -0
  196. package/dist/src/wgsl/scan-block.wgsl.d.ts.map +1 -0
  197. package/dist/src/wgsl/scan-block.wgsl.js +30 -0
  198. package/dist/src/wgsl/scan-block.wgsl.js.map +1 -0
  199. package/dist/src/wgsl/segmented-reduce.wgsl.d.ts +22 -8
  200. package/dist/src/wgsl/segmented-reduce.wgsl.d.ts.map +1 -1
  201. package/dist/src/wgsl/segmented-reduce.wgsl.js +84 -15
  202. package/dist/src/wgsl/segmented-reduce.wgsl.js.map +1 -1
  203. package/dist/src/wgsl/spmv-pull.wgsl.d.ts +22 -11
  204. package/dist/src/wgsl/spmv-pull.wgsl.d.ts.map +1 -1
  205. package/dist/src/wgsl/spmv-pull.wgsl.js +110 -36
  206. package/dist/src/wgsl/spmv-pull.wgsl.js.map +1 -1
  207. package/dist/tsconfig.build.tsbuildinfo +1 -1
  208. package/dist/webgpu-graph-algorithms.js +5016 -1130
  209. package/dist/webgpu-graph-algorithms.js.map +1 -1
  210. package/package.json +10 -7
  211. package/src/accelerator.ts +46 -12
  212. package/src/algorithms/components.ts +12 -16
  213. package/src/algorithms/degree.ts +58 -43
  214. package/src/algorithms/pagerank.ts +20 -18
  215. package/src/algorithms/power-iteration.ts +19 -18
  216. package/src/constants.ts +108 -8
  217. package/src/errors.ts +3 -1
  218. package/src/index.ts +25 -5
  219. package/src/kernel/dispatch.ts +18 -7
  220. package/src/kernel/kernel.ts +59 -5
  221. package/src/kernel/prelude.ts +15 -0
  222. package/src/kernel/profiler.ts +28 -4
  223. package/src/kernels.ts +378 -24
  224. package/src/layouts/calibrate.ts +187 -0
  225. package/src/layouts/force-simulation.ts +111 -26
  226. package/src/layouts/forceatlas2.ts +346 -324
  227. package/src/layouts/fruchterman-reingold.ts +918 -0
  228. package/src/layouts/model-common.ts +323 -0
  229. package/src/layouts/repulsion-grid.ts +451 -0
  230. package/src/layouts/spring-electrical.ts +845 -0
  231. package/src/memory/residency.ts +126 -20
  232. package/src/primitives/core-shape.ts +91 -4
  233. package/src/primitives/grid-pyramid.ts +221 -0
  234. package/src/primitives/grid.ts +349 -0
  235. package/src/primitives/histogram.ts +273 -0
  236. package/src/primitives/radix-sort.ts +246 -0
  237. package/src/primitives/scan.ts +197 -0
  238. package/src/primitives/segmented-reduce.ts +214 -56
  239. package/src/primitives/spmv.ts +125 -65
  240. package/src/primitives/verify.ts +249 -0
  241. package/src/types/accelerator.ts +15 -3
  242. package/src/types/context.ts +56 -0
  243. package/src/types/layout.ts +58 -0
  244. package/src/types/options.ts +45 -1
  245. package/src/wgsl/counting-scatter.wgsl.ts +16 -0
  246. package/src/wgsl/fa2-attraction.wgsl.ts +100 -17
  247. package/src/wgsl/fa2-integrate.wgsl.ts +28 -2
  248. package/src/wgsl/fa2-repulsion-exact.wgsl.ts +14 -5
  249. package/src/wgsl/fa2-stats-finalize.wgsl.ts +54 -0
  250. package/src/wgsl/grid-cell-key.wgsl.ts +29 -0
  251. package/src/wgsl/grid-centroid-hub.wgsl.ts +28 -0
  252. package/src/wgsl/grid-centroid.wgsl.ts +28 -0
  253. package/src/wgsl/grid-downsample.wgsl.ts +27 -0
  254. package/src/wgsl/grid-far-field.wgsl.ts +97 -0
  255. package/src/wgsl/grid-near-field.wgsl.ts +128 -0
  256. package/src/wgsl/histogram.wgsl.ts +14 -0
  257. package/src/wgsl/indirect-finalize.wgsl.ts +25 -0
  258. package/src/wgsl/radix-hist.wgsl.ts +30 -0
  259. package/src/wgsl/radix-scatter.wgsl.ts +39 -0
  260. package/src/wgsl/scan-add.wgsl.ts +13 -0
  261. package/src/wgsl/scan-block.wgsl.ts +29 -0
  262. package/src/wgsl/segmented-reduce.wgsl.ts +84 -15
  263. package/src/wgsl/spmv-pull.wgsl.ts +110 -36
  264. 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.5.0",
3
+ "version": "0.6.0",
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.1"
63
+ "@graphty/graph-format": "^1.0.2"
64
64
  },
65
65
  "peerDependencies": {
66
66
  "@graphty/algorithms": "^1.0.0",
@@ -84,14 +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.7.0",
94
- "@graphty/algorithms": "^1.8.0"
95
+ "@graphty/layout": "^1.8.0",
96
+ "@graphty/algorithms": "^1.8.1"
95
97
  },
96
98
  "scripts": {
97
99
  "build": "tsc -p tsconfig.build.json",
@@ -103,12 +105,13 @@
103
105
  "typecheck:strict-consumer": "tsc -p tsconfig.strict-consumer.json",
104
106
  "test": "vitest",
105
107
  "test:ui": "vitest --ui",
106
- "test:run": "vitest run --project=node",
107
- "test:node": "vitest run --project=node",
108
+ "test:run": "vitest run --project=node --project=node-device-errors",
109
+ "test:node": "vitest run --project=node --project=node-device-errors",
110
+ "test:node:ci": "node scripts/run-node-shard.js",
108
111
  "test:browser": "vitest run --project=browser",
109
112
  "test:browser:ci": "node scripts/run-browser-project.js",
110
113
  "test:limits": "vitest run --project=node-limits",
111
- "coverage": "vitest run --project=node --coverage",
114
+ "coverage": "vitest run --project=node --project=node-device-errors --coverage",
112
115
  "coverage:preview": "npx serve coverage -p 9058",
113
116
  "bench": "tsx benchmarks/run.ts",
114
117
  "bench:compare": "node scripts/bench-compare.js",
@@ -3,13 +3,13 @@
3
3
  * AlgorithmAccelerator and LayoutAccelerator interfaces (spec 9.2, 9.3). Both are the REAL declarations,
4
4
  * `LayoutAccelerator` from `@graphty/layout` (W1b, layout half) and `AlgorithmAccelerator` from
5
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
- * and P7's seven
8
- * algorithm members (spec 8.2, 8.3; M8b-T8, PD-14) and nothing else: the CPU-side dispatchers (`accelerated()`,
9
- * `createSimulation()`) test `acc.betweennessCentrality !== undefined` and route to the CPU when the member is
10
- * absent (spec 2.4 row "method missing"), so a method the GPU does not implement must not exist here -- never a
11
- * throwing stub. The remaining algorithm members arrive one per shipped algorithm from P8; `fruchtermanReingold` /
12
- * `springElectrical` with P5.
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.
13
13
  */
14
14
 
15
15
  import { type F32, type F64, type GraphSnapshot } from "@graphty/graph-format";
@@ -19,6 +19,8 @@ import { pageRank, personalizedPageRank } from "./algorithms/pagerank.js";
19
19
  import { eigenvectorCentrality, hits, katzCentrality } from "./algorithms/spectral.js";
20
20
  import { type GpuContext } from "./context.js";
21
21
  import { createForceAtlas2 } from "./layouts/forceatlas2.js";
22
+ import { createFruchtermanReingold } from "./layouts/fruchterman-reingold.js";
23
+ import { createSpringElectrical } from "./layouts/spring-electrical.js";
22
24
  import { type AcceleratorOptions, type GpuAccelerator } from "./types/accelerator.js";
23
25
  import {
24
26
  type ComponentsOptions,
@@ -31,8 +33,18 @@ import {
31
33
  type KatzOptions,
32
34
  type PageRankOptions,
33
35
  } from "./types/algorithms.js";
34
- import { type ForceAtlas2Stats, type GpuLayoutSimulation, type GpuLayoutTuning } from "./types/layout.js";
35
- import { type ForceAtlas2Options } from "./types/options.js";
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";
36
48
 
37
49
  /** The `algorithms` record of AcceleratorOptions (spec 3.3), named for the copy helpers. */
38
50
  type AlgorithmDefaults = NonNullable<AcceleratorOptions["algorithms"]>;
@@ -91,7 +103,8 @@ function freezeOptions(options: AcceleratorOptions | undefined): Readonly<Accele
91
103
 
92
104
  /**
93
105
  * Spec 3.3 createAccelerator, verbatim: the object implementing AlgorithmAccelerator & LayoutAccelerator
94
- * structurally; P3's forceAtlas2, release and dispose plus P7's seven algorithm members, each a delegation to
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
95
108
  * its algorithm with `ctx.assertReady()` first. The accelerator's algorithm defaults are not consulted by any of
96
109
  * them: only `betweenness` has any, and it belongs to P9. One per call (the app creates one and injects
97
110
  * it, spec 2.4); `kind` is "webgpu"; `options` is a frozen deep copy; `forceAtlas2(o)` is
@@ -101,8 +114,9 @@ function freezeOptions(options: AcceleratorOptions | undefined): Readonly<Accele
101
114
  * @param ctx - the context every simulation the accelerator creates runs on
102
115
  * @param options - GPU-only defaults inherited by every simulation (`layout`) and, from P9, the algorithm defaults
103
116
  * @returns the injectable accelerator
104
- * @throws E_DISPOSED / E_DEVICE_LOST from `ctx.assertReady()` (here and inside `forceAtlas2()`); `forceAtlas2()`
105
- * 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)
106
120
  */
107
121
  export function createAccelerator(ctx: GpuContext, options?: AcceleratorOptions): GpuAccelerator {
108
122
  ctx.assertReady();
@@ -120,6 +134,26 @@ export function createAccelerator(ctx: GpuContext, options?: AcceleratorOptions)
120
134
  ctx.assertReady();
121
135
  return createForceAtlas2(ctx, { ...o, ...frozen.layout });
122
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
+ },
123
157
  /**
124
158
  * PageRank on the device (spec 8.2; contract 3.14).
125
159
  * @param gs - the snapshot
@@ -19,11 +19,13 @@ import { type GraphSnapshot, renumberPartition, type U32 } from "@graphty/graph-
19
19
 
20
20
  import { U32_MAX } from "../constants.js";
21
21
  import { type GpuContext } from "../context.js";
22
- import { isWebGpuGraphError, WebGpuGraphError } from "../errors.js";
22
+ import { WebGpuGraphError } from "../errors.js";
23
23
  import { CommandBatch } from "../kernel/batch.js";
24
24
  import { plan1d, planGridStride } from "../kernel/dispatch.js";
25
25
  import { FILL_PARAMS, graphBindings, graphOverrides, kernelSpec, WCC_PARAMS } from "../kernels.js";
26
26
  import { type CoreBinding } from "../memory/residency.js";
27
+ import { assertWholeCore } from "../primitives/core-shape.js";
28
+ import { assertDeviceComputes } from "../primitives/verify.js";
27
29
  import { type ComponentsOptions, type GpuLabelResult } from "../types/algorithms.js";
28
30
  import { type Binding } from "../types/memory.js";
29
31
  import { type GpuRunOptions } from "../types/run.js";
@@ -66,25 +68,16 @@ function checkDest(dest: Float32Array | Uint32Array | undefined, n: number): U32
66
68
  }
67
69
 
68
70
  /**
69
- * The resident core; a windowed plan (`E_TOO_LARGE { path: "windowed", algorithm: null }`, spec 3.8) is re-thrown
70
- * with the algorithm name (3.12). Transcribed from degree.ts, whose coreOf is module-private.
71
+ * The resident core; a windowed plan is refused with `E_TOO_LARGE { path: "windowed", algorithm }` (spec 3.8, 3.12;
72
+ * DEP-P4-B: only degree and segmentedReduce execute windows).
71
73
  * @param ctx - the context
72
74
  * @param s - the snapshot
73
75
  * @returns the core binding
74
76
  */
75
77
  function coreOf(ctx: GpuContext, s: GraphSnapshot): CoreBinding {
76
- try {
77
- return ctx.residency.core(s);
78
- } catch (error: unknown) {
79
- if (isWebGpuGraphError(error) && error.code === "E_TOO_LARGE" && error.details.path === "windowed") {
80
- throw new WebGpuGraphError(
81
- "E_TOO_LARGE",
82
- `${ALGORITHM}: the arc arrays need a windowed upload, which P1-P3 plan but do not execute`,
83
- { ...error.details, algorithm: ALGORITHM },
84
- );
85
- }
86
- throw error;
87
- }
78
+ const core = ctx.residency.core(s);
79
+ assertWholeCore(core, s.arcCount, ctx.caps.limits.maxStorageBufferBindingSize, ALGORITHM);
80
+ return core;
88
81
  }
89
82
 
90
83
  /**
@@ -197,6 +190,7 @@ export async function connectedComponents(
197
190
  options?: ComponentsOptions & GpuRunOptions,
198
191
  ): Promise<GpuLabelResult> {
199
192
  ctx.assertReady();
193
+ await assertDeviceComputes(ctx);
200
194
  const n = s.nodeCount;
201
195
  const renumber = options?.renumber !== false;
202
196
  const dest = checkDest(options?.dest, n);
@@ -312,7 +306,9 @@ export async function connectedComponents(
312
306
  rounds += ROUNDS_PER_BATCH;
313
307
  ctx.assertReady();
314
308
  if (options?.signal?.aborted) {
315
- throw new WebGpuGraphError("E_ABORTED", `${ALGORITHM}: the signal was aborted`, { batchId: submitted.id });
309
+ throw new WebGpuGraphError("E_ABORTED", `${ALGORITHM}: the signal was aborted`, {
310
+ batchId: submitted.id,
311
+ });
316
312
  }
317
313
  if (new Uint32Array(back, flagRequest.offset, 1)[0] === 0) {
318
314
  break;
@@ -9,24 +9,24 @@
9
9
  *
10
10
  * Contract (3.12): `ctx.assertReady()` first; `nodeCount === 0` returns an empty `Uint32Array` (or `dest`) with no
11
11
  * GPU work (spec 5.6: there is no work, which is not a fallback); an already-aborted `signal` is `E_ABORTED` before
12
- * any work; `dest` must be a `Uint32Array` of length n over an `ArrayBuffer`; a windowed core is `E_TOO_LARGE
13
- * { path: "windowed", algorithm: "degree" }` until P4 executes windows -- `residency.core()` (3.8) throws that error
14
- * itself with `algorithm: null` before it would ever return a windowed `CoreBinding`, so `degree` catches it and
15
- * re-throws the same error with `algorithm: "degree"` (the 3.12 detail) instead of testing `core.plan`, which never
16
- * observes "windowed" in P1-P3; `arcCount === 0` skips the dispatch (only `rowPtr` is resident)
17
- * and the result is zeros; otherwise ONE dispatch over rows [0, n) with `accumulate = 0` (every row is written, so no
18
- * fill precedes it), a readback into the result, `onProgress(1, 1)`, and the scratch returned in a finally. Two runs
19
- * are bitwise identical (spec 11.9 item 4).
12
+ * any work; `dest` must be a `Uint32Array` of length n over an `ArrayBuffer`; `arcCount === 0` skips the dispatch
13
+ * (only `rowPtr` is resident) and the result is zeros; otherwise ONE dispatch over rows [0, n) with `accumulate = 0`
14
+ * (every row is written, so no fill precedes it) -- or, on a windowed core (spec 4.2, PD-8), a `fill` of zeros and
15
+ * one dispatch per window over its rows with `arcBase = w.start`, `arcEnd = w.end`, `accumulate = 1`, so a row split
16
+ * across windows adds its partial counts -- a readback into the result, `onProgress(1, 1)`, and the scratch returned
17
+ * in a finally. Two runs are bitwise identical (spec 11.9 item 4).
20
18
  */
21
19
 
22
20
  import { type GraphSnapshot, type U32 } from "@graphty/graph-format";
23
21
 
24
22
  import { type GpuContext } from "../context.js";
25
23
  import { BufferUsage } from "../device/webgpu-constants.js";
26
- import { isWebGpuGraphError, WebGpuGraphError } from "../errors.js";
24
+ import { WebGpuGraphError } from "../errors.js";
27
25
  import { plan1d } from "../kernel/dispatch.js";
28
- import { graphBindings, graphOverrides, kernelSpec, RANGE_PARAMS } from "../kernels.js";
29
- import { type CoreBinding } from "../memory/residency.js";
26
+ import { type UniformBlock, type UniformValues } from "../kernel/struct-block.js";
27
+ import { FILL_PARAMS, graphBindings, graphOverrides, kernelSpec, RANGE_PARAMS } from "../kernels.js";
28
+ import { windowBinding } from "../primitives/core-shape.js";
29
+ import { assertDeviceComputes } from "../primitives/verify.js";
30
30
  import { type Binding } from "../types/memory.js";
31
31
  import { type GpuRunOptions } from "../types/run.js";
32
32
 
@@ -55,26 +55,21 @@ function checkDest(dest: Float32Array | Uint32Array | undefined, n: number): U32
55
55
  }
56
56
 
57
57
  /**
58
- * Uploads (or finds) the core through the residency; a windowed plan, which the residency reports as `E_TOO_LARGE
59
- * { path: "windowed", algorithm: null }` (3.8), is re-thrown with `algorithm: "degree"` (3.12) and its other details
60
- * (`needed`, `limit`, `path`) kept. Every other error passes through untouched.
61
- * @param ctx - the context whose residency holds the core
62
- * @param s - the snapshot
63
- * @returns the resident core binding (arena or perArray)
58
+ * One pool-acquired uniform buffer holding one params record (a params buffer per dispatch; the caller releases
59
+ * `pooled` in its finally).
60
+ * @param ctx - the context whose pool and queue are used
61
+ * @param pooled - the list the buffer is appended to for release
62
+ * @param block - the uniform block
63
+ * @param values - the record's values
64
+ * @returns the whole-buffer binding
64
65
  */
65
- function coreOf(ctx: GpuContext, s: GraphSnapshot): CoreBinding {
66
- try {
67
- return ctx.residency.core(s);
68
- } catch (error: unknown) {
69
- if (isWebGpuGraphError(error) && error.code === "E_TOO_LARGE" && error.details.path === "windowed") {
70
- throw new WebGpuGraphError(
71
- "E_TOO_LARGE",
72
- "degree: the arc arrays need a windowed upload, which P1-P3 plan but do not execute",
73
- { ...error.details, algorithm: "degree" },
74
- );
75
- }
76
- throw error;
77
- }
66
+ function paramsBinding(ctx: GpuContext, pooled: GPUBuffer[], block: UniformBlock, values: UniformValues): Binding {
67
+ const buffer = ctx.pool.acquire(block.byteLength, BufferUsage.UNIFORM | BufferUsage.COPY_DST, "degree/params");
68
+ pooled.push(buffer);
69
+ const bytes = new ArrayBuffer(block.byteLength);
70
+ block.write(new DataView(bytes), values);
71
+ ctx.device.queue.writeBuffer(buffer, 0, bytes);
72
+ return { buffer, offset: 0, size: block.byteLength, window: null };
78
73
  }
79
74
 
80
75
  /**
@@ -89,6 +84,7 @@ function coreOf(ctx: GpuContext, s: GraphSnapshot): CoreBinding {
89
84
  */
90
85
  export async function degree(ctx: GpuContext, s: GraphSnapshot, options?: GpuRunOptions): Promise<U32> {
91
86
  ctx.assertReady();
87
+ await assertDeviceComputes(ctx);
92
88
  const n = s.nodeCount;
93
89
  const dest = checkDest(options?.dest, n);
94
90
  if (options?.signal?.aborted) {
@@ -98,7 +94,7 @@ export async function degree(ctx: GpuContext, s: GraphSnapshot, options?: GpuRun
98
94
  options?.onProgress?.(1, 1);
99
95
  return dest ?? new Uint32Array(0);
100
96
  }
101
- const core = coreOf(ctx, s);
97
+ const core = ctx.residency.core(s);
102
98
  if (s.arcCount === 0) {
103
99
  const zeros = dest ?? new Uint32Array(n);
104
100
  zeros.fill(0);
@@ -111,23 +107,40 @@ export async function degree(ctx: GpuContext, s: GraphSnapshot, options?: GpuRun
111
107
  BufferUsage.STORAGE | BufferUsage.COPY_SRC | BufferUsage.COPY_DST,
112
108
  "degree/out",
113
109
  );
114
- const params = ctx.pool.acquire(
115
- RANGE_PARAMS.byteLength,
116
- BufferUsage.UNIFORM | BufferUsage.COPY_DST,
117
- "degree/params",
118
- );
110
+ const pooled: GPUBuffer[] = [];
111
+ const params = (block: UniformBlock, values: UniformValues): Binding => paramsBinding(ctx, pooled, block, values);
119
112
  try {
120
113
  await ctx.allocator.check();
121
114
  const kernel = await ctx.pipelines.kernel(kernelSpec("degree", graphOverrides(core, null)));
122
- const bytes = new ArrayBuffer(RANGE_PARAMS.byteLength);
123
- RANGE_PARAMS.write(new DataView(bytes), { start: 0, end: n, arcBase: 0, arcEnd: s.arcCount, accumulate: 0, n });
124
- ctx.device.queue.writeBuffer(params, 0, bytes);
125
115
  const outBinding: Binding = { buffer: out, offset: 0, size: byteLength, window: null };
126
- const paramsBinding: Binding = { buffer: params, offset: 0, size: RANGE_PARAMS.byteLength, window: null };
127
- const bound = kernel.bind({ ...graphBindings(core, null), out: outBinding, P: paramsBinding });
128
116
  const encoder = ctx.device.createCommandEncoder({ label: "degree" });
129
117
  const pass = encoder.beginComputePass({ label: "degree" });
130
- kernel.dispatch(pass, bound, plan1d(n, ctx.workgroupSize, ctx.caps), [0]);
118
+ if (core.windows === null) {
119
+ const P = params(RANGE_PARAMS, { start: 0, end: n, arcBase: 0, arcEnd: s.arcCount, accumulate: 0, n });
120
+ const bound = kernel.bind({ ...graphBindings(core, null), out: outBinding, P });
121
+ kernel.dispatch(pass, bound, plan1d(n, ctx.workgroupSize, ctx.caps), [0]);
122
+ } else {
123
+ const fill = await ctx.pipelines.kernel(kernelSpec("fill"));
124
+ const zero = fill.bind({ dst: outBinding, P: params(FILL_PARAMS, { count: n, value: 0, mode: 0 }) });
125
+ fill.dispatch(pass, zero, plan1d(n, ctx.workgroupSize, ctx.caps), [0]);
126
+ for (const w of core.windows) {
127
+ const windowed = {
128
+ ...core,
129
+ colIdx: windowBinding(core, "colIdx", w),
130
+ weights: core.weights === null ? null : windowBinding(core, "weights", w),
131
+ };
132
+ const P = params(RANGE_PARAMS, {
133
+ start: w.rowFirst,
134
+ end: w.rowLast + 1,
135
+ arcBase: w.start,
136
+ arcEnd: w.end,
137
+ accumulate: 1,
138
+ n,
139
+ });
140
+ const bound = kernel.bind({ ...graphBindings(windowed, null), out: outBinding, P });
141
+ kernel.dispatch(pass, bound, plan1d(w.rowLast - w.rowFirst + 1, ctx.workgroupSize, ctx.caps), [0]);
142
+ }
143
+ }
131
144
  pass.end();
132
145
  ctx.device.queue.submit([encoder.finish()]);
133
146
  ctx.assertReady();
@@ -136,7 +149,9 @@ export async function degree(ctx: GpuContext, s: GraphSnapshot, options?: GpuRun
136
149
  options?.onProgress?.(1, 1);
137
150
  return dest ?? new Uint32Array(result);
138
151
  } finally {
139
- ctx.pool.release(params);
152
+ for (const buffer of pooled) {
153
+ ctx.pool.release(buffer);
154
+ }
140
155
  ctx.pool.release(out);
141
156
  }
142
157
  }
@@ -18,14 +18,15 @@ import { type F32, type GraphSnapshot } from "@graphty/graph-format";
18
18
 
19
19
  import { U32_MAX } from "../constants.js";
20
20
  import { type GpuContext } from "../context.js";
21
- import { isWebGpuGraphError, WebGpuGraphError } from "../errors.js";
21
+ import { WebGpuGraphError } from "../errors.js";
22
22
  import { CommandBatch } from "../kernel/batch.js";
23
23
  import { groupsOf, plan1d } from "../kernel/dispatch.js";
24
24
  import { kernelSpec, PR_PARAMS, PR_PARTIAL } from "../kernels.js";
25
25
  import { type ArrayBinding, type CoreBinding } from "../memory/residency.js";
26
- import { coreOfView } from "../primitives/core-shape.js";
26
+ import { assertWholeCore, coreOfView } from "../primitives/core-shape.js";
27
27
  import { prepareSegmentedReduce } from "../primitives/segmented-reduce.js";
28
28
  import { prepareSpmvPull } from "../primitives/spmv.js";
29
+ import { assertDeviceComputes } from "../primitives/verify.js";
29
30
  import { type GpuPageRankResult, type PageRankOptions } from "../types/algorithms.js";
30
31
  import { type Binding } from "../types/memory.js";
31
32
  import { type GpuRunOptions } from "../types/run.js";
@@ -62,26 +63,17 @@ function checkDest(dest: Float32Array | Uint32Array | undefined, n: number, algo
62
63
  }
63
64
 
64
65
  /**
65
- * The resident core; a windowed plan (`E_TOO_LARGE { path: "windowed", algorithm: null }`, spec 3.8) is re-thrown
66
- * with the algorithm name (3.12). Transcribed from degree.ts, whose coreOf is module-private.
66
+ * The resident core; a windowed plan is refused with `E_TOO_LARGE { path: "windowed", algorithm }` (spec 3.8, 3.12;
67
+ * DEP-P4-B: only degree and segmentedReduce execute windows).
67
68
  * @param ctx - the context
68
69
  * @param s - the snapshot
69
70
  * @param algorithm - the caller's name
70
71
  * @returns the core binding
71
72
  */
72
73
  function coreOf(ctx: GpuContext, s: GraphSnapshot, algorithm: string): CoreBinding {
73
- try {
74
- return ctx.residency.core(s);
75
- } catch (error: unknown) {
76
- if (isWebGpuGraphError(error) && error.code === "E_TOO_LARGE" && error.details.path === "windowed") {
77
- throw new WebGpuGraphError(
78
- "E_TOO_LARGE",
79
- `${algorithm}: the arc arrays need a windowed upload, which P1-P3 plan but do not execute`,
80
- { ...error.details, algorithm },
81
- );
82
- }
83
- throw error;
84
- }
74
+ const core = ctx.residency.core(s);
75
+ assertWholeCore(core, s.arcCount, ctx.caps.limits.maxStorageBufferBindingSize, algorithm);
76
+ return core;
85
77
  }
86
78
 
87
79
  /**
@@ -126,6 +118,7 @@ async function run(
126
118
  algorithm: string,
127
119
  ): Promise<GpuPageRankResult> {
128
120
  ctx.assertReady();
121
+ await assertDeviceComputes(ctx);
129
122
  const n = s.nodeCount;
130
123
  const alpha = options?.dampingFactor ?? 0.85;
131
124
  const maxIterations = options?.maxIterations ?? 100;
@@ -144,7 +137,13 @@ async function run(
144
137
  }
145
138
  if (n === 0) {
146
139
  options?.onProgress?.(maxIterations, maxIterations);
147
- return { scores: dest ?? new Float32Array(0), iterations: 0, converged: true, danglingMass: 0, precision: "f32" };
140
+ return {
141
+ scores: dest ?? new Float32Array(0),
142
+ iterations: 0,
143
+ converged: true,
144
+ danglingMass: 0,
145
+ precision: "f32",
146
+ };
148
147
  }
149
148
  const core = coreOf(ctx, s, algorithm);
150
149
  if (s.arcCount === 0) {
@@ -322,7 +321,10 @@ export async function personalizedPageRank(
322
321
  expected,
323
322
  });
324
323
  if (!(personalization instanceof Float32Array) || personalization.length !== n) {
325
- throw invalid(`${personalization.constructor.name}(${personalization.length})`, `a Float32Array of length ${n}`);
324
+ throw invalid(
325
+ `${personalization.constructor.name}(${personalization.length})`,
326
+ `a Float32Array of length ${n}`,
327
+ );
326
328
  }
327
329
  let total = 0;
328
330
  for (let v = 0; v < n; v++) {
@@ -20,13 +20,14 @@ import { type F32, type GraphSnapshot } from "@graphty/graph-format";
20
20
 
21
21
  import { U32_MAX } from "../constants.js";
22
22
  import { type GpuContext } from "../context.js";
23
- import { isWebGpuGraphError, WebGpuGraphError } from "../errors.js";
23
+ import { WebGpuGraphError } from "../errors.js";
24
24
  import { CommandBatch } from "../kernel/batch.js";
25
25
  import { groupsOf, plan1d } from "../kernel/dispatch.js";
26
26
  import { kernelSpec, PR_PARAMS, PR_PARTIAL } from "../kernels.js";
27
27
  import { type CoreBinding } from "../memory/residency.js";
28
- import { coreOfView } from "../primitives/core-shape.js";
28
+ import { assertWholeCore, coreOfView } from "../primitives/core-shape.js";
29
29
  import { prepareSpmvPull } from "../primitives/spmv.js";
30
+ import { assertDeviceComputes } from "../primitives/verify.js";
30
31
  import { type Binding } from "../types/memory.js";
31
32
  import { algorithmScope } from "./scope.js";
32
33
 
@@ -103,26 +104,17 @@ export function checkDest(dest: Float32Array | Uint32Array | undefined, n: numbe
103
104
  }
104
105
 
105
106
  /**
106
- * The resident core; a windowed plan (`E_TOO_LARGE { path: "windowed", algorithm: null }`, spec 3.8) is re-thrown
107
- * with the algorithm name (3.12). Transcribed from degree.ts, whose coreOf is module-private.
107
+ * The resident core; a windowed plan is refused with `E_TOO_LARGE { path: "windowed", algorithm }` (spec 3.8, 3.12;
108
+ * DEP-P4-B: only degree and segmentedReduce execute windows).
108
109
  * @param ctx - the context
109
110
  * @param s - the snapshot
110
111
  * @param algorithm - the caller's name
111
112
  * @returns the core binding
112
113
  */
113
114
  export function coreOf(ctx: GpuContext, s: GraphSnapshot, algorithm: string): CoreBinding {
114
- try {
115
- return ctx.residency.core(s);
116
- } catch (error: unknown) {
117
- if (isWebGpuGraphError(error) && error.code === "E_TOO_LARGE" && error.details.path === "windowed") {
118
- throw new WebGpuGraphError(
119
- "E_TOO_LARGE",
120
- `${algorithm}: the arc arrays need a windowed upload, which P1-P3 plan but do not execute`,
121
- { ...error.details, algorithm },
122
- );
123
- }
124
- throw error;
125
- }
115
+ const core = ctx.residency.core(s);
116
+ assertWholeCore(core, s.arcCount, ctx.caps.limits.maxStorageBufferBindingSize, algorithm);
117
+ return core;
126
118
  }
127
119
 
128
120
  /**
@@ -179,6 +171,7 @@ export async function runPowerIteration(
179
171
  n: number,
180
172
  config: PowerIterationConfig,
181
173
  ): Promise<PowerIterationRun> {
174
+ await assertDeviceComputes(ctx);
182
175
  const scope = algorithmScope(ctx, config.label, RING_SLOTS);
183
176
  try {
184
177
  const bytes = 4 * n;
@@ -231,7 +224,14 @@ export async function runPowerIteration(
231
224
  const rankOut = ring[iteration % ring.length];
232
225
  const { core, pull } = pulls[(iteration - 1) % pulls.length];
233
226
  // outWeightSum takes rankIn as its dummy: storage-ro, read only under NORM_MODE 0, never compiled here
234
- const scaleBindings = { rankIn, rankPrev: rankOut, outWeightSum: rankIn, xNorm, partials, P: params.binding };
227
+ const scaleBindings = {
228
+ rankIn,
229
+ rankPrev: rankOut,
230
+ outWeightSum: rankIn,
231
+ xNorm,
232
+ partials,
233
+ P: params.binding,
234
+ };
235
235
  scaleNorm.dispatch(pass, scaleNorm.bind(scaleBindings), scalePlan, [params.offset]);
236
236
  finalize.dispatch(pass, finalize.bind({ partials, P: params.binding }), finalizePlan, [params.offset]);
237
237
  if (scaleApply !== null) {
@@ -265,7 +265,8 @@ export async function runPowerIteration(
265
265
  }
266
266
  return {
267
267
  scores: new Float32Array(back, scoresRequest.offset, n).slice(),
268
- previous: previousRequest === null ? null : new Float32Array(back, previousRequest.offset, n).slice(),
268
+ previous:
269
+ previousRequest === null ? null : new Float32Array(back, previousRequest.offset, n).slice(),
269
270
  iterations: converged ? firstConverged : iterationsRun,
270
271
  converged,
271
272
  iterationsRun,