StochasticForceInference 2.0.0__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 (156) hide show
  1. stochasticforceinference-2.0.0/AGENTS.md +402 -0
  2. stochasticforceinference-2.0.0/CONTRIBUTING.md +53 -0
  3. stochasticforceinference-2.0.0/GALLERY_STYLE_GUIDE.md +391 -0
  4. stochasticforceinference-2.0.0/LICENSE +21 -0
  5. stochasticforceinference-2.0.0/MANIFEST.in +39 -0
  6. stochasticforceinference-2.0.0/PKG-INFO +203 -0
  7. stochasticforceinference-2.0.0/README.md +149 -0
  8. stochasticforceinference-2.0.0/SFI/__init__.py +64 -0
  9. stochasticforceinference-2.0.0/SFI/bases/__init__.py +85 -0
  10. stochasticforceinference-2.0.0/SFI/bases/constants.py +492 -0
  11. stochasticforceinference-2.0.0/SFI/bases/linear.py +325 -0
  12. stochasticforceinference-2.0.0/SFI/bases/monomials.py +218 -0
  13. stochasticforceinference-2.0.0/SFI/bases/pairs.py +998 -0
  14. stochasticforceinference-2.0.0/SFI/bases/spde.py +1537 -0
  15. stochasticforceinference-2.0.0/SFI/diagnostics/__init__.py +60 -0
  16. stochasticforceinference-2.0.0/SFI/diagnostics/assess.py +87 -0
  17. stochasticforceinference-2.0.0/SFI/diagnostics/dynamics_order.py +621 -0
  18. stochasticforceinference-2.0.0/SFI/diagnostics/plotting.py +226 -0
  19. stochasticforceinference-2.0.0/SFI/diagnostics/report.py +238 -0
  20. stochasticforceinference-2.0.0/SFI/diagnostics/residual_tests.py +395 -0
  21. stochasticforceinference-2.0.0/SFI/diagnostics/residuals.py +688 -0
  22. stochasticforceinference-2.0.0/SFI/inference/__init__.py +58 -0
  23. stochasticforceinference-2.0.0/SFI/inference/base.py +1460 -0
  24. stochasticforceinference-2.0.0/SFI/inference/optimizers.py +200 -0
  25. stochasticforceinference-2.0.0/SFI/inference/overdamped.py +1214 -0
  26. stochasticforceinference-2.0.0/SFI/inference/parametric_core/__init__.py +34 -0
  27. stochasticforceinference-2.0.0/SFI/inference/parametric_core/covariance.py +232 -0
  28. stochasticforceinference-2.0.0/SFI/inference/parametric_core/driver.py +272 -0
  29. stochasticforceinference-2.0.0/SFI/inference/parametric_core/flow.py +149 -0
  30. stochasticforceinference-2.0.0/SFI/inference/parametric_core/flow_multi.py +362 -0
  31. stochasticforceinference-2.0.0/SFI/inference/parametric_core/flow_ud.py +168 -0
  32. stochasticforceinference-2.0.0/SFI/inference/parametric_core/jacobians.py +540 -0
  33. stochasticforceinference-2.0.0/SFI/inference/parametric_core/objective.py +286 -0
  34. stochasticforceinference-2.0.0/SFI/inference/parametric_core/objective_ud.py +253 -0
  35. stochasticforceinference-2.0.0/SFI/inference/parametric_core/precision.py +229 -0
  36. stochasticforceinference-2.0.0/SFI/inference/parametric_core/solve.py +763 -0
  37. stochasticforceinference-2.0.0/SFI/inference/result.py +362 -0
  38. stochasticforceinference-2.0.0/SFI/inference/serialization.py +245 -0
  39. stochasticforceinference-2.0.0/SFI/inference/sparse/__init__.py +67 -0
  40. stochasticforceinference-2.0.0/SFI/inference/sparse/base.py +43 -0
  41. stochasticforceinference-2.0.0/SFI/inference/sparse/beam.py +303 -0
  42. stochasticforceinference-2.0.0/SFI/inference/sparse/greedy.py +151 -0
  43. stochasticforceinference-2.0.0/SFI/inference/sparse/hillclimb.py +307 -0
  44. stochasticforceinference-2.0.0/SFI/inference/sparse/lasso.py +178 -0
  45. stochasticforceinference-2.0.0/SFI/inference/sparse/metrics.py +78 -0
  46. stochasticforceinference-2.0.0/SFI/inference/sparse/result.py +278 -0
  47. stochasticforceinference-2.0.0/SFI/inference/sparse/scorer.py +323 -0
  48. stochasticforceinference-2.0.0/SFI/inference/sparse/stlsq.py +165 -0
  49. stochasticforceinference-2.0.0/SFI/inference/sparsity.py +40 -0
  50. stochasticforceinference-2.0.0/SFI/inference/underdamped.py +1355 -0
  51. stochasticforceinference-2.0.0/SFI/integrate/__init__.py +38 -0
  52. stochasticforceinference-2.0.0/SFI/integrate/api.py +920 -0
  53. stochasticforceinference-2.0.0/SFI/integrate/integrand.py +402 -0
  54. stochasticforceinference-2.0.0/SFI/integrate/rk4.py +156 -0
  55. stochasticforceinference-2.0.0/SFI/integrate/timeops.py +174 -0
  56. stochasticforceinference-2.0.0/SFI/langevin/__init__.py +29 -0
  57. stochasticforceinference-2.0.0/SFI/langevin/base.py +863 -0
  58. stochasticforceinference-2.0.0/SFI/langevin/chunked.py +225 -0
  59. stochasticforceinference-2.0.0/SFI/langevin/noise.py +446 -0
  60. stochasticforceinference-2.0.0/SFI/langevin/overdamped.py +560 -0
  61. stochasticforceinference-2.0.0/SFI/langevin/underdamped.py +448 -0
  62. stochasticforceinference-2.0.0/SFI/statefunc/__init__.py +49 -0
  63. stochasticforceinference-2.0.0/SFI/statefunc/basis.py +87 -0
  64. stochasticforceinference-2.0.0/SFI/statefunc/core/runtime.py +47 -0
  65. stochasticforceinference-2.0.0/SFI/statefunc/factory.py +346 -0
  66. stochasticforceinference-2.0.0/SFI/statefunc/interactor.py +90 -0
  67. stochasticforceinference-2.0.0/SFI/statefunc/layout/__init__.py +29 -0
  68. stochasticforceinference-2.0.0/SFI/statefunc/layout/_base.py +186 -0
  69. stochasticforceinference-2.0.0/SFI/statefunc/layout/_eval_compiler.py +453 -0
  70. stochasticforceinference-2.0.0/SFI/statefunc/layout/_fd_atoms.py +196 -0
  71. stochasticforceinference-2.0.0/SFI/statefunc/layout/_grid.py +878 -0
  72. stochasticforceinference-2.0.0/SFI/statefunc/layout/_sectors.py +166 -0
  73. stochasticforceinference-2.0.0/SFI/statefunc/memhint.py +222 -0
  74. stochasticforceinference-2.0.0/SFI/statefunc/nodes/__init__.py +56 -0
  75. stochasticforceinference-2.0.0/SFI/statefunc/nodes/base.py +318 -0
  76. stochasticforceinference-2.0.0/SFI/statefunc/nodes/contract.py +422 -0
  77. stochasticforceinference-2.0.0/SFI/statefunc/nodes/interactions/__init__.py +23 -0
  78. stochasticforceinference-2.0.0/SFI/statefunc/nodes/interactions/dispatcher.py +1365 -0
  79. stochasticforceinference-2.0.0/SFI/statefunc/nodes/interactions/prepare.py +161 -0
  80. stochasticforceinference-2.0.0/SFI/statefunc/nodes/interactions/specs.py +362 -0
  81. stochasticforceinference-2.0.0/SFI/statefunc/nodes/interactions/stencils.py +718 -0
  82. stochasticforceinference-2.0.0/SFI/statefunc/nodes/leaf.py +530 -0
  83. stochasticforceinference-2.0.0/SFI/statefunc/nodes/ops/__init__.py +27 -0
  84. stochasticforceinference-2.0.0/SFI/statefunc/nodes/ops/concat.py +28 -0
  85. stochasticforceinference-2.0.0/SFI/statefunc/nodes/ops/derivative.py +447 -0
  86. stochasticforceinference-2.0.0/SFI/statefunc/nodes/ops/einsum.py +120 -0
  87. stochasticforceinference-2.0.0/SFI/statefunc/nodes/ops/linear.py +153 -0
  88. stochasticforceinference-2.0.0/SFI/statefunc/nodes/ops/mapn.py +138 -0
  89. stochasticforceinference-2.0.0/SFI/statefunc/nodes/ops/reshape_rank.py +158 -0
  90. stochasticforceinference-2.0.0/SFI/statefunc/nodes/ops/slice.py +83 -0
  91. stochasticforceinference-2.0.0/SFI/statefunc/params.py +309 -0
  92. stochasticforceinference-2.0.0/SFI/statefunc/psf.py +112 -0
  93. stochasticforceinference-2.0.0/SFI/statefunc/sf.py +104 -0
  94. stochasticforceinference-2.0.0/SFI/statefunc/stateexpr.py +1243 -0
  95. stochasticforceinference-2.0.0/SFI/statefunc/structexpr.py +1003 -0
  96. stochasticforceinference-2.0.0/SFI/trajectory/__init__.py +31 -0
  97. stochasticforceinference-2.0.0/SFI/trajectory/collection.py +1122 -0
  98. stochasticforceinference-2.0.0/SFI/trajectory/dataset.py +1164 -0
  99. stochasticforceinference-2.0.0/SFI/trajectory/degrade.py +1108 -0
  100. stochasticforceinference-2.0.0/SFI/trajectory/io.py +1027 -0
  101. stochasticforceinference-2.0.0/SFI/trajectory/reserved_extras.py +129 -0
  102. stochasticforceinference-2.0.0/SFI/utils/__init__.py +17 -0
  103. stochasticforceinference-2.0.0/SFI/utils/formatting.py +308 -0
  104. stochasticforceinference-2.0.0/SFI/utils/maths.py +185 -0
  105. stochasticforceinference-2.0.0/SFI/utils/neighbors.py +162 -0
  106. stochasticforceinference-2.0.0/SFI/utils/plotting.py +1936 -0
  107. stochasticforceinference-2.0.0/StochasticForceInference.egg-info/PKG-INFO +203 -0
  108. stochasticforceinference-2.0.0/StochasticForceInference.egg-info/SOURCES.txt +155 -0
  109. stochasticforceinference-2.0.0/StochasticForceInference.egg-info/dependency_links.txt +1 -0
  110. stochasticforceinference-2.0.0/StochasticForceInference.egg-info/requires.txt +30 -0
  111. stochasticforceinference-2.0.0/StochasticForceInference.egg-info/top_level.txt +1 -0
  112. stochasticforceinference-2.0.0/examples/_gallery_utils/__init__.py +2 -0
  113. stochasticforceinference-2.0.0/examples/_gallery_utils/abp.py +101 -0
  114. stochasticforceinference-2.0.0/examples/_gallery_utils/coarse_grain.py +227 -0
  115. stochasticforceinference-2.0.0/examples/_gallery_utils/helpers.py +303 -0
  116. stochasticforceinference-2.0.0/examples/_gallery_utils/scenario_picker.py +167 -0
  117. stochasticforceinference-2.0.0/examples/convert_active_nematic_mat.py +409 -0
  118. stochasticforceinference-2.0.0/examples/experimental_data/_analyze_results.py +69 -0
  119. stochasticforceinference-2.0.0/examples/experimental_data/_expanded_basis.py +66 -0
  120. stochasticforceinference-2.0.0/examples/experimental_data/generate_tweezer_csv.py +35 -0
  121. stochasticforceinference-2.0.0/examples/gallery/abp_align_demo.py +574 -0
  122. stochasticforceinference-2.0.0/examples/gallery/abp_nonreciprocal_demo.py +524 -0
  123. stochasticforceinference-2.0.0/examples/gallery/abp_to_spde_demo.py +602 -0
  124. stochasticforceinference-2.0.0/examples/gallery/active_nematic_demo.py +910 -0
  125. stochasticforceinference-2.0.0/examples/gallery/advanced/flocking_3d_demo.py +314 -0
  126. stochasticforceinference-2.0.0/examples/gallery/advanced/multi_experiment_demo.py +347 -0
  127. stochasticforceinference-2.0.0/examples/gallery/advanced/nn_force_demo.py +430 -0
  128. stochasticforceinference-2.0.0/examples/gallery/anisotropic_diffusion_demo.py +235 -0
  129. stochasticforceinference-2.0.0/examples/gallery/custom_basis_demo.py +254 -0
  130. stochasticforceinference-2.0.0/examples/gallery/diagnostics_demo.py +150 -0
  131. stochasticforceinference-2.0.0/examples/gallery/dynamics_order_demo.py +271 -0
  132. stochasticforceinference-2.0.0/examples/gallery/experimental_workflow_demo.py +216 -0
  133. stochasticforceinference-2.0.0/examples/gallery/gray_scott_demo.py +574 -0
  134. stochasticforceinference-2.0.0/examples/gallery/home_range_demo.py +394 -0
  135. stochasticforceinference-2.0.0/examples/gallery/limitcycle_demo.py +213 -0
  136. stochasticforceinference-2.0.0/examples/gallery/lorenz_demo.py +220 -0
  137. stochasticforceinference-2.0.0/examples/gallery/lotka_volterra_demo.py +319 -0
  138. stochasticforceinference-2.0.0/examples/gallery/multiplicative_diffusion_demo.py +242 -0
  139. stochasticforceinference-2.0.0/examples/gallery/ou_demo.py +413 -0
  140. stochasticforceinference-2.0.0/examples/gallery/time_dependent_forcing_demo.py +156 -0
  141. stochasticforceinference-2.0.0/examples/gallery/time_fourier_demo.py +234 -0
  142. stochasticforceinference-2.0.0/examples/gallery/van_der_pol_demo.py +245 -0
  143. stochasticforceinference-2.0.0/examples/gallery/velocity_dependent_noise_demo.py +203 -0
  144. stochasticforceinference-2.0.0/examples/manual_examples/abp_align_demo.py +617 -0
  145. stochasticforceinference-2.0.0/examples/manual_examples/abp_largescale_config.py +121 -0
  146. stochasticforceinference-2.0.0/examples/manual_examples/abp_largescale_demo.py +313 -0
  147. stochasticforceinference-2.0.0/examples/manual_examples/abp_largescale_infer.py +291 -0
  148. stochasticforceinference-2.0.0/examples/manual_examples/abp_largescale_plot.py +343 -0
  149. stochasticforceinference-2.0.0/examples/manual_examples/abp_largescale_simulate.py +83 -0
  150. stochasticforceinference-2.0.0/examples/manual_examples/abp_pursuit_demo.py +475 -0
  151. stochasticforceinference-2.0.0/pyproject.toml +86 -0
  152. stochasticforceinference-2.0.0/setup.cfg +4 -0
  153. stochasticforceinference-2.0.0/tests/test_dual_mask.py +185 -0
  154. stochasticforceinference-2.0.0/tests/test_parametric_mask.py +202 -0
  155. stochasticforceinference-2.0.0/tests/test_parametric_mask_abp.py +238 -0
  156. stochasticforceinference-2.0.0/tests/test_same_particle_jacobian.py +240 -0
