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,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
|
+
)
|