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.
- stochasticforceinference-2.0.0/AGENTS.md +402 -0
- stochasticforceinference-2.0.0/CONTRIBUTING.md +53 -0
- stochasticforceinference-2.0.0/GALLERY_STYLE_GUIDE.md +391 -0
- stochasticforceinference-2.0.0/LICENSE +21 -0
- stochasticforceinference-2.0.0/MANIFEST.in +39 -0
- stochasticforceinference-2.0.0/PKG-INFO +203 -0
- stochasticforceinference-2.0.0/README.md +149 -0
- stochasticforceinference-2.0.0/SFI/__init__.py +64 -0
- stochasticforceinference-2.0.0/SFI/bases/__init__.py +85 -0
- stochasticforceinference-2.0.0/SFI/bases/constants.py +492 -0
- stochasticforceinference-2.0.0/SFI/bases/linear.py +325 -0
- stochasticforceinference-2.0.0/SFI/bases/monomials.py +218 -0
- stochasticforceinference-2.0.0/SFI/bases/pairs.py +998 -0
- stochasticforceinference-2.0.0/SFI/bases/spde.py +1537 -0
- stochasticforceinference-2.0.0/SFI/diagnostics/__init__.py +60 -0
- stochasticforceinference-2.0.0/SFI/diagnostics/assess.py +87 -0
- stochasticforceinference-2.0.0/SFI/diagnostics/dynamics_order.py +621 -0
- stochasticforceinference-2.0.0/SFI/diagnostics/plotting.py +226 -0
- stochasticforceinference-2.0.0/SFI/diagnostics/report.py +238 -0
- stochasticforceinference-2.0.0/SFI/diagnostics/residual_tests.py +395 -0
- stochasticforceinference-2.0.0/SFI/diagnostics/residuals.py +688 -0
- stochasticforceinference-2.0.0/SFI/inference/__init__.py +58 -0
- stochasticforceinference-2.0.0/SFI/inference/base.py +1460 -0
- stochasticforceinference-2.0.0/SFI/inference/optimizers.py +200 -0
- stochasticforceinference-2.0.0/SFI/inference/overdamped.py +1214 -0
- stochasticforceinference-2.0.0/SFI/inference/parametric_core/__init__.py +34 -0
- stochasticforceinference-2.0.0/SFI/inference/parametric_core/covariance.py +232 -0
- stochasticforceinference-2.0.0/SFI/inference/parametric_core/driver.py +272 -0
- stochasticforceinference-2.0.0/SFI/inference/parametric_core/flow.py +149 -0
- stochasticforceinference-2.0.0/SFI/inference/parametric_core/flow_multi.py +362 -0
- stochasticforceinference-2.0.0/SFI/inference/parametric_core/flow_ud.py +168 -0
- stochasticforceinference-2.0.0/SFI/inference/parametric_core/jacobians.py +540 -0
- stochasticforceinference-2.0.0/SFI/inference/parametric_core/objective.py +286 -0
- stochasticforceinference-2.0.0/SFI/inference/parametric_core/objective_ud.py +253 -0
- stochasticforceinference-2.0.0/SFI/inference/parametric_core/precision.py +229 -0
- stochasticforceinference-2.0.0/SFI/inference/parametric_core/solve.py +763 -0
- stochasticforceinference-2.0.0/SFI/inference/result.py +362 -0
- stochasticforceinference-2.0.0/SFI/inference/serialization.py +245 -0
- stochasticforceinference-2.0.0/SFI/inference/sparse/__init__.py +67 -0
- stochasticforceinference-2.0.0/SFI/inference/sparse/base.py +43 -0
- stochasticforceinference-2.0.0/SFI/inference/sparse/beam.py +303 -0
- stochasticforceinference-2.0.0/SFI/inference/sparse/greedy.py +151 -0
- stochasticforceinference-2.0.0/SFI/inference/sparse/hillclimb.py +307 -0
- stochasticforceinference-2.0.0/SFI/inference/sparse/lasso.py +178 -0
- stochasticforceinference-2.0.0/SFI/inference/sparse/metrics.py +78 -0
- stochasticforceinference-2.0.0/SFI/inference/sparse/result.py +278 -0
- stochasticforceinference-2.0.0/SFI/inference/sparse/scorer.py +323 -0
- stochasticforceinference-2.0.0/SFI/inference/sparse/stlsq.py +165 -0
- stochasticforceinference-2.0.0/SFI/inference/sparsity.py +40 -0
- stochasticforceinference-2.0.0/SFI/inference/underdamped.py +1355 -0
- stochasticforceinference-2.0.0/SFI/integrate/__init__.py +38 -0
- stochasticforceinference-2.0.0/SFI/integrate/api.py +920 -0
- stochasticforceinference-2.0.0/SFI/integrate/integrand.py +402 -0
- stochasticforceinference-2.0.0/SFI/integrate/rk4.py +156 -0
- stochasticforceinference-2.0.0/SFI/integrate/timeops.py +174 -0
- stochasticforceinference-2.0.0/SFI/langevin/__init__.py +29 -0
- stochasticforceinference-2.0.0/SFI/langevin/base.py +863 -0
- stochasticforceinference-2.0.0/SFI/langevin/chunked.py +225 -0
- stochasticforceinference-2.0.0/SFI/langevin/noise.py +446 -0
- stochasticforceinference-2.0.0/SFI/langevin/overdamped.py +560 -0
- stochasticforceinference-2.0.0/SFI/langevin/underdamped.py +448 -0
- stochasticforceinference-2.0.0/SFI/statefunc/__init__.py +49 -0
- stochasticforceinference-2.0.0/SFI/statefunc/basis.py +87 -0
- stochasticforceinference-2.0.0/SFI/statefunc/core/runtime.py +47 -0
- stochasticforceinference-2.0.0/SFI/statefunc/factory.py +346 -0
- stochasticforceinference-2.0.0/SFI/statefunc/interactor.py +90 -0
- stochasticforceinference-2.0.0/SFI/statefunc/layout/__init__.py +29 -0
- stochasticforceinference-2.0.0/SFI/statefunc/layout/_base.py +186 -0
- stochasticforceinference-2.0.0/SFI/statefunc/layout/_eval_compiler.py +453 -0
- stochasticforceinference-2.0.0/SFI/statefunc/layout/_fd_atoms.py +196 -0
- stochasticforceinference-2.0.0/SFI/statefunc/layout/_grid.py +878 -0
- stochasticforceinference-2.0.0/SFI/statefunc/layout/_sectors.py +166 -0
- stochasticforceinference-2.0.0/SFI/statefunc/memhint.py +222 -0
- stochasticforceinference-2.0.0/SFI/statefunc/nodes/__init__.py +56 -0
- stochasticforceinference-2.0.0/SFI/statefunc/nodes/base.py +318 -0
- stochasticforceinference-2.0.0/SFI/statefunc/nodes/contract.py +422 -0
- stochasticforceinference-2.0.0/SFI/statefunc/nodes/interactions/__init__.py +23 -0
- stochasticforceinference-2.0.0/SFI/statefunc/nodes/interactions/dispatcher.py +1365 -0
- stochasticforceinference-2.0.0/SFI/statefunc/nodes/interactions/prepare.py +161 -0
- stochasticforceinference-2.0.0/SFI/statefunc/nodes/interactions/specs.py +362 -0
- stochasticforceinference-2.0.0/SFI/statefunc/nodes/interactions/stencils.py +718 -0
- stochasticforceinference-2.0.0/SFI/statefunc/nodes/leaf.py +530 -0
- stochasticforceinference-2.0.0/SFI/statefunc/nodes/ops/__init__.py +27 -0
- stochasticforceinference-2.0.0/SFI/statefunc/nodes/ops/concat.py +28 -0
- stochasticforceinference-2.0.0/SFI/statefunc/nodes/ops/derivative.py +447 -0
- stochasticforceinference-2.0.0/SFI/statefunc/nodes/ops/einsum.py +120 -0
- stochasticforceinference-2.0.0/SFI/statefunc/nodes/ops/linear.py +153 -0
- stochasticforceinference-2.0.0/SFI/statefunc/nodes/ops/mapn.py +138 -0
- stochasticforceinference-2.0.0/SFI/statefunc/nodes/ops/reshape_rank.py +158 -0
- stochasticforceinference-2.0.0/SFI/statefunc/nodes/ops/slice.py +83 -0
- stochasticforceinference-2.0.0/SFI/statefunc/params.py +309 -0
- stochasticforceinference-2.0.0/SFI/statefunc/psf.py +112 -0
- stochasticforceinference-2.0.0/SFI/statefunc/sf.py +104 -0
- stochasticforceinference-2.0.0/SFI/statefunc/stateexpr.py +1243 -0
- stochasticforceinference-2.0.0/SFI/statefunc/structexpr.py +1003 -0
- stochasticforceinference-2.0.0/SFI/trajectory/__init__.py +31 -0
- stochasticforceinference-2.0.0/SFI/trajectory/collection.py +1122 -0
- stochasticforceinference-2.0.0/SFI/trajectory/dataset.py +1164 -0
- stochasticforceinference-2.0.0/SFI/trajectory/degrade.py +1108 -0
- stochasticforceinference-2.0.0/SFI/trajectory/io.py +1027 -0
- stochasticforceinference-2.0.0/SFI/trajectory/reserved_extras.py +129 -0
- stochasticforceinference-2.0.0/SFI/utils/__init__.py +17 -0
- stochasticforceinference-2.0.0/SFI/utils/formatting.py +308 -0
- stochasticforceinference-2.0.0/SFI/utils/maths.py +185 -0
- stochasticforceinference-2.0.0/SFI/utils/neighbors.py +162 -0
- stochasticforceinference-2.0.0/SFI/utils/plotting.py +1936 -0
- stochasticforceinference-2.0.0/StochasticForceInference.egg-info/PKG-INFO +203 -0
- stochasticforceinference-2.0.0/StochasticForceInference.egg-info/SOURCES.txt +155 -0
- stochasticforceinference-2.0.0/StochasticForceInference.egg-info/dependency_links.txt +1 -0
- stochasticforceinference-2.0.0/StochasticForceInference.egg-info/requires.txt +30 -0
- stochasticforceinference-2.0.0/StochasticForceInference.egg-info/top_level.txt +1 -0
- stochasticforceinference-2.0.0/examples/_gallery_utils/__init__.py +2 -0
- stochasticforceinference-2.0.0/examples/_gallery_utils/abp.py +101 -0
- stochasticforceinference-2.0.0/examples/_gallery_utils/coarse_grain.py +227 -0
- stochasticforceinference-2.0.0/examples/_gallery_utils/helpers.py +303 -0
- stochasticforceinference-2.0.0/examples/_gallery_utils/scenario_picker.py +167 -0
- stochasticforceinference-2.0.0/examples/convert_active_nematic_mat.py +409 -0
- stochasticforceinference-2.0.0/examples/experimental_data/_analyze_results.py +69 -0
- stochasticforceinference-2.0.0/examples/experimental_data/_expanded_basis.py +66 -0
- stochasticforceinference-2.0.0/examples/experimental_data/generate_tweezer_csv.py +35 -0
- stochasticforceinference-2.0.0/examples/gallery/abp_align_demo.py +574 -0
- stochasticforceinference-2.0.0/examples/gallery/abp_nonreciprocal_demo.py +524 -0
- stochasticforceinference-2.0.0/examples/gallery/abp_to_spde_demo.py +602 -0
- stochasticforceinference-2.0.0/examples/gallery/active_nematic_demo.py +910 -0
- stochasticforceinference-2.0.0/examples/gallery/advanced/flocking_3d_demo.py +314 -0
- stochasticforceinference-2.0.0/examples/gallery/advanced/multi_experiment_demo.py +347 -0
- stochasticforceinference-2.0.0/examples/gallery/advanced/nn_force_demo.py +430 -0
- stochasticforceinference-2.0.0/examples/gallery/anisotropic_diffusion_demo.py +235 -0
- stochasticforceinference-2.0.0/examples/gallery/custom_basis_demo.py +254 -0
- stochasticforceinference-2.0.0/examples/gallery/diagnostics_demo.py +150 -0
- stochasticforceinference-2.0.0/examples/gallery/dynamics_order_demo.py +271 -0
- stochasticforceinference-2.0.0/examples/gallery/experimental_workflow_demo.py +216 -0
- stochasticforceinference-2.0.0/examples/gallery/gray_scott_demo.py +574 -0
- stochasticforceinference-2.0.0/examples/gallery/home_range_demo.py +394 -0
- stochasticforceinference-2.0.0/examples/gallery/limitcycle_demo.py +213 -0
- stochasticforceinference-2.0.0/examples/gallery/lorenz_demo.py +220 -0
- stochasticforceinference-2.0.0/examples/gallery/lotka_volterra_demo.py +319 -0
- stochasticforceinference-2.0.0/examples/gallery/multiplicative_diffusion_demo.py +242 -0
- stochasticforceinference-2.0.0/examples/gallery/ou_demo.py +413 -0
- stochasticforceinference-2.0.0/examples/gallery/time_dependent_forcing_demo.py +156 -0
- stochasticforceinference-2.0.0/examples/gallery/time_fourier_demo.py +234 -0
- stochasticforceinference-2.0.0/examples/gallery/van_der_pol_demo.py +245 -0
- stochasticforceinference-2.0.0/examples/gallery/velocity_dependent_noise_demo.py +203 -0
- stochasticforceinference-2.0.0/examples/manual_examples/abp_align_demo.py +617 -0
- stochasticforceinference-2.0.0/examples/manual_examples/abp_largescale_config.py +121 -0
- stochasticforceinference-2.0.0/examples/manual_examples/abp_largescale_demo.py +313 -0
- stochasticforceinference-2.0.0/examples/manual_examples/abp_largescale_infer.py +291 -0
- stochasticforceinference-2.0.0/examples/manual_examples/abp_largescale_plot.py +343 -0
- stochasticforceinference-2.0.0/examples/manual_examples/abp_largescale_simulate.py +83 -0
- stochasticforceinference-2.0.0/examples/manual_examples/abp_pursuit_demo.py +475 -0
- stochasticforceinference-2.0.0/pyproject.toml +86 -0
- stochasticforceinference-2.0.0/setup.cfg +4 -0
- stochasticforceinference-2.0.0/tests/test_dual_mask.py +185 -0
- stochasticforceinference-2.0.0/tests/test_parametric_mask.py +202 -0
- stochasticforceinference-2.0.0/tests/test_parametric_mask_abp.py +238 -0
- 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`.
|