StochasticForceInference 2.0.0__py3-none-any.whl

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 (104) hide show
  1. SFI/__init__.py +64 -0
  2. SFI/bases/__init__.py +85 -0
  3. SFI/bases/constants.py +492 -0
  4. SFI/bases/linear.py +325 -0
  5. SFI/bases/monomials.py +218 -0
  6. SFI/bases/pairs.py +998 -0
  7. SFI/bases/spde.py +1537 -0
  8. SFI/diagnostics/__init__.py +60 -0
  9. SFI/diagnostics/assess.py +87 -0
  10. SFI/diagnostics/dynamics_order.py +621 -0
  11. SFI/diagnostics/plotting.py +226 -0
  12. SFI/diagnostics/report.py +238 -0
  13. SFI/diagnostics/residual_tests.py +395 -0
  14. SFI/diagnostics/residuals.py +688 -0
  15. SFI/inference/__init__.py +58 -0
  16. SFI/inference/base.py +1460 -0
  17. SFI/inference/optimizers.py +200 -0
  18. SFI/inference/overdamped.py +1214 -0
  19. SFI/inference/parametric_core/__init__.py +34 -0
  20. SFI/inference/parametric_core/covariance.py +232 -0
  21. SFI/inference/parametric_core/driver.py +272 -0
  22. SFI/inference/parametric_core/flow.py +149 -0
  23. SFI/inference/parametric_core/flow_multi.py +362 -0
  24. SFI/inference/parametric_core/flow_ud.py +168 -0
  25. SFI/inference/parametric_core/jacobians.py +540 -0
  26. SFI/inference/parametric_core/objective.py +286 -0
  27. SFI/inference/parametric_core/objective_ud.py +253 -0
  28. SFI/inference/parametric_core/precision.py +229 -0
  29. SFI/inference/parametric_core/solve.py +763 -0
  30. SFI/inference/result.py +362 -0
  31. SFI/inference/serialization.py +245 -0
  32. SFI/inference/sparse/__init__.py +67 -0
  33. SFI/inference/sparse/base.py +43 -0
  34. SFI/inference/sparse/beam.py +303 -0
  35. SFI/inference/sparse/greedy.py +151 -0
  36. SFI/inference/sparse/hillclimb.py +307 -0
  37. SFI/inference/sparse/lasso.py +178 -0
  38. SFI/inference/sparse/metrics.py +78 -0
  39. SFI/inference/sparse/result.py +278 -0
  40. SFI/inference/sparse/scorer.py +323 -0
  41. SFI/inference/sparse/stlsq.py +165 -0
  42. SFI/inference/sparsity.py +40 -0
  43. SFI/inference/underdamped.py +1355 -0
  44. SFI/integrate/__init__.py +38 -0
  45. SFI/integrate/api.py +920 -0
  46. SFI/integrate/integrand.py +402 -0
  47. SFI/integrate/rk4.py +156 -0
  48. SFI/integrate/timeops.py +174 -0
  49. SFI/langevin/__init__.py +29 -0
  50. SFI/langevin/base.py +863 -0
  51. SFI/langevin/chunked.py +225 -0
  52. SFI/langevin/noise.py +446 -0
  53. SFI/langevin/overdamped.py +560 -0
  54. SFI/langevin/underdamped.py +448 -0
  55. SFI/statefunc/__init__.py +49 -0
  56. SFI/statefunc/basis.py +87 -0
  57. SFI/statefunc/core/runtime.py +47 -0
  58. SFI/statefunc/factory.py +346 -0
  59. SFI/statefunc/interactor.py +90 -0
  60. SFI/statefunc/layout/__init__.py +29 -0
  61. SFI/statefunc/layout/_base.py +186 -0
  62. SFI/statefunc/layout/_eval_compiler.py +453 -0
  63. SFI/statefunc/layout/_fd_atoms.py +196 -0
  64. SFI/statefunc/layout/_grid.py +878 -0
  65. SFI/statefunc/layout/_sectors.py +166 -0
  66. SFI/statefunc/memhint.py +222 -0
  67. SFI/statefunc/nodes/__init__.py +56 -0
  68. SFI/statefunc/nodes/base.py +318 -0
  69. SFI/statefunc/nodes/contract.py +422 -0
  70. SFI/statefunc/nodes/interactions/__init__.py +23 -0
  71. SFI/statefunc/nodes/interactions/dispatcher.py +1365 -0
  72. SFI/statefunc/nodes/interactions/prepare.py +161 -0
  73. SFI/statefunc/nodes/interactions/specs.py +362 -0
  74. SFI/statefunc/nodes/interactions/stencils.py +718 -0
  75. SFI/statefunc/nodes/leaf.py +530 -0
  76. SFI/statefunc/nodes/ops/__init__.py +27 -0
  77. SFI/statefunc/nodes/ops/concat.py +28 -0
  78. SFI/statefunc/nodes/ops/derivative.py +447 -0
  79. SFI/statefunc/nodes/ops/einsum.py +120 -0
  80. SFI/statefunc/nodes/ops/linear.py +153 -0
  81. SFI/statefunc/nodes/ops/mapn.py +138 -0
  82. SFI/statefunc/nodes/ops/reshape_rank.py +158 -0
  83. SFI/statefunc/nodes/ops/slice.py +83 -0
  84. SFI/statefunc/params.py +309 -0
  85. SFI/statefunc/psf.py +112 -0
  86. SFI/statefunc/sf.py +104 -0
  87. SFI/statefunc/stateexpr.py +1243 -0
  88. SFI/statefunc/structexpr.py +1003 -0
  89. SFI/trajectory/__init__.py +31 -0
  90. SFI/trajectory/collection.py +1122 -0
  91. SFI/trajectory/dataset.py +1164 -0
  92. SFI/trajectory/degrade.py +1108 -0
  93. SFI/trajectory/io.py +1027 -0
  94. SFI/trajectory/reserved_extras.py +129 -0
  95. SFI/utils/__init__.py +17 -0
  96. SFI/utils/formatting.py +308 -0
  97. SFI/utils/maths.py +185 -0
  98. SFI/utils/neighbors.py +162 -0
  99. SFI/utils/plotting.py +1936 -0
  100. stochasticforceinference-2.0.0.dist-info/METADATA +203 -0
  101. stochasticforceinference-2.0.0.dist-info/RECORD +104 -0
  102. stochasticforceinference-2.0.0.dist-info/WHEEL +5 -0
  103. stochasticforceinference-2.0.0.dist-info/licenses/LICENSE +21 -0
  104. stochasticforceinference-2.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,229 @@
