StochasticForceInference 2.0.0.post1__tar.gz → 2.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.
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/AGENTS.md +29 -12
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/PKG-INFO +1 -1
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/inference/base.py +8 -1
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/inference/overdamped.py +317 -54
- stochasticforceinference-2.0.1/SFI/inference/parametric_core/__init__.py +32 -0
- stochasticforceinference-2.0.1/SFI/inference/parametric_core/banded.py +279 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/inference/parametric_core/covariance.py +105 -14
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/inference/parametric_core/driver.py +55 -5
- stochasticforceinference-2.0.1/SFI/inference/parametric_core/flow_multi.py +494 -0
- stochasticforceinference-2.0.1/SFI/inference/parametric_core/gram.py +21 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/inference/parametric_core/jacobians.py +191 -49
- stochasticforceinference-2.0.1/SFI/inference/parametric_core/precompute.py +221 -0
- stochasticforceinference-2.0.1/SFI/inference/parametric_core/runner.py +853 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/inference/parametric_core/solve.py +421 -142
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/inference/underdamped.py +510 -26
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/integrate/rk4.py +1 -1
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/langevin/underdamped.py +121 -20
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/statefunc/nodes/ops/derivative.py +11 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/statefunc/nodes/ops/reshape_rank.py +5 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/statefunc/nodes/ops/slice.py +5 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/trajectory/dataset.py +14 -10
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/StochasticForceInference.egg-info/PKG-INFO +1 -1
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/StochasticForceInference.egg-info/SOURCES.txt +7 -5
- stochasticforceinference-2.0.1/examples/_gallery_utils/bench_utils.py +27 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/examples/_gallery_utils/helpers.py +11 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/examples/gallery/abp_align_demo.py +3 -3
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/examples/gallery/abp_nonreciprocal_demo.py +11 -6
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/examples/gallery/abp_to_spde_demo.py +3 -3
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/examples/gallery/advanced/flocking_3d_demo.py +4 -4
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/examples/gallery/advanced/multi_experiment_demo.py +2 -2
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/examples/gallery/advanced/nn_force_demo.py +161 -70
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/examples/gallery/anisotropic_diffusion_demo.py +2 -3
- stochasticforceinference-2.0.1/examples/gallery/entropy_production_demo.py +561 -0
- stochasticforceinference-2.0.1/examples/gallery/entropy_production_underdamped_demo.py +464 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/examples/gallery/experimental_workflow_demo.py +2 -2
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/examples/gallery/gray_scott_demo.py +2 -2
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/examples/gallery/home_range_demo.py +2 -2
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/examples/gallery/lorenz_demo.py +0 -1
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/examples/gallery/lotka_volterra_demo.py +7 -7
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/examples/gallery/multiplicative_diffusion_demo.py +2 -4
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/examples/gallery/velocity_dependent_noise_demo.py +2 -3
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/pyproject.toml +1 -1
- stochasticforceinference-2.0.0.post1/SFI/inference/parametric_core/__init__.py +0 -34
- stochasticforceinference-2.0.0.post1/SFI/inference/parametric_core/flow_multi.py +0 -362
- stochasticforceinference-2.0.0.post1/SFI/inference/parametric_core/objective.py +0 -286
- stochasticforceinference-2.0.0.post1/SFI/inference/parametric_core/objective_ud.py +0 -253
- stochasticforceinference-2.0.0.post1/SFI/inference/parametric_core/precision.py +0 -229
- stochasticforceinference-2.0.0.post1/examples/convert_active_nematic_mat.py +0 -409
- stochasticforceinference-2.0.0.post1/examples/gallery/active_nematic_demo.py +0 -910
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/CONTRIBUTING.md +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/GALLERY_STYLE_GUIDE.md +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/LICENSE +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/MANIFEST.in +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/README.md +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/__init__.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/bases/__init__.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/bases/constants.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/bases/linear.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/bases/monomials.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/bases/pairs.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/bases/spde.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/diagnostics/__init__.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/diagnostics/assess.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/diagnostics/dynamics_order.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/diagnostics/plotting.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/diagnostics/report.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/diagnostics/residual_tests.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/diagnostics/residuals.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/inference/__init__.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/inference/optimizers.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/inference/parametric_core/flow.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/inference/parametric_core/flow_ud.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/inference/result.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/inference/serialization.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/inference/sparse/__init__.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/inference/sparse/base.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/inference/sparse/beam.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/inference/sparse/greedy.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/inference/sparse/hillclimb.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/inference/sparse/lasso.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/inference/sparse/metrics.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/inference/sparse/result.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/inference/sparse/scorer.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/inference/sparse/stlsq.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/inference/sparsity.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/integrate/__init__.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/integrate/api.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/integrate/integrand.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/integrate/timeops.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/langevin/__init__.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/langevin/base.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/langevin/chunked.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/langevin/noise.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/langevin/overdamped.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/statefunc/__init__.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/statefunc/basis.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/statefunc/core/runtime.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/statefunc/factory.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/statefunc/interactor.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/statefunc/layout/__init__.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/statefunc/layout/_base.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/statefunc/layout/_eval_compiler.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/statefunc/layout/_fd_atoms.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/statefunc/layout/_grid.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/statefunc/layout/_sectors.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/statefunc/memhint.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/statefunc/nodes/__init__.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/statefunc/nodes/base.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/statefunc/nodes/contract.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/statefunc/nodes/interactions/__init__.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/statefunc/nodes/interactions/dispatcher.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/statefunc/nodes/interactions/prepare.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/statefunc/nodes/interactions/specs.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/statefunc/nodes/interactions/stencils.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/statefunc/nodes/leaf.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/statefunc/nodes/ops/__init__.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/statefunc/nodes/ops/concat.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/statefunc/nodes/ops/einsum.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/statefunc/nodes/ops/linear.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/statefunc/nodes/ops/mapn.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/statefunc/params.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/statefunc/psf.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/statefunc/sf.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/statefunc/stateexpr.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/statefunc/structexpr.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/trajectory/__init__.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/trajectory/collection.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/trajectory/degrade.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/trajectory/io.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/trajectory/reserved_extras.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/utils/__init__.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/utils/formatting.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/utils/maths.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/utils/neighbors.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/utils/plotting.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/StochasticForceInference.egg-info/dependency_links.txt +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/StochasticForceInference.egg-info/requires.txt +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/StochasticForceInference.egg-info/top_level.txt +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/examples/_gallery_utils/__init__.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/examples/_gallery_utils/abp.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/examples/_gallery_utils/coarse_grain.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/examples/_gallery_utils/scenario_picker.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/examples/experimental_data/_analyze_results.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/examples/experimental_data/_expanded_basis.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/examples/experimental_data/generate_tweezer_csv.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/examples/gallery/custom_basis_demo.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/examples/gallery/diagnostics_demo.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/examples/gallery/dynamics_order_demo.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/examples/gallery/limitcycle_demo.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/examples/gallery/ou_demo.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/examples/gallery/time_dependent_forcing_demo.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/examples/gallery/time_fourier_demo.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/examples/gallery/van_der_pol_demo.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/examples/manual_examples/abp_align_demo.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/examples/manual_examples/abp_largescale_config.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/examples/manual_examples/abp_largescale_demo.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/examples/manual_examples/abp_largescale_infer.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/examples/manual_examples/abp_largescale_plot.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/examples/manual_examples/abp_largescale_simulate.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/examples/manual_examples/abp_pursuit_demo.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/setup.cfg +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/tests/test_dual_mask.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/tests/test_parametric_mask.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/tests/test_parametric_mask_abp.py +0 -0
- {stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/tests/test_same_particle_jacobian.py +0 -0
|
@@ -110,7 +110,8 @@ compute for robustness; route by data regime, not by habit:
|
|
|
110
110
|
| Quick exploration / huge dataset | linear | closed form, no initial guess |
|
|
111
111
|
| Measurement (localization) noise | parametric | profiles Λ; EIV instrument removes the noise bias |
|
|
112
112
|
| Coarse sampling (large Δt) | parametric | RK4 flow step replaces the Euler secant |
|
|
113
|
-
|
|
|
113
|
+
| Non-uniform sampling (irregular `t` / per-step `dt`) | parametric | per-interval flow steps + process covariances, no resampling |
|
|
114
|
+
| Model nonlinear in θ (NN drift, gated PSF) | parametric | the damped-GN / L-BFGS paths are the only option |
|
|
114
115
|
| Multi-particle / interacting | either | linear for speed; parametric when noise/coarse Δt too |
|
|
115
116
|
| SPDE / grid fields | linear | the (experimental) SPDE toolbox is linear-only |
|
|
116
117
|
|
|
@@ -133,30 +134,42 @@ agreement is itself a diagnostic.
|
|
|
133
134
|
| `inf.print_report()` / `inf.report_dict()` | Human / machine summary |
|
|
134
135
|
| `inf.compare_to_exact(model_exact=...)` | Validate against a known model |
|
|
135
136
|
| `inf.holdout_score(test)` | Held-out NMSE on `coll.split_time(...)` test data (data-abundant scenarios only) |
|
|
137
|
+
| `inf.compute_entropy_production()` | Dissipation of the observed dynamics (after a linear force fit; both engines): rate ± error + fluctuation bias and the AIC-debiased rate (`Sdot_debiased`), in k_B. Underdamped: positions-only via the even/odd split; `coefficients=` enables cross-fitting |
|
|
138
|
+
| `inf.time_reversal_split()` | Underdamped only: the fitted field's reversible/irreversible parts `(F_even, F_odd)` as callables |
|
|
136
139
|
|
|
137
140
|
* **Parametric estimators (robust, flexible; compute-intensive).**
|
|
138
141
|
Robust to measurement noise
|
|
139
142
|
and finite Δt. Methods `infer_force` and `infer_diffusion`
|
|
140
143
|
(implemented in `SFI.inference.parametric_core`). Takes a `Basis`
|
|
141
144
|
(linear-in-θ; PASTIS sparsification wired) or a `PSF` (general
|
|
142
|
-
parametric family):
|
|
143
|
-
defines the residual, the banded residual covariance
|
|
144
|
-
|
|
145
|
-
|
|
145
|
+
parametric family): an RK4 flow step per observation interval
|
|
146
|
+
defines the residual, the block-banded residual covariance is
|
|
147
|
+
whitened *exactly* (reverse-time banded-innovations NLL — no window
|
|
148
|
+
truncation, no tuning knobs), and `(D, Λ)` are profiled natively
|
|
149
|
+
(moment-estimator init + an exact-NLL refinement on cached fixed-θ
|
|
150
|
+
tensors). Non-uniform sampling (a `t` vector or per-step `dt`
|
|
151
|
+
array) fits directly — force and diffusion, both engines. Two inner
|
|
146
152
|
solvers, selected by `inner="auto"`:
|
|
147
153
|
|
|
148
|
-
* `inner="gn"` — direct Gauss–Newton on the
|
|
154
|
+
* `inner="gn"` — direct Gauss–Newton on the whitened Gram (the
|
|
149
155
|
linear-in-θ fast path) with the skip-trick errors-in-variables
|
|
150
156
|
instrument (`eiv=True`, consistent under measurement noise).
|
|
157
|
+
Nonlinear-in-θ PSFs run the same loop as damped Gauss–Newton,
|
|
158
|
+
instrument active.
|
|
151
159
|
* `inner="lbfgs"` — frozen-precision L-BFGS for non-linear-in-θ
|
|
152
|
-
families (neural-net drift, gated PSFs, …); raise
|
|
153
|
-
for large models
|
|
160
|
+
overdamped families (neural-net drift, gated PSFs, …); raise
|
|
161
|
+
`inner_maxiter` for large models (underdamped redirects to the
|
|
162
|
+
damped-GN path). Worked example:
|
|
154
163
|
[`examples/gallery/advanced/nn_force_demo.py`](examples/gallery/advanced/nn_force_demo.py).
|
|
155
164
|
|
|
165
|
+
Caveat: for (near-)clean data at coarse effective sampling pass
|
|
166
|
+
`eiv=False, n_substeps=2` — `eiv="auto"` does not inspect the noise
|
|
167
|
+
level, and the `(D, Λ)` profile can fit a spurious Λ there.
|
|
168
|
+
|
|
156
169
|
Multi-particle / interacting systems are supported in both overdamped
|
|
157
170
|
and underdamped regimes (the multi-particle path uses the per-edge
|
|
158
171
|
`d_x(same_particle=True)` / `d_v(same_particle=True)` Jacobian
|
|
159
|
-
protocol; O(N) per
|
|
172
|
+
protocol; O(N) per residual).
|
|
160
173
|
|
|
161
174
|
See
|
|
162
175
|
[`docs/source/inference/user_guide.rst`](docs/source/inference/user_guide.rst)
|
|
@@ -171,8 +184,9 @@ inf.infer_force_linear(basis, preset="auto") # 2. force ("auto"/"robust"
|
|
|
171
184
|
inf.infer_diffusion_linear() # 3. optional: constant sym D (default basis)
|
|
172
185
|
inf.compute_force_error() # 4. error analysis
|
|
173
186
|
inf.sparsify_force(criterion="PASTIS", p=0.1) # 5. optional: sparsity
|
|
174
|
-
inf.
|
|
175
|
-
inf.
|
|
187
|
+
inf.compute_entropy_production() # 6. optional: dissipation (OD & UD)
|
|
188
|
+
inf.print_report() # 7. summary
|
|
189
|
+
inf.compare_to_exact(model_exact=process) # 8. optional: validate
|
|
176
190
|
```
|
|
177
191
|
|
|
178
192
|
**Parametric-estimator sequence:**
|
|
@@ -336,7 +350,10 @@ Use `coll.T` (frames) and `coll.d` (dimension) instead of
|
|
|
336
350
|
- `*.bak` files, if you ever see one, are stale snapshots — ignore them.
|
|
337
351
|
|
|
338
352
|
Deprecated / soon-to-be-removed symbols (do not use in new code):
|
|
339
|
-
- (
|
|
353
|
+
- `infer_force(..., extra_radius=)` / `infer_force(..., n_cond=)` —
|
|
354
|
+
deprecated tuning knobs, accepted but **inert** (a
|
|
355
|
+
`DeprecationWarning` fires when set). The banded likelihood has no
|
|
356
|
+
window truncation to tune.
|
|
340
357
|
|
|
341
358
|
## 6. Testing, building, environment
|
|
342
359
|
|
{stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/inference/base.py
RENAMED
|
@@ -526,10 +526,15 @@ class BaseLangevinInference(ABC):
|
|
|
526
526
|
# Entropy production
|
|
527
527
|
if hasattr(self, "DeltaS"):
|
|
528
528
|
print(
|
|
529
|
-
"Entropy production
|
|
529
|
+
"Entropy production (total Delta S, estimated error):",
|
|
530
530
|
self.DeltaS,
|
|
531
531
|
self.error_DeltaS,
|
|
532
532
|
)
|
|
533
|
+
if hasattr(self, "DeltaS_debiased"):
|
|
534
|
+
print(
|
|
535
|
+
"Entropy production, fluctuation-bias subtracted (AIC):",
|
|
536
|
+
self.DeltaS_debiased,
|
|
537
|
+
)
|
|
533
538
|
|
|
534
539
|
# Force inference metrics
|
|
535
540
|
if hasattr(self, "force_predicted_MSE"):
|
|
@@ -649,6 +654,8 @@ class BaseLangevinInference(ABC):
|
|
|
649
654
|
if hasattr(self, "DeltaS"):
|
|
650
655
|
d["DeltaS"] = float(self.DeltaS)
|
|
651
656
|
d["error_DeltaS"] = float(self.error_DeltaS)
|
|
657
|
+
if hasattr(self, "DeltaS_debiased"):
|
|
658
|
+
d["DeltaS_debiased"] = float(self.DeltaS_debiased)
|
|
652
659
|
|
|
653
660
|
if hasattr(self, "force_coefficients"):
|
|
654
661
|
d["force_coefficients"] = np.asarray(self.force_coefficients)
|
{stochasticforceinference-2.0.0.post1 → stochasticforceinference-2.0.1}/SFI/inference/overdamped.py
RENAMED
|
@@ -16,7 +16,7 @@ from SFI.integrate.integrand import (
|
|
|
16
16
|
)
|
|
17
17
|
from SFI.integrate.timeops import stream, timeop, velocity
|
|
18
18
|
from SFI.statefunc import PSF, SF, Basis
|
|
19
|
-
from SFI.utils.maths import sqrtm_psd
|
|
19
|
+
from SFI.utils.maths import sqrtm_psd, stable_pinv
|
|
20
20
|
|
|
21
21
|
from .result import InferenceResultSF # fitted SF with param_cov/meta
|
|
22
22
|
from .sparse import SparseScorer
|
|
@@ -403,6 +403,187 @@ class OverdampedLangevinInference(BaseLangevinInference):
|
|
|
403
403
|
self.diffusion_scorer = SparseScorer(M=self.diffusion_moments, G=self.diffusion_G_full)
|
|
404
404
|
self._update_diffusion_coefficients(self.diffusion_scorer.total_C)
|
|
405
405
|
|
|
406
|
+
def compute_entropy_production(self, *, support: str = "current") -> dict:
|
|
407
|
+
r"""Estimate the entropy production of the observed dynamics.
|
|
408
|
+
|
|
409
|
+
Projects the phase-space velocity (mean local velocity) of the data
|
|
410
|
+
onto the force basis and contracts it with the inverse average
|
|
411
|
+
diffusion — the estimator of Frishman & Ronceray, Phys. Rev. X 10,
|
|
412
|
+
021009 (2020), reconnected to the v2 moment engine. Requires a
|
|
413
|
+
prior :meth:`infer_force_linear` fit (any preset); the Stratonovich
|
|
414
|
+
velocity moments are reused from the fit when available and computed
|
|
415
|
+
on demand otherwise.
|
|
416
|
+
|
|
417
|
+
.. physics:: Entropy production estimator (overdamped, linear basis)
|
|
418
|
+
:label: entropy-production-inference-overdamped
|
|
419
|
+
:category: Observable
|
|
420
|
+
|
|
421
|
+
.. math::
|
|
422
|
+
|
|
423
|
+
\Delta\hat S = 2\, M_v^\top G^{-1} M_v,
|
|
424
|
+
\qquad
|
|
425
|
+
M_{v,a} = \sum_t \mathrm{d}X_t \circ A^{-1}\, b_a,
|
|
426
|
+
\qquad A = 2\bar D,
|
|
427
|
+
|
|
428
|
+
.. math::
|
|
429
|
+
|
|
430
|
+
\hat{\dot S} = \frac{\Delta\hat S}{\tau_N},
|
|
431
|
+
\qquad
|
|
432
|
+
\dot S_{\mathrm{bias}} = \frac{2 N_b}{\tau_N},
|
|
433
|
+
\qquad
|
|
434
|
+
\delta \Delta S = \sqrt{2\,\Delta\hat S + (2 N_b)^2}.
|
|
435
|
+
|
|
436
|
+
:math:`\hat v = G^{-1} M_v` is the basis projection of the
|
|
437
|
+
phase-space velocity :math:`v = F - D\nabla\ln\rho`, :math:`N_b`
|
|
438
|
+
the number of basis degrees of freedom, and :math:`\tau_N` the
|
|
439
|
+
total particle-time. The projection makes :math:`\Delta\hat S` a
|
|
440
|
+
*lower bound* on the entropy produced in the observed phase
|
|
441
|
+
space, up to the :math:`+2N_b` fluctuation bias. The bias is
|
|
442
|
+
reported both ways: the raw plug-in value keeps it, and
|
|
443
|
+
``DeltaS_debiased`` :math:`= \Delta\hat S - 2N_b` subtracts it.
|
|
444
|
+
This is exactly the **AIC complexity correction** applied to the
|
|
445
|
+
trajectory information: :math:`\Delta\hat S` is four times the
|
|
446
|
+
plug-in information carried by the projected velocity field, and
|
|
447
|
+
each fitted degree of freedom inflates a maximized (plug-in)
|
|
448
|
+
information by :math:`+\tfrac12` — the same counting that powers
|
|
449
|
+
the ``AIC`` criterion of ``sparsify_force``.
|
|
450
|
+
|
|
451
|
+
Parameters
|
|
452
|
+
----------
|
|
453
|
+
support : {"current", "full"}
|
|
454
|
+
``"current"`` (default) restricts the projection to the active
|
|
455
|
+
basis support — the full basis before sparsification, the
|
|
456
|
+
selected support after :meth:`sparsify_force` (shrinking the
|
|
457
|
+
:math:`2 N_b/\tau_N` bias). ``"full"`` always uses the complete
|
|
458
|
+
basis.
|
|
459
|
+
|
|
460
|
+
Returns
|
|
461
|
+
-------
|
|
462
|
+
dict
|
|
463
|
+
``{"Sdot", "Sdot_error", "Sdot_bias", "Sdot_debiased", "DeltaS",
|
|
464
|
+
"error_DeltaS", "DeltaS_debiased", "tauN", "Nb"}`` — the
|
|
465
|
+
per-particle entropy production rate, its estimated error and
|
|
466
|
+
fluctuation bias, the bias-subtracted (AIC-corrected) rate, the
|
|
467
|
+
total entropy :math:`\Delta\hat S = \hat{\dot S}\,\tau_N` with
|
|
468
|
+
error and its debiased value, the total particle-time and the
|
|
469
|
+
basis size. All plain floats, in units of :math:`k_B` (nats).
|
|
470
|
+
|
|
471
|
+
Notes
|
|
472
|
+
-----
|
|
473
|
+
- The estimate assumes constant diffusion: the contraction uses the
|
|
474
|
+
average :math:`\bar D` even when a state-dependent field was
|
|
475
|
+
fitted (a warning is emitted in that case). Extension point: a
|
|
476
|
+
trajectory integral of :math:`\hat v \cdot D(x)^{-1} \hat v`.
|
|
477
|
+
- ``Sdot_debiased`` can come out negative near equilibrium — that is
|
|
478
|
+
the expected fluctuation of an unbiased estimate around zero, not
|
|
479
|
+
an inconsistency. Use the raw ``Sdot`` when a conservative
|
|
480
|
+
plug-in value is wanted, the debiased one for point estimates and
|
|
481
|
+
detection decisions.
|
|
482
|
+
- **Independent measurement noise does not bias the estimator at
|
|
483
|
+
leading order.** The Stratonovich velocity moments are *odd*
|
|
484
|
+
under time reversal while i.i.d. (signal-independent) localization
|
|
485
|
+
noise is *even*, so the naive :math:`O(\Lambda/\Delta t)`
|
|
486
|
+
noise–increment correlation cancels exactly — per step it
|
|
487
|
+
telescopes to a boundary term. The surviving effects are second
|
|
488
|
+
order and *not* included in the error bar: an :math:`O(\Lambda)`
|
|
489
|
+
basis-smoothing distortion (the moments see the
|
|
490
|
+
:math:`\Lambda`-blurred basis functions) and a noise-inflated
|
|
491
|
+
variance that raises the effective fluctuation bias. A warning is
|
|
492
|
+
emitted when the estimated noise dominates the per-step diffusive
|
|
493
|
+
displacement (:math:`\mathrm{Tr}\,\Lambda \gtrsim 50\%` of
|
|
494
|
+
:math:`\mathrm{Tr}\,\bar D\,\Delta t`). Noise *correlated with
|
|
495
|
+
the motion* — motion blur, exposure-time averaging — breaks the
|
|
496
|
+
parity cancellation and does bias the estimate: correct or
|
|
497
|
+
deconvolve it first (see :doc:`/inference/noise_and_sampling`).
|
|
498
|
+
- Parametric fits (:meth:`infer_force`) are not supported yet. The
|
|
499
|
+
natural extension is the plug-in Stratonovich sum
|
|
500
|
+
:math:`\sum_t \mathrm{d}X_t \circ \bar D^{-1}\,
|
|
501
|
+
\tfrac12[\hat F(X_t)+\hat F(X_{t+1})]`.
|
|
502
|
+
- With weighted multi-dataset collections the normalization by the
|
|
503
|
+
unweighted :math:`\tau_N` is approximate (same caveat as
|
|
504
|
+
``force_information``).
|
|
505
|
+
"""
|
|
506
|
+
if self.metadata.get("force_method") != "linear":
|
|
507
|
+
raise RuntimeError(
|
|
508
|
+
"compute_entropy_production() requires a prior infer_force_linear() "
|
|
509
|
+
"fit; parametric fits (infer_force) are not supported yet."
|
|
510
|
+
)
|
|
511
|
+
lam = float(getattr(self, "Lambda_trace", 0.0))
|
|
512
|
+
if lam > 0.0:
|
|
513
|
+
# The leading O(Λ/Δt) noise bias cancels by time-reversal parity
|
|
514
|
+
# for signal-independent noise (the Stratonovich moments are odd,
|
|
515
|
+
# i.i.d. noise is even); what survives is second order. Warn only
|
|
516
|
+
# when noise dominates the per-step diffusive displacement.
|
|
517
|
+
dt_val = float(self.data.peek_row(require={"dt"})["dt"])
|
|
518
|
+
diffusive_scale = float(jnp.trace(self.diffusion_average)) * dt_val
|
|
519
|
+
if lam > 0.5 * diffusive_scale:
|
|
520
|
+
logger.warning(
|
|
521
|
+
"Strong measurement noise (Tr Lambda = %.3g, %.0f%% of "
|
|
522
|
+
"Tr(D)*dt). Independent noise does not bias the entropy "
|
|
523
|
+
"estimate at leading order (parity cancellation), but "
|
|
524
|
+
"basis smoothing and noise-inflated variance (second "
|
|
525
|
+
"order) are NOT in the error bar — and noise correlated "
|
|
526
|
+
"with the motion (blur) does not cancel.",
|
|
527
|
+
lam, 100.0 * lam / max(diffusive_scale, 1e-300),
|
|
528
|
+
)
|
|
529
|
+
if self._diffusion_looks_state_dependent():
|
|
530
|
+
logger.warning(
|
|
531
|
+
"A state-dependent diffusion field was inferred; the entropy "
|
|
532
|
+
"estimator contracts with the constant average diffusion and is "
|
|
533
|
+
"only exact for constant D."
|
|
534
|
+
)
|
|
535
|
+
|
|
536
|
+
# Stratonovich v-moments: by-product of a Strato fit, else on demand.
|
|
537
|
+
if not hasattr(self, "force_v_moments"):
|
|
538
|
+
with self._structural_scope(self.force_basis):
|
|
539
|
+
self.force_v_moments = self._strato_v_moments()
|
|
540
|
+
|
|
541
|
+
if support == "current":
|
|
542
|
+
S = self.force_support
|
|
543
|
+
G_pinv = self.force_G_pinv
|
|
544
|
+
elif support == "full":
|
|
545
|
+
S = jnp.arange(self.force_v_moments.shape[0])
|
|
546
|
+
G_pinv = stable_pinv(self.force_G_full)
|
|
547
|
+
else:
|
|
548
|
+
raise ValueError(f"support must be 'current' or 'full', got {support!r}")
|
|
549
|
+
|
|
550
|
+
Mv = self.force_v_moments[S]
|
|
551
|
+
Nb = int(S.shape[0])
|
|
552
|
+
# A carries one factor of A_inv in both M_v and G, so the A-scaling
|
|
553
|
+
# cancels in G^{-1} M_v; the leading 2 restores D̄^{-1} = 2 A^{-1}.
|
|
554
|
+
DeltaS = float(2.0 * (Mv @ (G_pinv @ Mv))) if Nb > 0 else 0.0
|
|
555
|
+
tauN = float(self.data.Teff({"X", "X_plus", "dX", "dt"}))
|
|
556
|
+
|
|
557
|
+
self.DeltaS = DeltaS
|
|
558
|
+
self.error_DeltaS = float((2.0 * DeltaS + (2.0 * Nb) ** 2) ** 0.5)
|
|
559
|
+
self.DeltaS_debiased = DeltaS - 2.0 * Nb
|
|
560
|
+
self.Sdot = DeltaS / tauN
|
|
561
|
+
self.Sdot_bias = 2.0 * Nb / tauN
|
|
562
|
+
self.Sdot_debiased = self.DeltaS_debiased / tauN
|
|
563
|
+
self.Sdot_error = self.error_DeltaS / tauN
|
|
564
|
+
self.metadata["entropy_production"] = {
|
|
565
|
+
"support": support,
|
|
566
|
+
"Nb": Nb,
|
|
567
|
+
"tauN": tauN,
|
|
568
|
+
}
|
|
569
|
+
logger.info(
|
|
570
|
+
"Entropy production: DeltaS = %.4g +- %.4g "
|
|
571
|
+
"(rate %.4g +- %.4g, fluctuation bias %.4g, debiased %.4g)",
|
|
572
|
+
DeltaS, self.error_DeltaS, self.Sdot, self.Sdot_error,
|
|
573
|
+
self.Sdot_bias, self.Sdot_debiased,
|
|
574
|
+
)
|
|
575
|
+
return {
|
|
576
|
+
"Sdot": self.Sdot,
|
|
577
|
+
"Sdot_error": self.Sdot_error,
|
|
578
|
+
"Sdot_bias": self.Sdot_bias,
|
|
579
|
+
"Sdot_debiased": self.Sdot_debiased,
|
|
580
|
+
"DeltaS": self.DeltaS,
|
|
581
|
+
"error_DeltaS": self.error_DeltaS,
|
|
582
|
+
"DeltaS_debiased": self.DeltaS_debiased,
|
|
583
|
+
"tauN": tauN,
|
|
584
|
+
"Nb": Nb,
|
|
585
|
+
}
|
|
586
|
+
|
|
406
587
|
def simulate_bootstrapped_trajectory(self, key, oversampling=1, simulate=True, dataset=0):
|
|
407
588
|
"""
|
|
408
589
|
Simulate an overdamped Langevin trajectory with the inferred force and diffusion fields.
|
|
@@ -461,7 +642,7 @@ class OverdampedLangevinInference(BaseLangevinInference):
|
|
|
461
642
|
return data_bootstrap, bootstrapped_process
|
|
462
643
|
return bootstrapped_process
|
|
463
644
|
|
|
464
|
-
# ── Parametric (
|
|
645
|
+
# ── Parametric (exact-core) force inference ──
|
|
465
646
|
|
|
466
647
|
def infer_force(
|
|
467
648
|
self,
|
|
@@ -478,18 +659,23 @@ class OverdampedLangevinInference(BaseLangevinInference):
|
|
|
478
659
|
inner_maxiter: int = 80,
|
|
479
660
|
extra_radius: int = 1,
|
|
480
661
|
) -> None:
|
|
481
|
-
r"""Infer the force field with the
|
|
482
|
-
|
|
483
|
-
Built on ``SFI.inference.parametric_core``:
|
|
484
|
-
|
|
485
|
-
covariance
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
662
|
+
r"""Infer the force field with the exact parametric estimator.
|
|
663
|
+
|
|
664
|
+
Built on ``SFI.inference.parametric_core``: an RK4 flow step per
|
|
665
|
+
observation interval defines the residual, the block-tridiagonal
|
|
666
|
+
residual covariance is whitened *exactly* by a reverse-time
|
|
667
|
+
block-LDLᵀ innovations recursion (no window truncation, no tuning
|
|
668
|
+
knobs), and the parameters are found by direct Gauss–Newton
|
|
669
|
+
(linear-in-θ ``Basis``, with the skip-trick errors-in-variables
|
|
670
|
+
instrument) or L-BFGS (nonlinear-in-θ ``PSF``). ``(D, Λ)`` are
|
|
671
|
+
profiled natively: moment-estimator init, then an exact banded-NLL
|
|
672
|
+
refinement at the fitted θ on cached fixed-θ tensors (zero basis
|
|
673
|
+
evaluations per profile iteration). Non-uniform sampling is
|
|
674
|
+
supported end-to-end: datasets carrying an absolute time vector
|
|
675
|
+
``t`` or a per-step ``dt`` array fit directly, with per-interval
|
|
676
|
+
flow steps and process covariances.
|
|
677
|
+
|
|
678
|
+
.. physics:: Parametric force inference (overdamped)
|
|
493
679
|
:label: parametric-force-overdamped
|
|
494
680
|
:category: Inference
|
|
495
681
|
|
|
@@ -500,10 +686,11 @@ class OverdampedLangevinInference(BaseLangevinInference):
|
|
|
500
686
|
:math:`\Phi(x;\theta) = z(\Delta t) - x`
|
|
501
687
|
(one RK4 step by default) defines the residual
|
|
502
688
|
:math:`r_t = y_{t+1} - y_t - \Phi(y_t;\theta)`.
|
|
503
|
-
Residuals follow a
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
689
|
+
Residuals follow a block-tridiagonal Gaussian that is
|
|
690
|
+
whitened exactly (reverse-time innovations), and the
|
|
691
|
+
whitened sensitivities enter the Gauss–Newton normal
|
|
692
|
+
equations; under measurement noise the left factor is
|
|
693
|
+
replaced by the η-clean *skip* instrument
|
|
507
694
|
:math:`\psi_{\rm inst} = \partial\Phi/\partial\theta`
|
|
508
695
|
evaluated at the lagged clean point (``eiv=True``),
|
|
509
696
|
giving a consistent estimating equation.
|
|
@@ -526,27 +713,36 @@ class OverdampedLangevinInference(BaseLangevinInference):
|
|
|
526
713
|
Flow predictor (default ``"rk4"``, a single 4th-order step).
|
|
527
714
|
n_substeps : int
|
|
528
715
|
Integrator micro-steps per observation interval (default 1 —
|
|
529
|
-
the single-step minimal estimator).
|
|
716
|
+
the single-step minimal estimator). At coarse effective
|
|
717
|
+
sampling (fast dynamics per interval) raise to 2 — the RK4
|
|
718
|
+
flow model saturates at ``n_substeps=2`` on typical chaotic
|
|
719
|
+
benchmarks. With per-step ``dt`` the substep is
|
|
720
|
+
``h_k = dt_k / n_substeps``.
|
|
530
721
|
inner : {"auto", "gn", "lbfgs"}
|
|
531
|
-
Inner solver. ``"auto"`` →
|
|
532
|
-
``Basis``,
|
|
722
|
+
Inner solver. ``"auto"`` → Gauss–Newton (direct for a
|
|
723
|
+
linear ``Basis``, damped for a nonlinear ``PSF`` — the
|
|
724
|
+
instrument stays active); ``"lbfgs"`` forces the
|
|
725
|
+
frozen-precision quadratic route.
|
|
533
726
|
eiv : {"auto", True, False, float}
|
|
534
727
|
Measurement-noise errors-in-variables instrument. ``"auto"``
|
|
535
728
|
(default) → ``True`` for all models (interacting models use
|
|
536
729
|
the same N-body flow for the instrument as for the residual);
|
|
537
730
|
``True`` forces the η-clean skip instrument (consistent under
|
|
538
731
|
noise); ``False`` is the plain MLE; a float in ``[0, 1]``
|
|
539
|
-
blends.
|
|
732
|
+
blends. ``"auto"`` does *not* inspect the noise level: for
|
|
733
|
+
(near-)clean data at coarse effective sampling pass
|
|
734
|
+
``eiv=False`` — the ``(D, Λ)`` profile cannot separate a
|
|
735
|
+
Δt-independent Λ from the process increment structure there
|
|
736
|
+
and can fit a spurious measurement noise (see
|
|
737
|
+
:ref:`parametric-algorithm`).
|
|
540
738
|
max_outer : int
|
|
541
739
|
Outer Gauss–Newton / IRLS iterations (default 5).
|
|
542
740
|
inner_maxiter : int
|
|
543
741
|
Inner L-BFGS iterations per outer step on the PSF path
|
|
544
742
|
(default 80; raise for large nonlinear families, e.g. NNs).
|
|
545
743
|
extra_radius : int
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
β = Tr(Λ)/Tr(2DΔt) ≫ 1, where the windowed precision
|
|
549
|
-
decays slowly and the default window under-resolves it.
|
|
744
|
+
Deprecated, no effect (a ``DeprecationWarning`` fires when
|
|
745
|
+
a non-default value is passed).
|
|
550
746
|
|
|
551
747
|
Updates
|
|
552
748
|
-------
|
|
@@ -587,7 +783,14 @@ class OverdampedLangevinInference(BaseLangevinInference):
|
|
|
587
783
|
F_psf = _as_psf(F)
|
|
588
784
|
|
|
589
785
|
with self._structural_scope(F_psf):
|
|
590
|
-
|
|
786
|
+
import numpy as _np
|
|
787
|
+
|
|
788
|
+
from SFI.inference.parametric_core.solve import _dt_steps
|
|
789
|
+
|
|
790
|
+
# representative Δt for logging and the reported β — the median
|
|
791
|
+
# step (identical to the single step at uniform sampling)
|
|
792
|
+
dt_val = float(_np.median(_np.concatenate(
|
|
793
|
+
[_dt_steps(ds) for ds in self.data.datasets])))
|
|
591
794
|
|
|
592
795
|
logger.info(
|
|
593
796
|
"[infer_force] OD minimal parametric solve (n_params=%d, dt=%.4g, %s·n%d, basis_mode=%s)...",
|
|
@@ -600,6 +803,7 @@ class OverdampedLangevinInference(BaseLangevinInference):
|
|
|
600
803
|
integrator=integrator, n_substeps=n_substeps,
|
|
601
804
|
inner=inner, eiv=eiv, max_outer=max_outer,
|
|
602
805
|
inner_maxiter=inner_maxiter, extra_radius=extra_radius,
|
|
806
|
+
chunk_target_bytes=self._chunk_target_bytes,
|
|
603
807
|
)
|
|
604
808
|
t_elapsed = _time.perf_counter() - t0
|
|
605
809
|
|
|
@@ -689,10 +893,13 @@ class OverdampedLangevinInference(BaseLangevinInference):
|
|
|
689
893
|
r"""Infer state-dependent diffusion D(x) from parametric residuals.
|
|
690
894
|
|
|
691
895
|
Requires a prior parametric :meth:`infer_force` call. Holds the
|
|
692
|
-
fitted force fixed and minimises the
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
896
|
+
fitted force fixed and minimises the exact banded NLL over the
|
|
897
|
+
diffusion parameters (the log-det term makes the diffusion level
|
|
898
|
+
identifiable) on cached fixed-θ tensors: the flow residuals and
|
|
899
|
+
stage Jacobians are computed once at :math:`\hat\theta`, so each
|
|
900
|
+
L-BFGS iteration costs linear algebra only — zero force or basis
|
|
901
|
+
evaluations. Non-uniform sampling (``t`` vector or per-step
|
|
902
|
+
``dt``) is supported.
|
|
696
903
|
|
|
697
904
|
.. physics:: State-dependent diffusion inference (overdamped)
|
|
698
905
|
:label: parametric-diffusion-overdamped
|
|
@@ -700,7 +907,7 @@ class OverdampedLangevinInference(BaseLangevinInference):
|
|
|
700
907
|
|
|
701
908
|
With the force :math:`\hat F` held fixed, the state-dependent
|
|
702
909
|
diffusion :math:`D(x;\theta_D)` is optimised by minimising the
|
|
703
|
-
|
|
910
|
+
exact banded negative log-likelihood; :math:`\Lambda`
|
|
704
911
|
from the force inference is held fixed. A rank-2 basis gives
|
|
705
912
|
:math:`D(x) = \sum_j (\theta_D)_j\, d_j(x)`; a PSF is evaluated
|
|
706
913
|
directly.
|
|
@@ -756,6 +963,7 @@ class OverdampedLangevinInference(BaseLangevinInference):
|
|
|
756
963
|
self.data, self.force_psf, self.force_coefficients_full, D_psf,
|
|
757
964
|
Lambda=Lambda, theta_D0=theta_D0,
|
|
758
965
|
n_substeps=n_substeps, integrator=integrator, maxiter=maxiter,
|
|
966
|
+
chunk_target_bytes=self._chunk_target_bytes,
|
|
759
967
|
)
|
|
760
968
|
theta_D = res.theta_D
|
|
761
969
|
self.diffusion_coefficients = theta_D
|
|
@@ -786,6 +994,61 @@ class OverdampedLangevinInference(BaseLangevinInference):
|
|
|
786
994
|
b_right = self.force_basis @ self.A_inv
|
|
787
995
|
return self.__G_matrix__(b_left, b_right, self.__force_G_mode__, "ima,imb->iab")
|
|
788
996
|
|
|
997
|
+
def _strato_v_moments(self) -> jnp.ndarray:
|
|
998
|
+
r"""Stratonovich (trapezoid) phase-space velocity moments.
|
|
999
|
+
|
|
1000
|
+
Extensive, dt-weighted, mask-aware sums
|
|
1001
|
+
|
|
1002
|
+
.. math::
|
|
1003
|
+
|
|
1004
|
+
M_{v,a} = \sum_t \mathrm{d}X_t \cdot A^{-1} \cdot
|
|
1005
|
+
\tfrac12\bigl[b_a(X_t) + b_a(X_{t+1})\bigr]
|
|
1006
|
+
|
|
1007
|
+
— the by-product of Stratonovich force fitting and the raw material
|
|
1008
|
+
of :meth:`compute_entropy_production`.
|
|
1009
|
+
"""
|
|
1010
|
+
A = ConstOperand(self.A_inv, alias="A")
|
|
1011
|
+
V = TimeOperand(velocity("dX", "dt"), alias="V")
|
|
1012
|
+
B0 = ExprOperand(expr=self.force_basis, x=stream("X"), alias="B0")
|
|
1013
|
+
Bp = ExprOperand(expr=self.force_basis, x=stream("X_plus"), alias="Bp")
|
|
1014
|
+
prog_B0 = Integrand(
|
|
1015
|
+
exprs=[B0],
|
|
1016
|
+
times=[V],
|
|
1017
|
+
consts=[A],
|
|
1018
|
+
terms=[Term(eq="im,mn,ina->ia", ops=("V", "A", "B0"))],
|
|
1019
|
+
)
|
|
1020
|
+
prog_Bp = Integrand(
|
|
1021
|
+
exprs=[Bp],
|
|
1022
|
+
times=[V],
|
|
1023
|
+
consts=[A],
|
|
1024
|
+
terms=[Term(eq="im,mn,ina->ia", ops=("V", "A", "Bp"))],
|
|
1025
|
+
)
|
|
1026
|
+
prog_strato_vb = 0.5 * (prog_B0 + prog_Bp)
|
|
1027
|
+
return integrate(
|
|
1028
|
+
self.data, prog_strato_vb, reduce="sum", chunk_target_bytes=self._chunk_target_bytes
|
|
1029
|
+
)
|
|
1030
|
+
|
|
1031
|
+
def _diffusion_looks_state_dependent(self) -> bool:
|
|
1032
|
+
"""Best-effort probe: does ``diffusion_inferred`` vary with x?
|
|
1033
|
+
|
|
1034
|
+
Used by :meth:`compute_entropy_production` to warn when a
|
|
1035
|
+
state-dependent diffusion field was fitted (the estimator contracts
|
|
1036
|
+
with the constant average). Conservative: unprobeable fields count
|
|
1037
|
+
as state-dependent.
|
|
1038
|
+
"""
|
|
1039
|
+
D_sf = getattr(self, "diffusion_inferred", None)
|
|
1040
|
+
if D_sf is None:
|
|
1041
|
+
return False
|
|
1042
|
+
try:
|
|
1043
|
+
x0 = jnp.asarray(self.data.datasets[0].X[0][:1]) # (1, d)
|
|
1044
|
+
x1 = x0 + 1.0 + 0.5 * jnp.abs(x0)
|
|
1045
|
+
D_a = jnp.asarray(D_sf(x0))
|
|
1046
|
+
D_b = jnp.asarray(D_sf(x1))
|
|
1047
|
+
scale = 1.0 + float(jnp.max(jnp.abs(D_a)))
|
|
1048
|
+
return bool(float(jnp.max(jnp.abs(D_a - D_b))) > 1e-6 * scale)
|
|
1049
|
+
except Exception: # pragma: no cover — conservative fallback
|
|
1050
|
+
return True
|
|
1051
|
+
|
|
789
1052
|
def _force_moments(self):
|
|
790
1053
|
r"""
|
|
791
1054
|
Compute force moments ⟨ v · A_inv · b ⟩ with Ito / Ito-shift / Strato flavors.
|
|
@@ -869,24 +1132,7 @@ class OverdampedLangevinInference(BaseLangevinInference):
|
|
|
869
1132
|
logger.debug("Computing Strato force coefficients.")
|
|
870
1133
|
|
|
871
1134
|
# v ∘ b via trapezoid on basis
|
|
872
|
-
|
|
873
|
-
Bp = ExprOperand(expr=self.force_basis, x=stream("X_plus"), alias="Bp")
|
|
874
|
-
prog_B0 = Integrand(
|
|
875
|
-
exprs=[B0],
|
|
876
|
-
times=[V],
|
|
877
|
-
consts=[A],
|
|
878
|
-
terms=[Term(eq="im,mn,ina->ia", ops=("V", "A", "B0"))],
|
|
879
|
-
)
|
|
880
|
-
prog_Bp = Integrand(
|
|
881
|
-
exprs=[Bp],
|
|
882
|
-
times=[V],
|
|
883
|
-
consts=[A],
|
|
884
|
-
terms=[Term(eq="im,mn,ina->ia", ops=("V", "A", "Bp"))],
|
|
885
|
-
)
|
|
886
|
-
prog_strato_vb = 0.5 * (prog_B0 + prog_Bp)
|
|
887
|
-
self.force_v_moments = integrate(
|
|
888
|
-
self.data, prog_strato_vb, reduce="sum", chunk_target_bytes=self._chunk_target_bytes
|
|
889
|
-
)
|
|
1135
|
+
self.force_v_moments = self._strato_v_moments()
|
|
890
1136
|
|
|
891
1137
|
logger.debug("Computing Strato gradient term.")
|
|
892
1138
|
|
|
@@ -1133,13 +1379,22 @@ def _D_noisy(**streams):
|
|
|
1133
1379
|
|
|
1134
1380
|
Two-point estimator robust to measurement noise
|
|
1135
1381
|
(CL Vestergaard, PC Blainey, H Flyvbjerg - Physical Review E, 2014).
|
|
1382
|
+
|
|
1383
|
+
Non-uniform sampling: the stencil expectation is
|
|
1384
|
+
:math:`2D(\mathrm{d}t + \mathrm{d}t^-)` with the localization terms
|
|
1385
|
+
cancelling at any spacing, so the denominator is the two-interval
|
|
1386
|
+
mean :math:`(\mathrm{d}t + \mathrm{d}t^-)/2` — identical to
|
|
1387
|
+
:math:`\mathrm{d}t` (bitwise) at uniform sampling, exactly unbiased
|
|
1388
|
+
under per-step ``dt``.
|
|
1136
1389
|
"""
|
|
1137
1390
|
dX = streams["dX"]
|
|
1138
1391
|
dXm = streams["dX_minus"]
|
|
1139
1392
|
dt = streams["dt"]
|
|
1393
|
+
dtm = streams["dt_minus"]
|
|
1140
1394
|
while dt.ndim < dX.ndim:
|
|
1141
1395
|
dt = dt[..., jnp.newaxis]
|
|
1142
|
-
|
|
1396
|
+
dtm = dtm[..., jnp.newaxis]
|
|
1397
|
+
invdt = 1.0 / (0.5 * (dt + dtm))
|
|
1143
1398
|
a = jnp.einsum("...m,...n->...mn", dX, dX * invdt)
|
|
1144
1399
|
b = jnp.einsum("...m,...n->...mn", dX, dXm * invdt)
|
|
1145
1400
|
c = jnp.einsum("...m,...n->...mn", dXm, dX * invdt)
|
|
@@ -1147,7 +1402,7 @@ def _D_noisy(**streams):
|
|
|
1147
1402
|
return 0.25 * (a + 2 * b + 2 * c + d)
|
|
1148
1403
|
|
|
1149
1404
|
|
|
1150
|
-
_D_noisy._requires = frozenset({"dX", "dX_minus", "dt"}) # type: ignore[attr-defined]
|
|
1405
|
+
_D_noisy._requires = frozenset({"dX", "dX_minus", "dt", "dt_minus"}) # type: ignore[attr-defined]
|
|
1151
1406
|
|
|
1152
1407
|
|
|
1153
1408
|
@timeop(name="D_weaknoise", batch_safe=True)
|
|
@@ -1169,19 +1424,27 @@ def _D_weaknoise(**streams):
|
|
|
1169
1424
|
|
|
1170
1425
|
Uses successive-displacement differences; suitable when localization
|
|
1171
1426
|
noise is negligible.
|
|
1427
|
+
|
|
1428
|
+
Non-uniform sampling: ``E[(dX − dX⁻)⊗(dX − dX⁻)] = 2D(dt + dt⁻)``
|
|
1429
|
+
for clean data at any spacing, so the denominator is the
|
|
1430
|
+
two-interval mean ``(dt + dt⁻)/2`` — bitwise-identical to ``dt``
|
|
1431
|
+
at uniform sampling, exactly unbiased per-step (same
|
|
1432
|
+
generalization as the noisy estimator above).
|
|
1172
1433
|
"""
|
|
1173
1434
|
dX = streams["dX"]
|
|
1174
1435
|
dXm = streams["dX_minus"]
|
|
1175
1436
|
dt = streams["dt"]
|
|
1437
|
+
dtm = streams["dt_minus"]
|
|
1176
1438
|
while dt.ndim < dX.ndim:
|
|
1177
1439
|
dt = dt[..., jnp.newaxis]
|
|
1178
|
-
|
|
1440
|
+
dtm = dtm[..., jnp.newaxis]
|
|
1441
|
+
invdt = 1.0 / (0.5 * (dt + dtm))
|
|
1179
1442
|
ddx = dX - dXm
|
|
1180
1443
|
dv = dX * invdt - dXm * invdt
|
|
1181
1444
|
return 0.25 * jnp.einsum("...m,...n->...mn", ddx, dv)
|
|
1182
1445
|
|
|
1183
1446
|
|
|
1184
|
-
_D_weaknoise._requires = frozenset({"dX", "dX_minus", "dt"}) # type: ignore[attr-defined]
|
|
1447
|
+
_D_weaknoise._requires = frozenset({"dX", "dX_minus", "dt", "dt_minus"}) # type: ignore[attr-defined]
|
|
1185
1448
|
|
|
1186
1449
|
|
|
1187
1450
|
@timeop(name="Lambda_meas_noise", batch_safe=True)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# SFI/inference/parametric_core/ — the exact parametric estimator.
|
|
2
|
+
"""
|
|
3
|
+
The parametric force/diffusion estimator: RK4-flow residuals, block-banded
|
|
4
|
+
residual covariance whitened *exactly* by a reverse-time block-LDLᵀ
|
|
5
|
+
innovations recursion (:mod:`banded` / :mod:`runner`), errors-in-variables
|
|
6
|
+
instrument, and native ``(D, Λ)`` profiling on cached fixed-θ tensors.
|
|
7
|
+
Per-interval (non-uniform) dt is supported end-to-end.
|
|
8
|
+
|
|
9
|
+
A single, paper-ready objective with a handful of parameters, integrating
|
|
10
|
+
like ``infer_*_linear`` (Basis/PSF in → ``InferenceResultSF`` out,
|
|
11
|
+
multi-particle aware).
|
|
12
|
+
|
|
13
|
+
Docs: ``docs/source/inference/parametric_concept.rst`` (foundations) and
|
|
14
|
+
``docs/source/inference/parametric_algorithm.rst`` (implementation).
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from .solve import (
|
|
20
|
+
DiffusionSolveResult,
|
|
21
|
+
ForceSolveResult,
|
|
22
|
+
solve_diffusion_od,
|
|
23
|
+
solve_diffusion_ud,
|
|
24
|
+
solve_force_od,
|
|
25
|
+
solve_force_ud,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
"solve_force_od", "solve_force_ud",
|
|
30
|
+
"solve_diffusion_od", "solve_diffusion_ud",
|
|
31
|
+
"ForceSolveResult", "DiffusionSolveResult",
|
|
32
|
+
]
|