@@ -0,0 +1,402 @@
1
+ # AGENTS.md — guide for AI coding agents working on SFI
2
+
3
+ > **Read this first.** Canonical entry point for automated coding
4
+ > agents (GitHub Copilot, Claude Code, Cursor, …) on the Stochastic
5
+ > Force Inference (SFI) repository. Human contributors should read
6
+ > [`CONTRIBUTING.md`](CONTRIBUTING.md).
7
+
8
+ ---
9
+
10
+ ## 1. What this project is
11
+
12
+ SFI is a JAX-based Python package for **inferring drift (force) and
13
+ diffusion fields from stochastic trajectory data** (overdamped and
14
+ underdamped Langevin SDEs). Targets real experimental data — tracked
15
+ particles, cells, organisms — where no dynamical model exists yet.
16
+ See [`README.md`](README.md) and
17
+ [`docs/source/index.rst`](docs/source/index.rst).
18
+
19
+ ## 2. Before you write code
20
+
21
+ 1. Read this file end-to-end.
22
+ 2. Open the playbook matching your task:
23
+ - Apply inference to a dataset →
24
+ [`docs/source/agent_playbooks/apply_inference.rst`](docs/source/agent_playbooks/apply_inference.rst)
25
+ - Add a feature to SFI →
26
+ [`docs/source/agent_playbooks/add_feature.rst`](docs/source/agent_playbooks/add_feature.rst)
27
+ 3. Skim §4 below — the canonical-imports table.
28
+ 4. Plotting? read [`GALLERY_STYLE_GUIDE.md`](GALLERY_STYLE_GUIDE.md).
29
+ 5. Need the full public surface?
30
+ [`_project_index/api_map.md`](_project_index/api_map.md) (human)
31
+ or [`_project_index/api_map.json`](_project_index/api_map.json)
32
+ (machine).
33
+
34
+ ## 3. Core principle — reuse, don't re-implement
35
+
36
+ SFI has mature, well-tested abstractions for every common operation.
37
+ **Do not re-implement them.**
38
+
39
+ | If you need to… | Use this (not custom code) |
40
+ | ---------------------------------------- | ------------------------------------------------------- |
41
+ | Load trajectories / compute increments | `SFI.trajectory.TrajectoryCollection` |
42
+ | Simulate an SDE | `SFI.langevin.OverdampedProcess` / `UnderdampedProcess` |
43
+ | Build a polynomial / pair / SPDE basis | `SFI.bases.*` helpers |
44
+ | Write a force as a symbolic expression | Compositional primitives: `x_components`, `v_components`, `unit_axes`, `frame`, `named_scalar(s)` — see [`docs/source/bases/user_guide.rst`](docs/source/bases/user_guide.rst) |
45
+ | Wrap a user function as a state function | `SFI.statefunc.make_basis` / `make_sf` / `make_psf` |
46
+ | Run force / diffusion inference | `SFI.OverdampedLangevinInference` / `UnderdampedLangevinInference` |
47
+ | Select a sparse model | `inf.sparsify_force(criterion="PASTIS", p=0.1)` |
48
+ | Compare to ground truth | `inf.compare_to_exact(model_exact=...)` |
49
+ | Plot in the SFI style | `SFI.utils.plotting.SFI_COLORS`, `dark_fig`, `phase2d` |
50
+ | Save / load a fitted model | `SFI.inference.save_model` / `load_model` |
51
+
52
+ If you find yourself writing an Euler step, a finite-difference
53
+ velocity reconstructor, a mask-aware increment, a polynomial feature
54
+ builder, a Gram-matrix assembler, or an information-criterion scorer —
55
+ **stop and search the codebase first**.
56
+
57
+ ## 4. Canonical imports
58
+
59
+ Top-level symbols are re-exported from `SFI/__init__.py`; prefer
60
+ `from SFI import X` whenever available. For the exhaustive surface
61
+ (every public function and class), see
62
+ [`_project_index/api_map.md`](_project_index/api_map.md).
63
+
64
+ ### 4.1 Top-level
65
+
66
+ ```python
67
+ from SFI import (
68
+ OverdampedLangevinInference, # 1st-order Langevin inference
69
+ UnderdampedLangevinInference, # 2nd-order (velocity-unobserved)
70
+ InferenceResultSF, # fitted SF with covariance + metadata
71
+ TrajectoryCollection, # multi-trajectory container
72
+ TrajectoryDataset, # single-trajectory container
73
+ Basis, PSF, SF, make_sf, # state-function core
74
+ classify_dynamics, # OD-vs-UD classifier -> DynamicsOrderReport
75
+ )
76
+ ```
77
+
78
+ The dynamics-order classifier (`classify_dynamics`, returning a
79
+ `DynamicsOrderReport`) decides overdamped vs underdamped from raw
80
+ positions; see `SFI.diagnostics` and `examples/gallery/dynamics_order_demo.py`.
81
+
82
+ ### 4.2 `SFI.statefunc` — composable state functions
83
+
84
+ | Symbol | Purpose |
85
+ | -------------------------------- | ----------------------------------------- |
86
+ | `Basis` | Dictionary of functions (no params) |
87
+ | `PSF` | Parametric family `F(x; θ)` |
88
+ | `SF` | State function with frozen parameters |
89
+ | `make_sf(func, dim, rank, ...)` | Wrap a Python callable as an `SF` |
90
+ | `make_psf(func, dim, rank, ...)` | Wrap a callable with parameters as `PSF` |
91
+ | `make_basis(func, dim, ...)` | Wrap a vector-valued callable as `Basis` |
92
+ | `Interactor`, `make_interactor` | Multi-scale interaction graphs |
93
+
94
+ ### 4.3 `SFI.inference` — inference engines
95
+
96
+ | Symbol | Purpose |
97
+ | --------------------------------------------------- | ------------------------------------------ |
98
+ | `OverdampedLangevinInference(collection)` | Overdamped inference engine |
99
+ | `UnderdampedLangevinInference(collection)` | Underdamped inference engine |
100
+ | `InferenceResultSF` | Fitted model + parameter covariance |
101
+ | `save_model` / `load_model` | Model (SF/PSF) serialisation |
102
+ | `save_results` / `load_results` | Full inference-object serialisation |
103
+
104
+ **Inference paths — two first-class estimator families.** They trade
105
+ compute for robustness; route by data regime, not by habit:
106
+
107
+ | Data regime | Use | Why |
108
+ | --- | --- | --- |
109
+ | Clean data, fine sampling | linear | exact in this limit; fastest |
110
+ | Quick exploration / huge dataset | linear | closed form, no initial guess |
111
+ | Measurement (localization) noise | parametric | profiles Λ; EIV instrument removes the noise bias |
112
+ | Coarse sampling (large Δt) | parametric | RK4 flow step replaces the Euler secant |
113
+ | Model nonlinear in θ (NN drift, gated PSF) | parametric | the L-BFGS path is the only option |
114
+ | Multi-particle / interacting | either | linear for speed; parametric when noise/coarse Δt too |
115
+ | SPDE / grid fields | linear | the (experimental) SPDE toolbox is linear-only |
116
+
117
+ When in doubt, run the linear estimator first to fix scales and
118
+ candidate terms, then confirm with the parametric estimator —
119
+ agreement is itself a diagnostic.
120
+
121
+ * **Linear estimators (fast, closed-form).** Methods
122
+ `compute_diffusion_constant`, `infer_force_linear`,
123
+ `infer_diffusion_linear`. Closed-form projection onto a `Basis`,
124
+ exact in the fine-sampling, low-noise limit; with `"auto"` settings
125
+ they apply the published SFI methodology's best refinements. Canonical
126
+ read-out after a fit (each accessor has a distinct job):
127
+
128
+ | Accessor | What it gives |
129
+ | --- | --- |
130
+ | `inf.force_inferred(x)` | Evaluate the inferred force at points (callable; carries parameter covariance) |
131
+ | `inf.force_predicted_MSE` | Scalar predicted NMSE (accuracy estimate) |
132
+ | `inf.force_coefficients` | Fitted coefficients |
133
+ | `inf.print_report()` / `inf.report_dict()` | Human / machine summary |
134
+ | `inf.compare_to_exact(model_exact=...)` | Validate against a known model |
135
+ | `inf.holdout_score(test)` | Held-out NMSE on `coll.split_time(...)` test data (data-abundant scenarios only) |
136
+
137
+ * **Parametric estimators (robust, flexible; compute-intensive).**
138
+ Robust to measurement noise
139
+ and finite Δt. Methods `infer_force` and `infer_diffusion`
140
+ (implemented in `SFI.inference.parametric_core`). Takes a `Basis`
141
+ (linear-in-θ; PASTIS sparsification wired) or a `PSF` (general
142
+ parametric family): a single RK4 flow step per observation interval
143
+ defines the residual, the banded residual covariance gives a
144
+ windowed-precision NLL, and `(D, Λ)` are profiled natively
145
+ (moment-estimator init + one conditional-NLL refinement). Two inner
146
+ solvers, selected by `inner="auto"`:
147
+
148
+ * `inner="gn"` — direct Gauss–Newton on the windowed Gram (the
149
+ linear-in-θ fast path) with the skip-trick errors-in-variables
150
+ instrument (`eiv=True`, consistent under measurement noise).
151
+ * `inner="lbfgs"` — frozen-precision L-BFGS for non-linear-in-θ
152
+ families (neural-net drift, gated PSFs, …); raise `inner_maxiter`
153
+ for large models. Worked example:
154
+ [`examples/gallery/advanced/nn_force_demo.py`](examples/gallery/advanced/nn_force_demo.py).
155
+
156
+ Multi-particle / interacting systems are supported in both overdamped
157
+ and underdamped regimes (the multi-particle path uses the per-edge
158
+ `d_x(same_particle=True)` / `d_v(same_particle=True)` Jacobian
159
+ protocol; O(N) per window).
160
+
161
+ See
162
+ [`docs/source/inference/user_guide.rst`](docs/source/inference/user_guide.rst)
163
+ for the regime table and workflow.
164
+
165
+ **Linear-estimator sequence:**
166
+
167
+ ```python
168
+ inf = OverdampedLangevinInference(collection)
169
+ inf.compute_diffusion_constant(method="auto") # 1. constant D ("noisy"/"WeakNoise"/"MSD")
170
+ inf.infer_force_linear(basis, preset="auto") # 2. force ("auto"/"robust"/"clean"/"KM"/legacy)
171
+ inf.infer_diffusion_linear() # 3. optional: constant sym D (default basis)
172
+ inf.compute_force_error() # 4. error analysis
173
+ inf.sparsify_force(criterion="PASTIS", p=0.1) # 5. optional: sparsity
174
+ inf.print_report() # 6. summary
175
+ inf.compare_to_exact(model_exact=process) # 7. optional: validate
176
+ ```
177
+
178
+ **Parametric-estimator sequence:**
179
+
180
+ ```python
181
+ inf = OverdampedLangevinInference(collection)
182
+ inf.infer_force(F) # Basis or PSF; profiles (D, Λ)
183
+ inf.infer_diffusion() # no-arg: defaults to symmetric_matrix_basis
184
+ inf.compute_force_error()
185
+ ```
186
+
187
+ **Sparsity.** PASTIS is the canonical criterion in SFI. Alternative
188
+ criteria (`AIC`, `BIC`, `SIC`) and strategies (greedy stepwise,
189
+ STLSQ, LASSO) are available — see
190
+ [`_project_index/api_map.md`](_project_index/api_map.md#sfiinferencesparse)
191
+ for the full inventory. Precision/recall and held-out NMSE helpers
192
+ live in `SFI.inference.sparse.overlap_metrics` and `predictive_nmse`.
193
+
194
+ ### 4.4 `SFI.trajectory` — data containers
195
+
196
+ Construction:
197
+
198
+ | Symbol | Purpose |
199
+ | ------------------------------------------------------ | ------------------------------------------------------ |
200
+ | `TrajectoryDataset.from_arrays(X=, dt=, ...)` | Single dataset from dense tensors `(T, N, d)` |
201
+ | `TrajectoryCollection.from_arrays(X=, dt=, ...)` | Collection wrapping one dense dataset |
202
+ | `TrajectoryCollection.from_dataframe(df, particle=, time=, coords=)` | Pandas tracking table → collection: columns by **name** (auto-detected when omitted), junk columns dropped, extras prefixes parsed |
203
+ | `TrajectoryCollection.from_columns(particle_idx, time_idx, state_vectors, ...)` | **Canonical multi-particle entry**: flat columns (one row per observation) → collection. Accepts tabular data where particles enter/leave at different times |
204
+ | `TrajectoryCollection.from_dataset(ds, weights=...)` | Promote a single dataset into a collection |
205
+ | `TrajectoryCollection.concat(items, weights="equal")` | Combine several collections (multi-experiment) |
206
+ | `TrajectoryCollection.load(path)` | Load from CSV/Parquet/HDF5 (multi-file via list) |
207
+ | `TimeSeriesExtra` / `time_series_extra` | Time-dependent extras (leading time axis; sliced per frame in inference, per-frame schedules in simulation) |
208
+ | `FunctionExtra`, `function_extra` | Register on-demand computed extras |
209
+
210
+ Notes:
211
+ - For tracked-particle data where the number of particles changes
212
+ over time, `from_columns` is the canonical path — **do not pre-pad
213
+ with NaNs and call `from_arrays`.**
214
+ - Synthetic degradation (noise / downsampling / data-loss /
215
+ motion blur) lives in `SFI.trajectory.degrade`.
216
+ - Low-level I/O lives in `SFI.trajectory.io` (used internally by
217
+ `TrajectoryCollection.load`).
218
+
219
+ ### 4.5 `SFI.langevin` — simulators
220
+
221
+ | Symbol | Purpose |
222
+ | ------------------------------------------------ | -------------------------------- |
223
+ | `OverdampedProcess(F, D)` | Euler–Maruyama / Heun integrator |
224
+ | `UnderdampedProcess(F, D, M)` | Velocity-Verlet / BAOAB |
225
+ | `WhiteNoise`, `ConservedNoise`, `CompositeNoise` | Noise models |
226
+
227
+ Pass a `Basis` (or a `PSF`, for a parametric family) directly as `F`,
228
+ with coefficients via `theta_F` — e.g.
229
+ `OverdampedProcess(F=X(dim=1), D=0.5, theta_F=jnp.array([-1.0]))`. Avoid
230
+ `make_basis(...).to_psf()` unless a parametric family is genuinely
231
+ required.
232
+
233
+ ### 4.6 `SFI.bases` — ready-made basis builders
234
+
235
+ | Symbol | Purpose |
236
+ | --------------------------------------------------- | ------------------------------------ |
237
+ | `monomials_up_to(order, dim, rank="scalar", ...)` | Polynomials up to total order |
238
+ | `monomials_degree(degree, dim, ...)` | Polynomials of exact total degree |
239
+ | `ones_basis(dim)` | Constant 1 |
240
+ | `unit_vector_basis(dim)` | Cartesian unit vectors |
241
+ | `unit_axes(dim)` | Per-axis unit-vector primitives `ex, ey, …` |
242
+ | `x_components(dim)` / `v_components(dim)` | Per-axis coordinate / velocity primitives |
243
+ | `frame(dim, velocity=False)` | `(x, axes)` or `(x, v, axes)` convenience |
244
+ | `named_scalar(name, default=...)` | Parametric scalar PSF with optional default |
245
+ | `extra_scalar(name)` | Basis symbol reading `extras[name]` (conditions, drive protocols, per-particle properties) |
246
+ | `per_dataset_scalar(name, n)` / `dataset_indicator(n)` | Experiment-specific parameters in pooled multi-experiment fits (parametric / linear route), via the auto-injected `dataset_index` extra |
247
+ | `named_scalars(*names)` / `named_scalars(**kw)` | Batch of named scalars (mutually exclusive) |
248
+ | `identity_matrix_basis(dim)` | Isotropic I |
249
+ | `symmetric_matrix_basis(dim)` | Symmetric-matrix templates |
250
+ | `linear_basis(dim)` | Coordinate-extraction identity |
251
+ | `X`, `V`, `x_coordinate`, `v_coordinate`, … | Coordinate accessors |
252
+ | `SFI.bases.pairs.*` | Pair-interaction builders (lazy) |
253
+ | `SFI.bases.spde.*` | PDE differential operators (lazy) |
254
+
255
+ **Canonical idiom for vector force bases.** For a polynomial library,
256
+ use the built-in shortcut `monomials_up_to(order, dim, rank='vector')`.
257
+ To lift a scalar basis you built compositionally, call
258
+ `scalar_basis.vectorize(dim)` (not
259
+ `scalar_basis * unit_vector_basis(dim)`). When the force is literally a
260
+ coordinate, use `X(dim)` / `V(dim)`.
261
+
262
+ See [`docs/source/bases/user_guide.rst`](docs/source/bases/user_guide.rst)
263
+ for the decision tree (compositional algebra vs. factories) and
264
+ canonical patterns (Lorenz, harmonic UD, limit cycle, double-well).
265
+
266
+ ABP / active-matter forces are **not** prepackaged as a basis — they are
267
+ too example-specific. Build them directly from `SFI.bases.pairs`
268
+ primitives (``heading_vector``, ``pbc_displacement``, ``wrap_angle``); see
269
+ `examples/_gallery_utils/abp.py` for a worked composition.
270
+
271
+ ### 4.7 `SFI.integrate` — time-averaging engine (backend)
272
+
273
+ **Backend module — do not call from user code.** Used internally by
274
+ the inference engines to build Gram matrices and time-averaged
275
+ operators. Central to the design: agents *extending* SFI (new
276
+ estimators, new observables) will interact with it; agents *applying*
277
+ SFI to data should never need to.
278
+
279
+ Entry points (for feature work only):
280
+
281
+ | Symbol | Purpose |
282
+ | ------------------------------------------- | --------------------------------------- |
283
+ | `integrate(collection, program, reduce)` | One-shot time-average |
284
+ | `make_parametric_integrator(program)` | Build a reusable JIT-compiled integrator|
285
+ | `Integrand`, `Term` | Compose expressions with Einstein sums |
286
+ | `stream`, `timeop`, `velocity` | Operand builders |
287
+
288
+ ### 4.8 `SFI.utils`
289
+
290
+ ```python
291
+ # Plotting (gallery/notebook helpers — not in __all__). Always take a
292
+ # TrajectoryCollection; never reach into coll.datasets[0].X by hand.
293
+ from SFI.utils.plotting import SFI_COLORS, dark_fig, dark_ax, wrap_positions
294
+ from SFI.utils.plotting import timeseries, timeseries_colored # x_d(t) lines / colored
295
+ from SFI.utils.plotting import phase2d, phase2d_scalar, phase3d # phase-space (2d/3d/scalar-colored)
296
+ from SFI.utils.plotting import trajectory_scatter # all-frames density cloud
297
+ from SFI.utils.plotting import plot_field, plot_tensor_field, stream_field
298
+ from SFI.utils.plotting import plot_profile_1d, plot_field_error # 1d F/D overlay; 2d ‖ΔF‖ heatmap
299
+ from SFI.utils.plotting import plot_particles, plot_nematic_director, plot_rods
300
+ from SFI.utils.plotting import plot_spde_snapshot, spatial_acorr2d # gridded/SPDE fields
301
+ from SFI.utils.plotting import animate_particles, animate_spde_comparison
302
+ from SFI.utils.plotting import comparison_scatter, plot_pareto_front
303
+ from SFI.utils.plotting import plot_recovery_bar, plot_recovery_bar_multi, plot_recovery_matrix
304
+ from SFI.utils.plotting import plot_time_profile_comparison
305
+
306
+ # Formatting
307
+ from SFI.utils.formatting import model_summary, print_model_comparison
308
+
309
+ # Numerics (also re-exported via SFI.utils)
310
+ from SFI.utils.maths import stable_pinv, sqrtm_psd, solve_or_pinv, fd_velocity
311
+ from SFI.utils.maths import default_float_dtype, as_default_float
312
+
313
+ # Neighbor lists (host-side, call between JIT chunks)
314
+ from SFI.utils.neighbors import build_neighbor_csr, make_neighbor_extras, pad_neighbor_csr
315
+ ```
316
+
317
+ Inferred-vs-exact read-outs live on the inference object:
318
+ `inf.comparison_scatter(model_exact=, field=)`,
319
+ `inf.force_comparison_arrays(model_exact=)`,
320
+ `inf.coeff_block(block, field=)`, `inf.predict_time_profile(basis, t)`,
321
+ `inf.compare_params_to_exact(theta_true, psf=)`. Collection helpers:
322
+ `coll.to_arrays()` / `coll.to_array()` (numpy materialization),
323
+ `coll.velocity_array(scheme=)` (finite-diff velocity), `coll.merge([...])`.
324
+ Use `coll.T` (frames) and `coll.d` (dimension) instead of
325
+ `coll.datasets[0].X.shape`.
326
+
327
+ `SFI_COLORS` is the semantic palette (`data`, `inferred`, `exact`,
328
+ …) — see
329
+ [`GALLERY_STYLE_GUIDE.md`](GALLERY_STYLE_GUIDE.md#color-palette).
330
+
331
+ ## 5. What to treat as private / not authoritative
332
+
333
+ - **Anything prefixed `_`** (modules, functions, attributes) is a private
334
+ internal: it may change without notice, so don't depend on it from user
335
+ code or new features.
336
+ - `*.bak` files, if you ever see one, are stale snapshots — ignore them.
337
+
338
+ Deprecated / soon-to-be-removed symbols (do not use in new code):
339
+ - (none currently)
340
+
341
+ ## 6. Testing, building, environment
342
+
343
+ Full details: [`docs/source/dev_notes.rst`](docs/source/dev_notes.rst).
344
+
345
+ ```bash
346
+ source .venv/bin/activate
347
+ pytest tests/ -v # full suite (CPU-only via conftest)
348
+ pytest tests/ -x --ff # quick smoke
349
+ cd docs && make clean && make html # fast docs build
350
+ cd docs && SFI_DOCS_RUN_GALLERY=1 make html # full build (slow)
351
+ ```
352
+
353
+ JAX persistent cache is opt-in:
354
+ `export SFI_JAX_CACHE_DIR=~/.cache/sfi/jax_cache`.
355
+
356
+ ## 7. Conventions
357
+
358
+ - Import each symbol from the **highest level at which it is exposed**:
359
+ `from SFI import X` when re-exported at top level, otherwise from the
360
+ subpackage (`from SFI.bases import monomials_up_to`) — never from a
361
+ leaf module (`SFI.bases.monomials`, `SFI.bases.linear`).
362
+ - New public symbols → add to the submodule's `__init__.py` and, if
363
+ top-level, also to `SFI/__init__.py`.
364
+ - Plots: use `SFI_COLORS`, `dark_fig` for dark-theme figures; never pure black.
365
+ - Tests: unit tests in `tests/<subpackage>/test_*.py`; files named
366
+ `validate_*.py`, `audit_*.py` are **not**
367
+ collected by pytest.
368
+ - Docstrings: numpy-style (Sphinx autodoc).
369
+ - JIT: toggle globally with `SFI.statefunc.set_jit(False)` to debug.
370
+
371
+ ## 8. Asking vs guessing
372
+
373
+ If this file, the playbooks, and the source code together do not
374
+ answer a design question, **ask the user** rather than guessing.
375
+ Silent assumptions in a feature PR are the main cause of rework here.
376
+
377
+ ## 9. Keeping these agent docs fresh
378
+
379
+ This file and the machine-readable API map drift out of sync fast.
380
+ **When you change public API, you must:**
381
+
382
+ 1. Update §4 of this file (add / remove / rename symbols).
383
+ 2. Update the matching row in `_project_index/api_map.md` **or**
384
+ regenerate both files:
385
+ ```bash
386
+ python scripts/gen_api_map.py
387
+ ```
388
+ 3. If you deprecated a symbol, add a row to §5.
389
+
390
+ Automated guards (run in CI; see
391
+ [`.github/workflows/agent_docs.yml`](.github/workflows/agent_docs.yml)):
392
+
393
+ - `scripts/gen_api_map.py --check` fails if the committed API map is
394
+ stale relative to the current `SFI/__init__.py`.
395
+ - A pre-commit hook is available —
396
+ `pre-commit install` once, and the map is regenerated automatically
397
+ on any commit that touches `SFI/**/*.py` (config:
398
+ [`.pre-commit-config.yaml`](.pre-commit-config.yaml)).
399
+
400
+ The add-feature playbook
401
+ ([`docs/source/agent_playbooks/add_feature.rst`](docs/source/agent_playbooks/add_feature.rst))
402
+ has a matching checklist step — don't skip it.
@@ -0,0 +1,53 @@
1
+ # Contributing to SFI
2
+
3
+ Thank you for contributing!
4
+
5
+ ## Quick start
6
+
7
+ ```bash
8
+ git clone https://github.com/ronceray/StochasticForceInference.git
9
+ cd StochasticForceInference
10
+ python -m venv .venv && source .venv/bin/activate
11
+ pip install -e ".[dev,io]"
12
+ pytest tests/ -v
13
+ ```
14
+
15
+ Full environment and build details:
16
+ [`docs/source/dev_notes.rst`](docs/source/dev_notes.rst).
17
+
18
+ ## Where things live
19
+
20
+ - `SFI/` — the package. See [`AGENTS.md`](AGENTS.md) §4 for the
21
+ canonical imports table.
22
+ - `examples/gallery/` — Sphinx-gallery demos (follow
23
+ [`GALLERY_STYLE_GUIDE.md`](GALLERY_STYLE_GUIDE.md)).
24
+ - `examples/benchmarks/` — validation / regression benchmarks (follow
25
+ [`docs/source/dev_benchmark_patterns.md`](docs/source/dev_benchmark_patterns.md)).
26
+ - `tests/` — pytest suite. Files matching `benchmark_*.py`,
27
+ `validate_*.py`, `audit_*.py` are **not** collected; they are
28
+ manually-invoked scripts.
29
+ - `docs/source/` — Sphinx documentation source.
30
+
31
+ ## Style
32
+
33
+ - Numpy-style docstrings (rendered by Sphinx autodoc).
34
+ - Plots must call `SFI.utils.plotting.apply_style()` and use
35
+ `SFI_COLORS`; never pure black.
36
+ - New public symbols should be re-exported from the relevant
37
+ `__init__.py`.
38
+
39
+ ## Using AI coding assistants
40
+
41
+ If you use GitHub Copilot, Claude Code, Cursor, or similar agents,
42
+ they will pick up the canonical guide at [`AGENTS.md`](AGENTS.md).
43
+ Skim it so you and your assistant are on the same page.
44
+
45
+ ## Pull requests
46
+
47
+ - Add or update tests for any behaviour change.
48
+ - Run `pytest tests/ -v` locally before pushing.
49
+ - If you add a user-visible feature, add or update a gallery demo and
50
+ the corresponding `docs/source/` user guide.
51
+ - If you touch parametric-estimator internals, run
52
+ `pytest tests/inference/ -v` and re-run the relevant benchmark in
53
+ `tests/benchmark_*.py`.