1
+ # SFI/inference/parametric_core/precision.py
2
+ """
3
+ Windowed local-precision kernels for the parametric core.
4
+
5
+ The estimating equations need the precision ``P = Σ⁻¹`` of the banded
6
+ residual covariance. ``P`` itself is *not* banded (the residuals are a
7
+ moving-average process, whose inverse covariance is AR(∞)), but its
8
+ entries decay geometrically away from the diagonal — at rate
9
+ ``λ = |ρ|/(1+√(1−4ρ²))`` for bandwidth 1 with lag-1 correlation ``ρ``
10
+ (``|ρ| ≤ ½``, saturating only as the noise-to-signal ratio diverges).
11
+ So the *center row* of ``P`` is read off a small local window of ``W``
12
+ residuals with ``O(λ^{W−1})`` truncation error: assemble the
13
+ ``(W·d)×(W·d)`` covariance, Cholesky-factor it once, and solve against
14
+ the right-hand sides. At extreme measurement noise the window can be
15
+ widened (``extra_radius`` / ``n_cond``).
16
+
17
+ These are **single-window** kernels: the sliding window itself (the
18
+ ``X_window:{W}`` stream), chunking, and the sum over centers are provided
19
+ by the ``SFI.integrate`` engine. Two kernels are exposed:
20
+
21
+ * :func:`center_gram_contribution` — Gauss–Newton Gram ``G``, RHS ``f``,
22
+ and pseudo-NLL from the center row (used for the parameter covariance,
23
+ the optional exact GN step, and the sparsity hand-off).
24
+ * :func:`center_loss_contribution` — the per-center quadratic
25
+ ``½ r_cᵀ P r`` with ``P`` *frozen*, whose AD-gradient w.r.t. live
26
+ residuals is exactly the GN score. This is the differentiable objective.
27
+ """
28
+
29
+ from __future__ import annotations
30
+
31
+ import jax
32
+ import jax.numpy as jnp
33
+
34
+ from .covariance import CovarianceBlocks, assemble_dense
35
+
36
+ __all__ = [
37
+ "center_gram_contribution",
38
+ "center_loss_contribution",
39
+ "center_nll_contribution",
40
+ "center_conditional_nll_contribution",
41
+ ]
42
+
43
+
44
+ def _cholesky(A_w, offdiag_w, jitter_chol):
45
+ Sigma = assemble_dense(CovarianceBlocks(A_w, tuple(offdiag_w), len(offdiag_w)))
46
+ Wd = Sigma.shape[0]
47
+ Sigma = Sigma + jitter_chol * jnp.eye(Wd, dtype=Sigma.dtype)
48
+ return jnp.linalg.cholesky(Sigma)
49
+
50
+
51
+ def center_gram_contribution(A_w, offdiag_w, r_w, psi_w, c, jitter_chol=1e-10, psi_right_w=None):
52
+ r"""Gram / RHS / NLL contribution of the center residual ``c``.
53
+
54
+ The estimating equation is ``⟨ψ_left P r⟩ = 0`` with ``P = Σ⁻¹`` the local
55
+ precision. ``ψ_left`` is the test function (an instrument — a free choice)
56
+ and ``ψ_right = ∂r/∂θ`` is the regressor. From the center row-block,
57
+
58
+ G_w = ψ_left,cᵀ (P ψ_right)_c, f_w = ψ_left,cᵀ (P r)_c,
59
+ nll_w = ½ r_cᵀ (P r)_c − ½ log det P_{cc}.
60
+
61
+ When ``psi_right_w is None`` the left and right factors coincide
62
+ (``ψ_left = ψ_right = ψ_w``) — the symmetric plug-in MLE, numerically
63
+ identical to the previous single-factor form. A separate ``psi_right_w``
64
+ yields the asymmetric (instrumental-variable) Gram used by the EIV path.
65
+ ``nll_w`` is a function of ``r`` only and is unchanged by the split.
66
+
67
+ The fourth output ``H_w = ψ_left,cᵀ (P ψ_left)_c`` is the model-based
68
+ variance of the estimating function (``Var(ψ_leftᵀPr) = ψ_leftᵀPψ_left``
69
+ since ``Cov(r) = P⁻¹``): the meat of the sandwich covariance
70
+ ``Cov(θ̂) = G⁻¹ H G⁻ᵀ`` for the asymmetric (IV) path. In the symmetric
71
+ case ``H_w = G_w`` (the information identity) and the sandwich collapses
72
+ to ``G⁻¹``. It comes nearly free: ``ψ_left`` differs from ``ψ_right``
73
+ only in row ``c``, so ``(Pψ_left)_c = (Pψ_right)_c + P_{cc}(ψ_left,c −
74
+ ψ_right,c)`` reuses the column block already computed for the log-det.
75
+
76
+ Parameters
77
+ ----------
78
+ A_w : ``(W, d, d)`` diagonal blocks.
79
+ offdiag_w : tuple of ``(W-lag, d, d)`` off-diagonal blocks.
80
+ r_w : ``(W, d)`` residuals.
81
+ psi_w : ``(W, d, n_params)`` left test functions (instrument).
82
+ c : int center index within the window.
83
+ jitter_chol : float
84
+ psi_right_w : ``(W, d, n_params)`` or None
85
+ Right factor (regressor ``∂r/∂θ``). ``None`` → ``psi_w`` (symmetric).
86
+
87
+ Returns
88
+ -------
89
+ G_w : ``(n_params, n_params)``
90
+ f_w : ``(n_params,)``
91
+ H_w : ``(n_params, n_params)``
92
+ nll_w : scalar
93
+ """
94
+ W, d = A_w.shape[0], A_w.shape[-1]
95
+ n_params = psi_w.shape[-1]
96
+ Wd = W * d
97
+ L = _cholesky(A_w, offdiag_w, jitter_chol)
98
+
99
+ r_flat = r_w.reshape(Wd)
100
+ Sinv_r = jax.scipy.linalg.cho_solve((L, True), r_flat)
101
+ Pr_c = Sinv_r[c * d:(c + 1) * d]
102
+
103
+ # P_{cc} for the log-det normaliser
104
+ E_c = jnp.zeros((Wd, d), dtype=A_w.dtype).at[c * d:(c + 1) * d, :].set(jnp.eye(d, dtype=A_w.dtype))
105
+ Sinv_Ec = jax.scipy.linalg.cho_solve((L, True), E_c)
106
+ P_cc = Sinv_Ec[c * d:(c + 1) * d, :]
107
+ _, logdet = jnp.linalg.slogdet(P_cc)
108
+ nll_w = 0.5 * jnp.dot(r_w[c], Pr_c) - 0.5 * logdet
109
+
110
+ sym = psi_right_w is None
111
+ psi_right_w = psi_w if sym else psi_right_w
112
+ psi_left_c = psi_w[c]
113
+ psi_right_flat = psi_right_w.reshape(Wd, n_params)
114
+ Ppsi_right_c = jax.scipy.linalg.cho_solve((L, True), psi_right_flat)[c * d:(c + 1) * d, :]
115
+ G_w = psi_left_c.T @ Ppsi_right_c
116
+ f_w = psi_left_c.T @ Pr_c
117
+ if sym:
118
+ H_w = G_w
119
+ else:
120
+ Ppsi_left_c = Ppsi_right_c + P_cc @ (psi_left_c - psi_right_w[c])
121
+ H_w = psi_left_c.T @ Ppsi_left_c
122
+ return G_w, f_w, H_w, nll_w
123
+
124
+
125
+ def center_conditional_nll_contribution(A_w, offdiag_w, r_w, c, n_cond, jitter_chol=1e-10):
126
+ r"""NLL of residual ``c`` conditioned on its ``n_cond`` predecessors.
127
+
128
+ Builds the joint covariance of ``[r_{c-n_cond}, …, r_c]`` from the
129
+ window blocks, forms the Schur complement of the past block, and
130
+ returns the conditional Gaussian NLL of the last block:
131
+
132
+ S = A_cc − Σ_{c,p} Σ_{p,p}⁻¹ Σ_{p,c}, e = r_c − Σ_{c,p} Σ_{p,p}⁻¹ r_p,
133
+ nll = ½ eᵀ S⁻¹ e + ½ log det S.
134
+
135
+ Summed over centers with the full past this is the exact chain-rule
136
+ NLL (telescoping log-det); with a fixed ``n_cond`` (a few past points)
137
+ it is an excellent, **non-degenerate** approximation — the correct
138
+ objective when ``D, Λ`` are free. ``c`` and ``n_cond`` are static.
139
+ """
140
+ d = A_w.shape[-1]
141
+ start = c - n_cond
142
+ r_c = r_w[c]
143
+
144
+ if n_cond == 0:
145
+ L = jnp.linalg.cholesky(A_w[c] + jitter_chol * jnp.eye(d, dtype=A_w.dtype))
146
+ Sinv_e = jax.scipy.linalg.cho_solve((L, True), r_c)
147
+ return 0.5 * jnp.dot(r_c, Sinv_e) + jnp.sum(jnp.log(jnp.diagonal(L)))
148
+
149
+ A_sub = A_w[start:c + 1]
150
+ off_sub = tuple(offdiag_w[lag][start:c - lag] for lag in range(len(offdiag_w)))
151
+ M = assemble_dense(CovarianceBlocks(A_sub, off_sub, len(off_sub)))
152
+ nd = n_cond * d
153
+ r_past = r_w[start:c].reshape(nd)
154
+
155
+ M_pp = M[:nd, :nd] + jitter_chol * jnp.eye(nd, dtype=M.dtype)
156
+ M_pc = M[:nd, nd:]
157
+ M_cc = M[nd:, nd:]
158
+ L_pp = jnp.linalg.cholesky(M_pp)
159
+ rhs = jnp.concatenate([M_pc, r_past[:, None]], axis=1)
160
+ sol = jax.scipy.linalg.cho_solve((L_pp, True), rhs) # (nd, d+1)
161
+ S = M_cc - M_pc.T @ sol[:, :d]
162
+ e = r_c - M_pc.T @ sol[:, d]
163
+
164
+ L_s = jnp.linalg.cholesky(S + jitter_chol * jnp.eye(d, dtype=S.dtype))
165
+ Sinv_e = jax.scipy.linalg.cho_solve((L_s, True), e)
166
+ return 0.5 * jnp.dot(e, Sinv_e) + jnp.sum(jnp.log(jnp.diagonal(L_s)))
167
+
168
+
169
+ def center_nll_contribution(A_w, offdiag_w, r_w, c, jitter_chol=1e-10):
170
+ r"""Per-center windowed NLL ``½ r_cᵀ (P r)_c − ½ log det P_{cc}``.
171
+
172
+ Unlike :func:`center_gram_contribution` this needs no test functions:
173
+ it is the *full* (log-det-bearing) windowed NLL contribution, and is
174
+ differentiable in ``θ, D, Λ`` jointly (the joint AD mode) when the
175
+ blocks are built from live parameters.
176
+
177
+ Returns
178
+ -------
179
+ nll_w : scalar
180
+ """
181
+ W, d = A_w.shape[0], A_w.shape[-1]
182
+ Wd = W * d
183
+ L = _cholesky(A_w, offdiag_w, jitter_chol)
184
+
185
+ r_flat = r_w.reshape(Wd)
186
+ Pr_c = jax.scipy.linalg.cho_solve((L, True), r_flat)[c * d:(c + 1) * d]
187
+ quad = 0.5 * jnp.dot(r_w[c], Pr_c)
188
+
189
+ E_c = jnp.zeros((Wd, d), dtype=A_w.dtype).at[c * d:(c + 1) * d, :].set(jnp.eye(d, dtype=A_w.dtype))
190
+ P_cc = jax.scipy.linalg.cho_solve((L, True), E_c)[c * d:(c + 1) * d, :]
191
+ _, logdet = jnp.linalg.slogdet(P_cc)
192
+ return quad - 0.5 * logdet
193
+
194
+
195
+ def center_loss_contribution(A_w, offdiag_w, r_w, c, jitter_chol=1e-10, bandwidth=1):
196
+ r"""Per-center quadratic ``½ r_cᵀ P_{cc} r_c + Σ_{l≥1} r_cᵀ P_{c,c+l} r_{c+l}``.
197
+
198
+ Summed over all window centers this reproduces the global quadratic
199
+ ``½ rᵀ P r`` exactly (for block-banded ``P``). ``P`` is built from the
200
+ blocks passed in; callers freeze it by passing ``stop_gradient``-ed
201
+ blocks while leaving ``r_w`` live, so the AD-gradient w.r.t. θ is the
202
+ Gauss–Newton score.
203
+
204
+ Parameters
205
+ ----------
206
+ A_w, offdiag_w, r_w : window blocks and residuals (see above).
207
+ c : int center index within the window.
208
+ jitter_chol : float
209
+ bandwidth : int number of lag terms to include.
210
+
211
+ Returns
212
+ -------
213
+ loss : scalar
214
+ """
215
+ W, d = A_w.shape[0], A_w.shape[-1]
216
+ Wd = W * d
217
+ L = _cholesky(A_w, offdiag_w, jitter_chol)
218
+
219
+ # Column c of P: solve Σ X = E_c → X[j] = P_{j,c} = P_{c,j}ᵀ
220
+ E_c = jnp.zeros((Wd, d), dtype=A_w.dtype).at[c * d:(c + 1) * d, :].set(jnp.eye(d, dtype=A_w.dtype))
221
+ Pcol_c = jax.scipy.linalg.cho_solve((L, True), E_c).reshape(W, d, d)
222
+
223
+ r_c = r_w[c]
224
+ P_cc = Pcol_c[c]
225
+ loss = 0.5 * jnp.dot(r_c, P_cc @ r_c)
226
+ for lag in range(1, bandwidth + 1):
227
+ P_c_clag = Pcol_c[c + lag].T # P_{c, c+lag}
228
+ loss = loss + jnp.dot(r_c, P_c_clag @ r_w[c + lag])
229
+ return loss