jaccpot 0.0.1__tar.gz

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 (61) hide show
  1. jaccpot-0.0.1/LICENSE +21 -0
  2. jaccpot-0.0.1/PKG-INFO +550 -0
  3. jaccpot-0.0.1/README.md +512 -0
  4. jaccpot-0.0.1/jaccpot/__init__.py +34 -0
  5. jaccpot-0.0.1/jaccpot/_typecheck.py +33 -0
  6. jaccpot-0.0.1/jaccpot/autodiff.py +36 -0
  7. jaccpot-0.0.1/jaccpot/basis/__init__.py +14 -0
  8. jaccpot-0.0.1/jaccpot/basis/base.py +56 -0
  9. jaccpot-0.0.1/jaccpot/basis/complex_sh.py +114 -0
  10. jaccpot-0.0.1/jaccpot/basis/real_sh.py +142 -0
  11. jaccpot-0.0.1/jaccpot/config.py +115 -0
  12. jaccpot-0.0.1/jaccpot/downward/__init__.py +1 -0
  13. jaccpot-0.0.1/jaccpot/downward/local_expansions.py +1301 -0
  14. jaccpot-0.0.1/jaccpot/nearfield/__init__.py +1 -0
  15. jaccpot-0.0.1/jaccpot/nearfield/near_field.py +3768 -0
  16. jaccpot-0.0.1/jaccpot/odisseo.py +90 -0
  17. jaccpot-0.0.1/jaccpot/operators/__init__.py +19 -0
  18. jaccpot-0.0.1/jaccpot/operators/complex_harmonics.py +205 -0
  19. jaccpot-0.0.1/jaccpot/operators/complex_ops.py +1918 -0
  20. jaccpot-0.0.1/jaccpot/operators/dtypes.py +5 -0
  21. jaccpot-0.0.1/jaccpot/operators/m2l_real_rot_scale.py +125 -0
  22. jaccpot-0.0.1/jaccpot/operators/multipole_utils.py +186 -0
  23. jaccpot-0.0.1/jaccpot/operators/real_harmonics.py +1719 -0
  24. jaccpot-0.0.1/jaccpot/operators/solidfmm_reference.py +145 -0
  25. jaccpot-0.0.1/jaccpot/operators/symmetric_tensors.py +117 -0
  26. jaccpot-0.0.1/jaccpot/pallas/__init__.py +20 -0
  27. jaccpot-0.0.1/jaccpot/pallas/m2l_core_z_real.py +152 -0
  28. jaccpot-0.0.1/jaccpot/pallas/nearfield_fused_leaf.py +593 -0
  29. jaccpot-0.0.1/jaccpot/py.typed +0 -0
  30. jaccpot-0.0.1/jaccpot/runtime/__init__.py +1 -0
  31. jaccpot-0.0.1/jaccpot/runtime/_adaptive_policy.py +973 -0
  32. jaccpot-0.0.1/jaccpot/runtime/_fmm_impl.py +13503 -0
  33. jaccpot-0.0.1/jaccpot/runtime/_interaction_cache.py +1279 -0
  34. jaccpot-0.0.1/jaccpot/runtime/_large_n_farfield.py +31 -0
  35. jaccpot-0.0.1/jaccpot/runtime/_large_n_nearfield.py +514 -0
  36. jaccpot-0.0.1/jaccpot/runtime/_large_n_pipeline.py +1925 -0
  37. jaccpot-0.0.1/jaccpot/runtime/_large_n_types.py +612 -0
  38. jaccpot-0.0.1/jaccpot/runtime/_nearfield_cache.py +94 -0
  39. jaccpot-0.0.1/jaccpot/runtime/_octree_adapter.py +161 -0
  40. jaccpot-0.0.1/jaccpot/runtime/_octree_fmm.py +729 -0
  41. jaccpot-0.0.1/jaccpot/runtime/dtypes.py +52 -0
  42. jaccpot-0.0.1/jaccpot/runtime/fmm.py +6 -0
  43. jaccpot-0.0.1/jaccpot/runtime/fmm_presets.py +134 -0
  44. jaccpot-0.0.1/jaccpot/runtime/reference.py +230 -0
  45. jaccpot-0.0.1/jaccpot/solver.py +1113 -0
  46. jaccpot-0.0.1/jaccpot/upward/__init__.py +1 -0
  47. jaccpot-0.0.1/jaccpot/upward/solidfmm_complex_tree_expansions.py +674 -0
  48. jaccpot-0.0.1/jaccpot/upward/tree_expansions.py +285 -0
  49. jaccpot-0.0.1/jaccpot.egg-info/PKG-INFO +550 -0
  50. jaccpot-0.0.1/jaccpot.egg-info/SOURCES.txt +59 -0
  51. jaccpot-0.0.1/jaccpot.egg-info/dependency_links.txt +1 -0
  52. jaccpot-0.0.1/jaccpot.egg-info/requires.txt +15 -0
  53. jaccpot-0.0.1/jaccpot.egg-info/top_level.txt +1 -0
  54. jaccpot-0.0.1/pyproject.toml +98 -0
  55. jaccpot-0.0.1/setup.cfg +4 -0
  56. jaccpot-0.0.1/tests/test_adaptive_order_runtime.py +338 -0
  57. jaccpot-0.0.1/tests/test_adaptive_policy_runtime.py +470 -0
  58. jaccpot-0.0.1/tests/test_force_scale_runtime.py +102 -0
  59. jaccpot-0.0.1/tests/test_gravity_vs_direct.py +108 -0
  60. jaccpot-0.0.1/tests/test_real_basis_runtime.py +105 -0
  61. jaccpot-0.0.1/tests/test_real_sh_roundtrip.py +55 -0
