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,200 @@
1
+ # SFI/inference/optimizers.py
2
+ """
3
+ Optimizer back-ends for parametric inference methods.
4
+
5
+ Provides L-BFGS-B (via SciPy) and Adam (via optax) wrappers with
6
+ logging, best-parameter tracking, and a unified result interface.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import logging
12
+
13
+ import jax
14
+ import jax.numpy as jnp
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ def optimize_lbfgsb(loss, loss_grad, theta0_flat, *, maxiter, tol):
20
+ """L-BFGS-B via SciPy (returns a SciPy OptimizeResult)."""
21
+ import time
22
+
23
+ from scipy.optimize import minimize
24
+
25
+ def fun(theta_np):
26
+ return float(loss(jnp.asarray(theta_np)))
27
+
28
+ def jac(theta_np):
29
+ return jnp.asarray(loss_grad(jnp.asarray(theta_np)), dtype=float)
30
+
31
+ logger.info("[force_nonlinear] Starting L-BFGS-B optimization...")
32
+ t0 = time.perf_counter()
33
+ res = minimize(
34
+ fun,
35
+ jnp.asarray(theta0_flat),
36
+ jac=jac,
37
+ method="L-BFGS-B",
38
+ tol=tol,
39
+ options={"maxiter": maxiter},
40
+ )
41
+ t1 = time.perf_counter()
42
+ logger.info(
43
+ "[force_nonlinear] L-BFGS-B finished in %.2f s | nit=%d, nfev=%d, final f=%.6g, status=%d (%s)",
44
+ t1 - t0,
45
+ res.nit,
46
+ res.nfev,
47
+ res.fun,
48
+ res.status,
49
+ res.message,
50
+ )
51
+ return res
52
+
53
+
54
+ def optimize_adam(
55
+ loss,
56
+ loss_grad,
57
+ theta0_flat,
58
+ *,
59
+ maxiter,
60
+ learning_rate,
61
+ lr_schedule,
62
+ loss_grad_batch=None,
63
+ batch_rng_seed=0,
64
+ batch_schedule=None,
65
+ ):
66
+ """Adam via optax (returns a namespace mimicking SciPy OptimizeResult).
67
+
68
+ When *loss_grad_batch* is provided, mini-batch stochastic gradients
69
+ are used for parameter updates while the full-data *loss* is still
70
+ used for tracking / best-parameter selection.
71
+
72
+ Parameters
73
+ ----------
74
+ loss_grad_batch : callable, optional
75
+ ``loss_grad_batch(theta, rng_key) -> grad``. When given, each
76
+ Adam step uses a stochastic gradient instead of the full-data
77
+ gradient. Ignored when *batch_schedule* is set.
78
+ batch_rng_seed : int
79
+ Seed for the mini-batch PRNG stream.
80
+ batch_schedule : list of (float, callable), optional
81
+ Batch-size annealing schedule. Each entry is
82
+ ``(step_fraction, grad_fn)`` where *grad_fn* has the signature
83
+ ``grad_fn(theta, rng_key) -> grad``. The list must be sorted
84
+ by ascending *step_fraction*; the last entry should have
85
+ fraction 1.0. At each step the active gradient function is
86
+ the first whose fraction exceeds ``step / maxiter``.
87
+ """
88
+ import time
89
+
90
+ import optax
91
+
92
+ # Build learning-rate schedule
93
+ if lr_schedule == "cosine":
94
+ schedule = optax.cosine_decay_schedule(
95
+ init_value=float(learning_rate),
96
+ decay_steps=maxiter,
97
+ )
98
+ elif lr_schedule == "constant" or lr_schedule is None:
99
+ schedule = float(learning_rate)
100
+ else:
101
+ raise ValueError(f"Unknown lr_schedule {lr_schedule!r}; choose 'cosine' or 'constant'.")
102
+
103
+ opt = optax.adam(schedule)
104
+ theta = jnp.asarray(theta0_flat)
105
+ opt_state = opt.init(theta)
106
+
107
+ best_f = float("inf")
108
+ best_theta = theta
109
+
110
+ use_schedule = batch_schedule is not None
111
+ use_minibatch = loss_grad_batch is not None or use_schedule
112
+
113
+ if use_minibatch:
114
+ rng_key = jax.random.PRNGKey(batch_rng_seed)
115
+ if use_schedule:
116
+ logger.info(
117
+ "[force_nonlinear] Starting Adam with batch-size annealing (%d phases, maxiter=%d, seed=%d)...",
118
+ len(batch_schedule),
119
+ maxiter,
120
+ batch_rng_seed,
121
+ )
122
+ else:
123
+ logger.info(
124
+ "[force_nonlinear] Starting Adam optimization with mini-batch (maxiter=%d, seed=%d)...",
125
+ maxiter,
126
+ batch_rng_seed,
127
+ )
128
+ else:
129
+ logger.info("[force_nonlinear] Starting Adam optimization (maxiter=%d)...", maxiter)
130
+
131
+ prev_phase = -1
132
+ t0 = time.perf_counter()
133
+ for step in range(maxiter):
134
+ if use_schedule:
135
+ frac = step / maxiter
136
+ phase = 0
137
+ active_grad = batch_schedule[-1][1]
138
+ for i, (sf, gfn) in enumerate(batch_schedule):
139
+ if frac < sf:
140
+ active_grad = gfn
141
+ phase = i
142
+ break
143
+ if phase != prev_phase:
144
+ prev_phase = phase
145
+ logger.info(
146
+ "[force_nonlinear] phase %d/%d starts at step %d",
147
+ phase,
148
+ len(batch_schedule),
149
+ step,
150
+ )
151
+ rng_key, subkey = jax.random.split(rng_key)
152
+ g = active_grad(theta, subkey)
153
+ elif use_minibatch:
154
+ rng_key, subkey = jax.random.split(rng_key)
155
+ assert loss_grad_batch is not None
156
+ g = loss_grad_batch(theta, subkey)
157
+ else:
158
+ g = loss_grad(theta)
159
+ updates, opt_state = opt.update(g, opt_state, theta)
160
+ theta = optax.apply_updates(theta, updates)
161
+
162
+ if step % max(1, maxiter // 20) == 0 or step == maxiter - 1:
163
+ f_val = float(loss(theta))
164
+ if f_val < best_f:
165
+ best_f = f_val
166
+ best_theta = theta
167
+ logger.info(
168
+ "[force_nonlinear] step %5d / %d loss=%.6g |grad|=%.3e",
169
+ step,
170
+ maxiter,
171
+ f_val,
172
+ float(jnp.linalg.norm(g)),
173
+ )
174
+
175
+ t1 = time.perf_counter()
176
+ # Final eval
177
+ f_final = float(loss(theta))
178
+ if f_final < best_f:
179
+ best_f = f_final
180
+ best_theta = theta
181
+ logger.info(
182
+ "[force_nonlinear] Adam finished in %.2f s | %d steps, final loss=%.6g, best loss=%.6g",
183
+ t1 - t0,
184
+ maxiter,
185
+ f_final,
186
+ best_f,
187
+ )
188
+
189
+ # Return an object with the same .x / .fun interface as SciPy
190
+ from types import SimpleNamespace
191
+
192
+ return SimpleNamespace(
193
+ x=best_theta,
194
+ fun=best_f,
195
+ nit=maxiter,
196
+ nfev=maxiter,
197
+ success=True,
198
+ message="Adam optimization completed.",
199
+ status=0,
200
+ )