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.
- SFI/__init__.py +64 -0
- SFI/bases/__init__.py +85 -0
- SFI/bases/constants.py +492 -0
- SFI/bases/linear.py +325 -0
- SFI/bases/monomials.py +218 -0
- SFI/bases/pairs.py +998 -0
- SFI/bases/spde.py +1537 -0
- SFI/diagnostics/__init__.py +60 -0
- SFI/diagnostics/assess.py +87 -0
- SFI/diagnostics/dynamics_order.py +621 -0
- SFI/diagnostics/plotting.py +226 -0
- SFI/diagnostics/report.py +238 -0
- SFI/diagnostics/residual_tests.py +395 -0
- SFI/diagnostics/residuals.py +688 -0
- SFI/inference/__init__.py +58 -0
- SFI/inference/base.py +1460 -0
- SFI/inference/optimizers.py +200 -0
- SFI/inference/overdamped.py +1214 -0
- SFI/inference/parametric_core/__init__.py +34 -0
- SFI/inference/parametric_core/covariance.py +232 -0
- SFI/inference/parametric_core/driver.py +272 -0
- SFI/inference/parametric_core/flow.py +149 -0
- SFI/inference/parametric_core/flow_multi.py +362 -0
- SFI/inference/parametric_core/flow_ud.py +168 -0
- SFI/inference/parametric_core/jacobians.py +540 -0
- SFI/inference/parametric_core/objective.py +286 -0
- SFI/inference/parametric_core/objective_ud.py +253 -0
- SFI/inference/parametric_core/precision.py +229 -0
- SFI/inference/parametric_core/solve.py +763 -0
- SFI/inference/result.py +362 -0
- SFI/inference/serialization.py +245 -0
- SFI/inference/sparse/__init__.py +67 -0
- SFI/inference/sparse/base.py +43 -0
- SFI/inference/sparse/beam.py +303 -0
- SFI/inference/sparse/greedy.py +151 -0
- SFI/inference/sparse/hillclimb.py +307 -0
- SFI/inference/sparse/lasso.py +178 -0
- SFI/inference/sparse/metrics.py +78 -0
- SFI/inference/sparse/result.py +278 -0
- SFI/inference/sparse/scorer.py +323 -0
- SFI/inference/sparse/stlsq.py +165 -0
- SFI/inference/sparsity.py +40 -0
- SFI/inference/underdamped.py +1355 -0
- SFI/integrate/__init__.py +38 -0
- SFI/integrate/api.py +920 -0
- SFI/integrate/integrand.py +402 -0
- SFI/integrate/rk4.py +156 -0
- SFI/integrate/timeops.py +174 -0
- SFI/langevin/__init__.py +29 -0
- SFI/langevin/base.py +863 -0
- SFI/langevin/chunked.py +225 -0
- SFI/langevin/noise.py +446 -0
- SFI/langevin/overdamped.py +560 -0
- SFI/langevin/underdamped.py +448 -0
- SFI/statefunc/__init__.py +49 -0
- SFI/statefunc/basis.py +87 -0
- SFI/statefunc/core/runtime.py +47 -0
- SFI/statefunc/factory.py +346 -0
- SFI/statefunc/interactor.py +90 -0
- SFI/statefunc/layout/__init__.py +29 -0
- SFI/statefunc/layout/_base.py +186 -0
- SFI/statefunc/layout/_eval_compiler.py +453 -0
- SFI/statefunc/layout/_fd_atoms.py +196 -0
- SFI/statefunc/layout/_grid.py +878 -0
- SFI/statefunc/layout/_sectors.py +166 -0
- SFI/statefunc/memhint.py +222 -0
- SFI/statefunc/nodes/__init__.py +56 -0
- SFI/statefunc/nodes/base.py +318 -0
- SFI/statefunc/nodes/contract.py +422 -0
- SFI/statefunc/nodes/interactions/__init__.py +23 -0
- SFI/statefunc/nodes/interactions/dispatcher.py +1365 -0
- SFI/statefunc/nodes/interactions/prepare.py +161 -0
- SFI/statefunc/nodes/interactions/specs.py +362 -0
- SFI/statefunc/nodes/interactions/stencils.py +718 -0
- SFI/statefunc/nodes/leaf.py +530 -0
- SFI/statefunc/nodes/ops/__init__.py +27 -0
- SFI/statefunc/nodes/ops/concat.py +28 -0
- SFI/statefunc/nodes/ops/derivative.py +447 -0
- SFI/statefunc/nodes/ops/einsum.py +120 -0
- SFI/statefunc/nodes/ops/linear.py +153 -0
- SFI/statefunc/nodes/ops/mapn.py +138 -0
- SFI/statefunc/nodes/ops/reshape_rank.py +158 -0
- SFI/statefunc/nodes/ops/slice.py +83 -0
- SFI/statefunc/params.py +309 -0
- SFI/statefunc/psf.py +112 -0
- SFI/statefunc/sf.py +104 -0
- SFI/statefunc/stateexpr.py +1243 -0
- SFI/statefunc/structexpr.py +1003 -0
- SFI/trajectory/__init__.py +31 -0
- SFI/trajectory/collection.py +1122 -0
- SFI/trajectory/dataset.py +1164 -0
- SFI/trajectory/degrade.py +1108 -0
- SFI/trajectory/io.py +1027 -0
- SFI/trajectory/reserved_extras.py +129 -0
- SFI/utils/__init__.py +17 -0
- SFI/utils/formatting.py +308 -0
- SFI/utils/maths.py +185 -0
- SFI/utils/neighbors.py +162 -0
- SFI/utils/plotting.py +1936 -0
- stochasticforceinference-2.0.0.dist-info/METADATA +203 -0
- stochasticforceinference-2.0.0.dist-info/RECORD +104 -0
- stochasticforceinference-2.0.0.dist-info/WHEEL +5 -0
- stochasticforceinference-2.0.0.dist-info/licenses/LICENSE +21 -0
- 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
|