jaccpot-0.0.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 AstroAI Lab
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
jaccpot-0.0.1/PKG-INFO ADDED
@@ -0,0 +1,550 @@
1
+ Metadata-Version: 2.4
2
+ Name: jaccpot
3
+ Version: 0.0.1
4
+ Summary: JAX Fast Multipole Method solver package built on yggdrax tree artifacts
5
+ Author-email: AstroAI Lab <astroai@iwr.uni-heidelberg.de>, Tobias Buck <tobias.buck@iwr.uni-heidelberg.de>
6
+ Maintainer-email: AstroAI Lab <astroai@iwr.uni-heidelberg.de>
7
+ License-Expression: MIT
8
+ Project-URL: Homepage, https://github.com/AstroAI-Lab/jaccpot
9
+ Project-URL: Repository, https://github.com/AstroAI-Lab/jaccpot
10
+ Project-URL: Issues, https://github.com/AstroAI-Lab/jaccpot/issues
11
+ Project-URL: Documentation, https://github.com/AstroAI-Lab/jaccpot#readme
12
+ Keywords: fmm,n-body,jax,gravity,treecode
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Scientific/Engineering :: Physics
20
+ Requires-Python: >=3.10
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: jax[cuda]>=0.8.0
24
+ Requires-Dist: jaxlib>=0.4.0
25
+ Requires-Dist: jaxtyping>=0.2.23
26
+ Requires-Dist: beartype>=0.14.0
27
+ Requires-Dist: yggdrax<0.1.0,>=0.0.1
28
+ Provides-Extra: dev
29
+ Requires-Dist: black>=24.8.0; extra == "dev"
30
+ Requires-Dist: isort>=5.13.2; extra == "dev"
31
+ Requires-Dist: pytest>=8.3.0; extra == "dev"
32
+ Requires-Dist: pytest-cov>=5.0.0; extra == "dev"
33
+ Requires-Dist: pytest-xdist>=3.6.0; extra == "dev"
34
+ Requires-Dist: pre-commit>=3.8.0; extra == "dev"
35
+ Requires-Dist: build>=1.2.2; extra == "dev"
36
+ Requires-Dist: twine>=5.1.1; extra == "dev"
37
+ Dynamic: license-file
38
+
39
+ # jaccpot
40
+
41
+ ![Python](https://img.shields.io/badge/python-3.10%2B-3776AB?logo=python&logoColor=white)
42
+ ![Black](https://img.shields.io/badge/code%20style-black-000000.svg)
43
+ ![isort](https://img.shields.io/badge/imports-isort-1674b1.svg)
44
+ ![pytest](https://img.shields.io/badge/tests-pytest-0a9edc.svg)
45
+ ![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white)
46
+ ![License](https://img.shields.io/badge/license-MIT-green.svg)
47
+ ![CI](https://img.shields.io/badge/CI-GitHub_Actions-2088FF?logo=githubactions&logoColor=white)
48
+
49
+ <p align="center">
50
+ <img src="./jaccpot.png" alt="jaccpot Logo" width="420" />
51
+ </p>
52
+
53
+ `jaccpot` is a JAX-first Fast Multipole Method (FMM) package for N-body gravity
54
+ and related hierarchical interaction problems. It provides multipole/local
55
+ expansion operators, near/far-field kernels, and a preset-driven high-level
56
+ solver API.
57
+
58
+ Tree construction and traversal artifacts are provided by the companion package
59
+ `yggdrax`.
60
+
61
+ ## Features
62
+
63
+ - High-level `FastMultipoleMethod` API with `fast`, `balanced`, `accurate`, and `large_n_gpu` presets
64
+ - Configurable expansion basis (`complex`/`solidfmm`, `real`, `cartesian`)
65
+ - Pure-JAX real spherical harmonic rotate+scale M2L path
66
+ - Adaptive-order far-field evaluation with fixed `p_gears` buckets
67
+ - Optional topology reuse for multiple nearby timesteps
68
+ - Optional Pallas acceleration for the real-basis z-translation hotspot
69
+ - Modular runtime with grouped/dense interaction pathways
70
+ - Near-field and far-field execution paths with optional prepared state reuse
71
+ - Explicit octree execution backend for `basis="solidfmm"`
72
+ - Differentiable gravitational acceleration helper via JAX autodiff
73
+
74
+ ## Installation
75
+
76
+ Install from source:
77
+
78
+ ```bash
79
+ pip install -e .
80
+ ```
81
+
82
+ `yggdrax` is not on PyPI yet. Install it from GitHub first (use the latest
83
+ `main`, which includes native `RadixTree` JAX pytree registration):
84
+
85
+ ```bash
86
+ git clone https://github.com/TobiBu/yggdrax.git
87
+ cd yggdrax
88
+ pip install -e .
89
+ cd ..
90
+ ```
91
+
92
+ Install with development tooling:
93
+
94
+ ```bash
95
+ pip install -e ".[dev]"
96
+ ```
97
+
98
+ ## Quick Start
99
+
100
+ ```python
101
+ import jax
102
+ import jax.numpy as jnp
103
+
104
+ from jaccpot import FastMultipoleMethod
105
+
106
+ key = jax.random.PRNGKey(0)
107
+ key_pos, key_mass = jax.random.split(key)
108
+ positions = jax.random.uniform(key_pos, (1024, 3), minval=-1.0, maxval=1.0)
109
+ masses = jax.random.uniform(key_mass, (1024,), minval=0.5, maxval=1.5)
110
+
111
+ solver = FastMultipoleMethod(preset="balanced", basis="solidfmm")
112
+ accelerations = solver.compute_accelerations(positions, masses)
113
+ print(accelerations.shape)
114
+ ```
115
+
116
+ Real-basis rotate+scale FMM uses the same high-level API:
117
+
118
+ ```python
119
+ solver = FastMultipoleMethod(
120
+ preset="accurate",
121
+ basis="real",
122
+ adaptive_order=True,
123
+ p_gears=(2, 3, 4),
124
+ )
125
+ accelerations = solver.compute_accelerations(positions, masses, max_order=4)
126
+ ```
127
+
128
+ For very large single-GPU runs, use the dedicated throughput/memory preset:
129
+
130
+ ```python
131
+ solver = FastMultipoleMethod(
132
+ preset="large_n_gpu",
133
+ basis="solidfmm",
134
+ precision="fp32",
135
+ )
136
+ ```
137
+
138
+ `large_n_gpu` keeps JIT enabled while applying memory-oriented runtime defaults
139
+ for large particle-count single-GPU runs (streamed far-pair mode, reduced
140
+ near-field precompute retention, and cache retention disabled).
141
+
142
+ For split-step integrators (for example active-particle substeps), you can
143
+ evaluate only a subset while still using all particles as FMM sources:
144
+
145
+ ```python
146
+ active = jnp.asarray([0, 7, 11, 32], dtype=jnp.int32)
147
+ state = solver.prepare_state(positions, masses)
148
+ active_acc = solver.evaluate_prepared_state(state, target_indices=active)
149
+ ```
150
+
151
+ For integrators that require jerk, use:
152
+
153
+ ```python
154
+ velocities = jax.random.uniform(key_pos, (1024, 3), minval=-0.2, maxval=0.2)
155
+ acc, jerk = solver.compute_accelerations_and_jerk(
156
+ positions,
157
+ masses,
158
+ velocities,
159
+ jerk_mode="fast_approx", # or "accurate"
160
+ )
161
+ ```
162
+
163
+ See [docs/derivatives_and_jerk.md](docs/derivatives_and_jerk.md) for API details,
164
+ mode tradeoffs, and output tensor layouts.
165
+ Current higher-order status:
166
+
167
+ - jerk is available via `compute_accelerations_and_jerk(...)`
168
+ - higher total time derivatives are available via
169
+ `compute_accelerations_with_time_derivatives(...)`
170
+ - public time-derivative support currently reaches crackle
171
+ (`max_time_derivative_order=3`)
172
+ - the general time-derivative API currently uses the analytic `accurate` path
173
+ - orders above crackle are not yet available
174
+ - acceleration spatial derivatives (`max_acc_derivative_order > 0`) currently
175
+ require `basis="solidfmm"`
176
+
177
+ There is also a worked example notebook for jerk, snap, and crackle, including
178
+ a small-`N` direct-sum accuracy check:
179
+ [`examples/time_derivatives_demo.ipynb`](/Users/buck/Documents/Nexus/Projects/jaccpot/examples/time_derivatives_demo.ipynb).
180
+
181
+ ### Jerk Mode Guide
182
+
183
+ | Goal | Mode | Notes |
184
+ |---|---|---|
185
+ | Lowest runtime overhead | `fast_approx` | Exact near-field jerk + far-field convective term. |
186
+ | Highest fidelity (includes source-motion effects) | `accurate` | Analytic far-field source-motion term + convective + exact near-field jerk. |
187
+ | Stable default for large production runs | `fast_approx` | Benchmark against your own workload before switching defaults. |
188
+
189
+ For ODISSEO-style primitive states `(N, 2, 3)`, you can use the adapter:
190
+
191
+ ```python
192
+ from jaccpot import OdisseoFMMCoupler
193
+
194
+ coupler = OdisseoFMMCoupler(solver, leaf_size=16, max_order=4)
195
+ coupler.prepare(primitive_state, masses) # full source tree
196
+ acc_active = coupler.accelerations(primitive_state, active_indices=active)
197
+ ```
198
+
199
+ ## Octree Backend
200
+
201
+ The default runtime path remains radix-oriented. To request explicit octree
202
+ execution, configure both the tree type and runtime backend:
203
+
204
+ ```python
205
+ from jaccpot import (
206
+ FastMultipoleMethod,
207
+ FMMAdvancedConfig,
208
+ RuntimePolicyConfig,
209
+ TreeConfig,
210
+ )
211
+
212
+ solver = FastMultipoleMethod(
213
+ preset="fast",
214
+ basis="solidfmm",
215
+ advanced=FMMAdvancedConfig(
216
+ tree=TreeConfig(tree_type="octree"),
217
+ runtime=RuntimePolicyConfig(execution_backend="octree"),
218
+ ),
219
+ )
220
+ ```
221
+
222
+ Current practical scope:
223
+
224
+ - the octree backend is validated for `basis="solidfmm"`
225
+ - prepared-state evaluation supports full outputs, target subsets, potentials,
226
+ JIT/eager traversal, and prepared-state cache reuse
227
+ - non-default runtime modes such as baseline nearfield and class-major
228
+ farfield are covered in the solver tests
229
+
230
+ Still worth keeping in mind:
231
+
232
+ - `execution_backend="auto"` may still resolve to the radix backend
233
+ - topology reuse remains radix-only
234
+ - validation is currently most reliable on the preferred project validation GPU
235
+
236
+ Example:
237
+
238
+ - [examples/compare_yggdrax_jaccpot_prepare.py](examples/compare_yggdrax_jaccpot_prepare.py)
239
+
240
+ ## Basis Selection
241
+
242
+ - `basis="complex"` or `basis="solidfmm"`:
243
+ default complex solidFMM-compatible path
244
+ - `basis="real"`:
245
+ real spherical harmonic coefficient layout with rotate+scale-to-z M2L
246
+ - `basis="cartesian"`:
247
+ cartesian multipole/local expansion path
248
+
249
+ The default remains the existing complex solidFMM-compatible path.
250
+
251
+ ## Precision Control
252
+
253
+ Use `precision` to select runtime dtype explicitly:
254
+
255
+ ```python
256
+ solver_fp32 = FastMultipoleMethod(
257
+ preset="fast",
258
+ basis="solidfmm",
259
+ precision="fp32",
260
+ )
261
+
262
+ solver_fp64 = FastMultipoleMethod(
263
+ preset="accurate",
264
+ basis="solidfmm",
265
+ precision="fp64",
266
+ )
267
+ ```
268
+
269
+ `precision="fp64"` requires `jax_enable_x64=True`. You can still pass
270
+ `working_dtype` directly; if both are set, they must match.
271
+
272
+ ## Adaptive Order
273
+
274
+ Use `adaptive_order=True` together with a static gear list:
275
+
276
+ ```python
277
+ solver = FastMultipoleMethod(
278
+ preset="accurate",
279
+ basis="real",
280
+ adaptive_order=True,
281
+ p_gears=(2, 3, 4),
282
+ )
283
+ ```
284
+
285
+ `p_gears` must be a fixed tuple or list of orders. This keeps all hot paths
286
+ JIT-friendly and avoids shape polymorphism.
287
+
288
+ Adaptive order selection now uses yggdrax's generic `pair_policy` +
289
+ `interaction_tags` traversal hook. The tree backend only provides generic
290
+ far-pair tags; jaccpot owns the solver-side policy state, order selection, and
291
+ per-order bucketing.
292
+
293
+ The current adaptive acceptance model is solver-owned and error-aware:
294
+ - acceptance uses the highest available order as a Dehnen-style safety check
295
+ - accepted pairs are limited to a relaxed geometric cone to avoid pathological
296
+ over-acceptance
297
+ - once accepted, the solver picks the smallest passing order from `p_gears`
298
+
299
+ In other words, the highest candidate order decides whether a pair is safe to
300
+ accept, while the first passing order decides how much far-field work is needed.
301
+ The current notebook example prints the resulting tag-derived
302
+ `far_pairs_by_gear_counts` from the solver runtime.
303
+
304
+ Adaptive traversal currently has two practical runtime modes:
305
+
306
+ - `adaptive_error_model="tail_proxy"` (default):
307
+ the validated high-performance mode; this remains the recommended default
308
+ when runtime matters most
309
+ - `adaptive_error_model="dehnen_paper"`:
310
+ the paper-inspired comparison mode; for JAX-native runs pair it with
311
+ `dehnen_geometry_mode="tree_approx"`
312
+
313
+ Other available knobs:
314
+
315
+ - `adaptive_error_model="dehnen_degree"`:
316
+ a simplified degree-resolved Dehnen-style source-power estimator
317
+ - `dehnen_geometry_mode="exact"`:
318
+ exact reference geometry for paper comparisons; not a throughput mode
319
+ - `dehnen_geometry_mode="tree_approx"`:
320
+ JAX-native paper geometry based on approximate leaf spheres plus upward
321
+ merged spheres
322
+ - `adaptive_eps=...`:
323
+ override the default theta-derived adaptive tolerance with a direct
324
+ solver-side tolerance scale
325
+
326
+ Examples:
327
+
328
+ - [examples/real_sh_adaptive_order.ipynb](/Users/buck/Documents/Nexus/Projects/jaccpot/examples/real_sh_adaptive_order.ipynb)
329
+ - [examples/real_sh_rot_scale_demo.py](/Users/buck/Documents/Nexus/Projects/jaccpot/examples/real_sh_rot_scale_demo.py)
330
+
331
+ ## Reproducible Comparison Modes
332
+
333
+ Use the following three solver configurations for reproducible comparisons on this branch.
334
+ Keep the remaining benchmark settings fixed, for example:
335
+
336
+ - `preset="accurate"`
337
+ - `basis="real"`
338
+ - `theta=0.6`
339
+ - `leaf_size=16`
340
+ - `max_order=4`
341
+ - `p_gears=(2, 3, 4)` for adaptive runs
342
+ - enlarged traversal caps, as used in `examples/adaptive_vs_fixed_benchmark.ipynb`
343
+
344
+ Fixed non-adaptive baseline:
345
+
346
+ ```python
347
+ fixed = FastMultipoleMethod(
348
+ preset="accurate",
349
+ basis="real",
350
+ theta=0.6,
351
+ adaptive_order=False,
352
+ )
353
+ ```
354
+
355
+ Adaptive high-performance default:
356
+
357
+ ```python
358
+ tail_proxy = FastMultipoleMethod(
359
+ preset="accurate",
360
+ basis="real",
361
+ theta=0.6,
362
+ adaptive_order=True,
363
+ p_gears=(2, 3, 4),
364
+ adaptive_error_model="tail_proxy",
365
+ mac_force_scale_mode="prev",
366
+ )
367
+ ```
368
+
369
+ Adaptive paper-inspired JAX-native mode:
370
+
371
+ ```python
372
+ dehnen_paper = FastMultipoleMethod(
373
+ preset="accurate",
374
+ basis="real",
375
+ theta=0.6,
376
+ adaptive_order=True,
377
+ p_gears=(2, 3, 4),
378
+ adaptive_error_model="dehnen_paper",
379
+ adaptive_eps=1.0e-3,
380
+ dehnen_geometry_mode="tree_approx",
381
+ mac_force_scale_mode="paper",
382
+ )
383
+ ```
384
+
385
+ Interpretation on the current branch:
386
+
387
+ - `fixed`: fastest non-adaptive baseline
388
+ - `tail_proxy`: best validated adaptive runtime default
389
+ - `dehnen_paper`: higher-accuracy, paper-inspired comparison mode
390
+
391
+ ## Force Scale Modes For Adaptive Traversal
392
+
393
+ Adaptive traversal can weight its solver-side policy state with per-node force
394
+ scales. Select how those scales are estimated with `mac_force_scale_mode`:
395
+
396
+ - `"prev"`:
397
+ reuse the previous full-step per-node force-scale estimate (`self._last_force_scale_nodes`).
398
+ This is the cheapest option and is the practical default for `tail_proxy`.
399
+ - `"prepass"`:
400
+ run a cheap lowest-order prepass for the current configuration and derive force
401
+ scales from that pass.
402
+ - `"paper"`:
403
+ run the stricter paper-style current-step prepass used by
404
+ `adaptive_error_model="dehnen_paper"`.
405
+
406
+ Interpretation:
407
+
408
+ - `prev` is a runtime-oriented reuse mode.
409
+ - `paper` is the more publication/reference-oriented mode because it derives the
410
+ threshold from a dedicated current-step prepass rather than from historical state.
411
+ - `prepass` sits between the two as a generic current-step estimate that is not
412
+ specifically tied to the paper-style Dehnen path.
413
+
414
+ These scales stay inside jaccpot's adaptive policy state; they are no longer
415
+ exported as backend-specific traversal `node_features`.
416
+
417
+ ## Pallas Acceleration
418
+
419
+ The real-basis z-translation core can be accelerated with Pallas:
420
+
421
+ ```python
422
+ solver = FastMultipoleMethod(
423
+ preset="accurate",
424
+ basis="real",
425
+ use_pallas=True,
426
+ )
427
+ ```
428
+
429
+ Current behavior:
430
+
431
+ - rotations stay in pure JAX
432
+ - only the real-basis z-axis M2L core is offloaded
433
+ - unsupported backends fall back to the pure-JAX kernel automatically
434
+
435
+ On the current `expanse` CPU environment, the example reports fallback rather
436
+ than true Pallas execution:
437
+
438
+ - [examples/pallas_m2l_speed.py](/Users/buck/Documents/Nexus/Projects/jaccpot/examples/pallas_m2l_speed.py)
439
+
440
+ ## Topology Reuse
441
+
442
+ For small multi-step particle motion, you can reuse cached topology and
443
+ interaction lists for a bounded number of steps:
444
+
445
+ ```python
446
+ solver = FastMultipoleMethod(
447
+ preset="accurate",
448
+ basis="real",
449
+ reuse_topology=True,
450
+ rebuild_every=3,
451
+ )
452
+ ```
453
+
454
+ The solver always recomputes reordered particles, geometry, upward multipoles,
455
+ and downward locals for the current state. Reuse only applies to cached
456
+ topology/traversal artifacts when the Morton ordering key remains unchanged.
457
+
458
+ Example:
459
+
460
+ - [examples/reuse_topology_demo.py](/Users/buck/Documents/Nexus/Projects/jaccpot/examples/reuse_topology_demo.py)
461
+
462
+ ## Development
463
+
464
+ Run quality gates locally:
465
+
466
+ ```bash
467
+ black --check .
468
+ isort --check-only .
469
+ pytest
470
+ ```
471
+
472
+ Or run pre-commit hooks:
473
+
474
+ ```bash
475
+ pre-commit run --all-files
476
+ ```
477
+
478
+ Coverage is enforced in CI via `pytest-cov`:
479
+
480
+ ```bash
481
+ pytest --cov=jaccpot --cov-report=term-missing
482
+ ```
483
+
484
+ ### Performance Guard
485
+
486
+ CI also runs a benchmark regression guard based on:
487
+
488
+ - [bench/bench_parallel_paths.py](/Users/buck/Documents/Nexus/Projects/jaccpot/bench/bench_parallel_paths.py)
489
+ - [bench/ci_benchmark_guard.py](/Users/buck/Documents/Nexus/Projects/jaccpot/bench/ci_benchmark_guard.py)
490
+ - [bench/benchmark_baseline.json](/Users/buck/Documents/Nexus/Projects/jaccpot/bench/benchmark_baseline.json)
491
+
492
+ Run the lightweight runtime-path benchmark and CI guard locally:
493
+
494
+ ```bash
495
+ python -m bench.bench_parallel_paths --n 512 --runs 3 --warmup 1
496
+ python -m bench.ci_benchmark_guard --n 384 --runs 2 --warmup 1
497
+ ```
498
+
499
+ If a performance change is intentional, refresh the baseline:
500
+
501
+ 1. Run `bench/bench_parallel_paths.py` with the CI benchmark arguments.
502
+ 2. Read the `timings_s` line and update:
503
+ `target_eval_mean_s` and `adaptive_prepare_mean_s` in
504
+ `bench/benchmark_baseline.json`.
505
+ 3. Re-run `bench/ci_benchmark_guard.py` to confirm the new baseline passes.
506
+
507
+ ## Examples
508
+
509
+ - `examples/benchmark_runtime_accuracy.ipynb`: main runtime/accuracy benchmark workflow
510
+ - `examples/adaptive_vs_fixed_benchmark.ipynb`: adaptive-order vs fixed-order comparison
511
+ - `examples/benchmark_gpu_radix_runtime.ipynb`: GPU/radix runtime and memory-pressure deep dive
512
+ - `examples/benchmark_gpu_single_n_memory.ipynb`: interactive single-`N` GPU memory probe with plots/tables
513
+ - `examples/benchmark_gpu_n_ladder_production.py`: production-oriented large-`N` parameter sweep
514
+ - `examples/time_derivatives_demo.ipynb`: usage plus direct-sum accuracy checks for jerk, snap, and crackle
515
+ - `examples/jerk_modes_demo.ipynb`: compare jerk `fast_approx` vs `accurate`, including analytic source-motion behavior
516
+ - `examples/real_sh_adaptive_order.ipynb`: real-basis adaptive-order demo
517
+
518
+ ## Runtime Type Checking
519
+
520
+ `jaccpot` can enable package-wide runtime checking for annotated callables using
521
+ `jaxtyping` + `beartype` at import time.
522
+
523
+ - Disabled by default.
524
+ - Enable when needed with:
525
+
526
+ ```bash
527
+ export JACCPOT_RUNTIME_TYPECHECK=1
528
+ ```
529
+
530
+ ## Project Structure
531
+
532
+ - `jaccpot/solver.py`: preset-first user-facing FMM API
533
+ - `jaccpot/config.py`: config model for solver/runtime knobs
534
+ - `jaccpot/runtime`: execution internals and integration with yggdrax artifacts
535
+ - `jaccpot/operators`: harmonic, translation, and multipole operators
536
+ - `jaccpot/upward`, `jaccpot/downward`, `jaccpot/nearfield`: sweep and near-field modules
537
+ - `tests`: unit, integration, and performance checks
538
+
539
+ ## CI
540
+
541
+ GitHub Actions runs:
542
+
543
+ - formatter checks (`black`, `isort`)
544
+ - unit/integration tests with coverage threshold
545
+ - release build and PyPI publish on version tags
546
+
547
+ Workflow files:
548
+
549
+ - `.github/workflows/ci.yml`
550
+ - `.github/workflows/release.yml`