freealg 0.7.12__py3-none-any.whl → 0.7.15__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.
- freealg/__version__.py +1 -1
- freealg/_algebraic_form/_cusp.py +357 -0
- freealg/_algebraic_form/_cusp_wrap.py +268 -0
- freealg/_algebraic_form/_decompress2.py +2 -0
- freealg/_algebraic_form/_decompress4.py +739 -0
- freealg/_algebraic_form/_decompress5.py +738 -0
- freealg/_algebraic_form/_decompress6.py +492 -0
- freealg/_algebraic_form/_decompress7.py +355 -0
- freealg/_algebraic_form/_decompress8.py +369 -0
- freealg/_algebraic_form/_decompress9.py +363 -0
- freealg/_algebraic_form/_decompress_new.py +431 -0
- freealg/_algebraic_form/_decompress_new_2.py +1631 -0
- freealg/_algebraic_form/_decompress_util.py +172 -0
- freealg/_algebraic_form/_homotopy2.py +289 -0
- freealg/_algebraic_form/_homotopy3.py +215 -0
- freealg/_algebraic_form/_homotopy4.py +320 -0
- freealg/_algebraic_form/_homotopy5.py +185 -0
- freealg/_algebraic_form/_moments.py +0 -1
- freealg/_algebraic_form/_support.py +132 -177
- freealg/_algebraic_form/algebraic_form.py +21 -2
- freealg/distributions/_compound_poisson.py +481 -0
- freealg/distributions/_deformed_marchenko_pastur.py +6 -7
- {freealg-0.7.12.dist-info → freealg-0.7.15.dist-info}/METADATA +1 -1
- {freealg-0.7.12.dist-info → freealg-0.7.15.dist-info}/RECORD +28 -12
- {freealg-0.7.12.dist-info → freealg-0.7.15.dist-info}/WHEEL +0 -0
- {freealg-0.7.12.dist-info → freealg-0.7.15.dist-info}/licenses/AUTHORS.txt +0 -0
- {freealg-0.7.12.dist-info → freealg-0.7.15.dist-info}/licenses/LICENSE.txt +0 -0
- {freealg-0.7.12.dist-info → freealg-0.7.15.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright 2026, Siavash Ameli <sameli@berkeley.edu>
|
|
2
|
+
# SPDX-License-Identifier: BSD-3-Clause
|
|
3
|
+
# SPDX-FileType: SOURCE
|
|
4
|
+
"""
|
|
5
|
+
FD decompression with correct characteristic map + robust root selection.
|
|
6
|
+
|
|
7
|
+
Keeps public API:
|
|
8
|
+
- build_time_grid(size, n0, min_n_times=..., include_t0=True) -> (t_all, idx_req)
|
|
9
|
+
- decompress_newton(z_list, t_grid, a_coeffs, w0_list=None, **newton_opt) -> (W, ok)
|
|
10
|
+
|
|
11
|
+
IMPORTANT: This implements the characteristic transform consistent with:
|
|
12
|
+
τ(t)=e^t, α(t)=1-τ^{-1},
|
|
13
|
+
P(z + α w^{-1}, τ w) = 0,
|
|
14
|
+
where P(ζ,y)=0 is the algebraic relation for m0.
|
|
15
|
+
|
|
16
|
+
We construct a polynomial in w:
|
|
17
|
+
Q(w) := w^{deg_z} * P(z + α/w, τ w),
|
|
18
|
+
which has degree deg_z + deg_m (no artificial extra zero roots).
|
|
19
|
+
|
|
20
|
+
Root selection:
|
|
21
|
+
- Herglotz (your sign): Im(w) >= -herglotz_tol for Im(z)>0
|
|
22
|
+
- Homotopy anchor in η: start at η_hi, track down to η_lo
|
|
23
|
+
- Filter roots near anchor, then Viterbi along x
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import math
|
|
29
|
+
import numpy as np
|
|
30
|
+
|
|
31
|
+
__all__ = ["build_time_grid", "decompress_newton"]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _inside_support_mask(x: np.ndarray, edges_row: np.ndarray, pad: float) -> np.ndarray:
|
|
35
|
+
"""
|
|
36
|
+
edges_row: [a1,b1,a2,b2,...] with NaNs allowed (ghost edges).
|
|
37
|
+
Returns mask for x inside union of intervals, with optional padding.
|
|
38
|
+
"""
|
|
39
|
+
mask = np.zeros_like(x, dtype=bool)
|
|
40
|
+
m = edges_row.size
|
|
41
|
+
for j in range(0, m, 2):
|
|
42
|
+
a = edges_row[j]
|
|
43
|
+
b = edges_row[j+1]
|
|
44
|
+
if not (np.isfinite(a) and np.isfinite(b)):
|
|
45
|
+
continue
|
|
46
|
+
aa = float(a) - float(pad)
|
|
47
|
+
bb = float(b) + float(pad)
|
|
48
|
+
mask |= (x >= aa) & (x <= bb)
|
|
49
|
+
return mask
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def build_time_grid(size, n0, min_n_times=0, include_t0=True):
|
|
54
|
+
n0 = float(n0)
|
|
55
|
+
size = np.asarray(size, dtype=float).ravel()
|
|
56
|
+
if size.size == 0:
|
|
57
|
+
if include_t0:
|
|
58
|
+
return np.array([0.0], dtype=float), np.array([0], dtype=int)
|
|
59
|
+
return np.empty((0,), dtype=float), np.empty((0,), dtype=int)
|
|
60
|
+
|
|
61
|
+
t_sizes = np.log(size / n0)
|
|
62
|
+
|
|
63
|
+
t_req = t_sizes.copy()
|
|
64
|
+
if include_t0:
|
|
65
|
+
t_req = np.concatenate((np.array([0.0], dtype=float), t_req))
|
|
66
|
+
|
|
67
|
+
t_req = np.unique(t_req)
|
|
68
|
+
t_req.sort()
|
|
69
|
+
|
|
70
|
+
if int(min_n_times) > 0 and t_req.size < int(min_n_times):
|
|
71
|
+
t_all = np.linspace(float(t_req[0]), float(t_req[-1]), int(min_n_times))
|
|
72
|
+
else:
|
|
73
|
+
t_all = t_req
|
|
74
|
+
|
|
75
|
+
t_all = np.unique(t_all)
|
|
76
|
+
t_all.sort()
|
|
77
|
+
|
|
78
|
+
idx_req = np.array([int(np.argmin(np.abs(t_all - ts))) for ts in t_sizes], dtype=int)
|
|
79
|
+
return t_all, idx_req
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# ===========================
|
|
83
|
+
# Polynomial curve utilities
|
|
84
|
+
# ===========================
|
|
85
|
+
|
|
86
|
+
def _poly_w_coeffs(z: complex, t: float, a_coeffs: np.ndarray) -> np.ndarray:
|
|
87
|
+
"""
|
|
88
|
+
Build Q(w) coeffs (descending) for:
|
|
89
|
+
Q(w) = w^{deg_z} * P(z + α/w, τ w)
|
|
90
|
+
where τ=e^t, α=1-1/τ.
|
|
91
|
+
"""
|
|
92
|
+
a = np.asarray(a_coeffs, dtype=np.complex128)
|
|
93
|
+
deg_z = a.shape[0] - 1
|
|
94
|
+
deg_m = a.shape[1] - 1
|
|
95
|
+
|
|
96
|
+
tau = math.exp(float(t))
|
|
97
|
+
alpha = 1.0 - 1.0 / tau
|
|
98
|
+
z = complex(z)
|
|
99
|
+
|
|
100
|
+
deg_Q = deg_z + deg_m
|
|
101
|
+
c = np.zeros((deg_Q + 1,), dtype=np.complex128) # ascending
|
|
102
|
+
|
|
103
|
+
# term: a_{i,j} (z + alpha/w)^i (tau*w)^j
|
|
104
|
+
# expand (z + alpha/w)^i = sum_{k=0}^i C(i,k) z^{i-k} (alpha/w)^k
|
|
105
|
+
# multiply by w^{deg_z}: exponent of w is deg_z + j - k (>=0).
|
|
106
|
+
for i in range(deg_z + 1):
|
|
107
|
+
for j in range(deg_m + 1):
|
|
108
|
+
aij = a[i, j]
|
|
109
|
+
if aij == 0:
|
|
110
|
+
continue
|
|
111
|
+
for k in range(i + 1):
|
|
112
|
+
p = deg_z + j - k
|
|
113
|
+
if p < 0 or p > deg_Q:
|
|
114
|
+
continue
|
|
115
|
+
c[p] += aij * math.comb(i, k) * (z ** (i - k)) * ((alpha) ** k) * (tau ** j)
|
|
116
|
+
|
|
117
|
+
nz = np.flatnonzero(np.abs(c) > 0)
|
|
118
|
+
if nz.size == 0:
|
|
119
|
+
return np.array([0.0], dtype=np.complex128)
|
|
120
|
+
p_max = int(nz.max())
|
|
121
|
+
c = c[:p_max + 1]
|
|
122
|
+
return c[::-1].copy()
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _herglotz_ok(w: complex, z: complex, tol: float = 0.0) -> bool:
|
|
126
|
+
z = complex(z)
|
|
127
|
+
w = complex(w)
|
|
128
|
+
if z.imag <= 0.0:
|
|
129
|
+
return True
|
|
130
|
+
return (w.imag >= -float(tol))
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _asym_score(z: complex, w: complex) -> float:
|
|
134
|
+
return float(abs(complex(z) * complex(w) + 1.0))
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _newton_poly_root(coeff_desc: np.ndarray, w0: complex, max_iter: int, tol: float,
|
|
138
|
+
armijo: bool = True, min_lam: float = 1e-4):
|
|
139
|
+
w = complex(w0)
|
|
140
|
+
dcoeff = np.polyder(coeff_desc)
|
|
141
|
+
for _ in range(int(max_iter)):
|
|
142
|
+
f = np.polyval(coeff_desc, w)
|
|
143
|
+
if not np.isfinite(f):
|
|
144
|
+
return w, False
|
|
145
|
+
if abs(f) <= float(tol) * (1.0 + abs(w)):
|
|
146
|
+
return w, True
|
|
147
|
+
df = np.polyval(dcoeff, w)
|
|
148
|
+
if (not np.isfinite(df)) or df == 0:
|
|
149
|
+
return w, False
|
|
150
|
+
step = f / df
|
|
151
|
+
|
|
152
|
+
if not armijo:
|
|
153
|
+
w = w - step
|
|
154
|
+
continue
|
|
155
|
+
|
|
156
|
+
f0 = abs(f)
|
|
157
|
+
lam = 1.0
|
|
158
|
+
while lam >= float(min_lam):
|
|
159
|
+
w_try = w - lam * step
|
|
160
|
+
f_try = np.polyval(coeff_desc, w_try)
|
|
161
|
+
if np.isfinite(f_try) and abs(f_try) <= (1.0 - 0.5 * lam) * f0:
|
|
162
|
+
w = w_try
|
|
163
|
+
break
|
|
164
|
+
lam *= 0.5
|
|
165
|
+
else:
|
|
166
|
+
w = w - float(min_lam) * step
|
|
167
|
+
|
|
168
|
+
f = np.polyval(coeff_desc, w)
|
|
169
|
+
return w, bool(np.isfinite(f) and abs(f) <= float(tol) * (1.0 + abs(w)))
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _roots_of_Q(z: complex, t: float, a_coeffs: np.ndarray):
|
|
173
|
+
coeff_desc = _poly_w_coeffs(z, t, a_coeffs)
|
|
174
|
+
if coeff_desc.size <= 1:
|
|
175
|
+
return coeff_desc, np.empty((0,), np.complex128)
|
|
176
|
+
r = np.roots(coeff_desc)
|
|
177
|
+
r = r[np.isfinite(r)]
|
|
178
|
+
return coeff_desc, r.astype(np.complex128, copy=False)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _physical_anchor_for_x(x: float, t: float, a_coeffs: np.ndarray,
|
|
182
|
+
eta_hi: float, eta_lo: float, n_eta: int,
|
|
183
|
+
herglotz_tol: float,
|
|
184
|
+
max_iter: int, tol: float,
|
|
185
|
+
armijo: bool, min_lam: float):
|
|
186
|
+
etas = np.linspace(float(eta_hi), float(eta_lo), int(n_eta))
|
|
187
|
+
z0 = complex(x, etas[0])
|
|
188
|
+
|
|
189
|
+
coeff0, roots0 = _roots_of_Q(z0, t, a_coeffs)
|
|
190
|
+
if roots0.size == 0:
|
|
191
|
+
return -1.0 / z0, False
|
|
192
|
+
|
|
193
|
+
# pick best among Herglotz candidates by asymptotic score
|
|
194
|
+
good = [w for w in roots0 if _herglotz_ok(w, z0, herglotz_tol)]
|
|
195
|
+
if len(good) == 0:
|
|
196
|
+
good = list(roots0)
|
|
197
|
+
good = np.asarray(good, dtype=np.complex128)
|
|
198
|
+
sc = np.array([_asym_score(z0, w) for w in good], dtype=float)
|
|
199
|
+
w = good[int(np.argmin(sc))]
|
|
200
|
+
|
|
201
|
+
# refine and track down
|
|
202
|
+
w, _ = _newton_poly_root(coeff0, w, max_iter=max_iter, tol=tol, armijo=armijo, min_lam=min_lam)
|
|
203
|
+
|
|
204
|
+
for eta in etas[1:]:
|
|
205
|
+
z = complex(x, eta)
|
|
206
|
+
coeff, _ = _roots_of_Q(z, t, a_coeffs)
|
|
207
|
+
w, ok2 = _newton_poly_root(coeff, w, max_iter=max_iter, tol=tol, armijo=armijo, min_lam=min_lam)
|
|
208
|
+
if not ok2:
|
|
209
|
+
_coeff, roots = _roots_of_Q(z, t, a_coeffs)
|
|
210
|
+
if roots.size == 0:
|
|
211
|
+
return w, False
|
|
212
|
+
roots = np.asarray(roots, dtype=np.complex128)
|
|
213
|
+
mask = np.array([_herglotz_ok(ww, z, herglotz_tol) for ww in roots], dtype=bool)
|
|
214
|
+
cand = roots[mask] if np.any(mask) else roots
|
|
215
|
+
jj = int(np.argmin(np.abs(cand - w)))
|
|
216
|
+
w = cand[jj]
|
|
217
|
+
w, ok2 = _newton_poly_root(coeff, w, max_iter=max_iter, tol=tol, armijo=armijo, min_lam=min_lam)
|
|
218
|
+
if not ok2:
|
|
219
|
+
return w, False
|
|
220
|
+
|
|
221
|
+
return w, True
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def _candidate_filter(roots: np.ndarray, z: complex, herglotz_tol: float,
|
|
225
|
+
anchor: complex | None, anchor_radius: float,
|
|
226
|
+
*, w_min: float = 0.0,
|
|
227
|
+
im_floor: float | None = None):
|
|
228
|
+
if roots.size == 0:
|
|
229
|
+
return np.empty((0,), np.complex128)
|
|
230
|
+
keep = []
|
|
231
|
+
for w in roots:
|
|
232
|
+
if abs(w) <= float(w_min):
|
|
233
|
+
continue
|
|
234
|
+
if not _herglotz_ok(w, z, herglotz_tol):
|
|
235
|
+
continue
|
|
236
|
+
if im_floor is not None and z.imag > 0.0:
|
|
237
|
+
if w.imag < float(im_floor):
|
|
238
|
+
continue
|
|
239
|
+
if anchor is not None:
|
|
240
|
+
if abs(w - anchor) > float(anchor_radius) * (1.0 + abs(anchor)):
|
|
241
|
+
continue
|
|
242
|
+
keep.append(complex(w))
|
|
243
|
+
if len(keep) == 0:
|
|
244
|
+
return np.empty((0,), np.complex128)
|
|
245
|
+
# dedup
|
|
246
|
+
out = []
|
|
247
|
+
for w in keep:
|
|
248
|
+
if all(abs(w-u) > 1e-9*(1.0+abs(u)) for u in out):
|
|
249
|
+
out.append(w)
|
|
250
|
+
return np.asarray(out, dtype=np.complex128)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def _viterbi_path(cand_list, z_list, w_prev,
|
|
254
|
+
lam_time, lam_space, lam_asym, lam_im2,
|
|
255
|
+
edge_k):
|
|
256
|
+
nz = len(cand_list)
|
|
257
|
+
chosen = np.empty((nz,), dtype=np.complex128)
|
|
258
|
+
ok = np.ones((nz,), dtype=bool)
|
|
259
|
+
big = 1e300
|
|
260
|
+
|
|
261
|
+
sizes = np.array([c.size for c in cand_list], dtype=int)
|
|
262
|
+
for i in range(nz):
|
|
263
|
+
if sizes[i] == 0:
|
|
264
|
+
chosen[i] = w_prev[i]
|
|
265
|
+
ok[i] = False
|
|
266
|
+
|
|
267
|
+
i = 0
|
|
268
|
+
while i < nz:
|
|
269
|
+
if sizes[i] == 0:
|
|
270
|
+
i += 1
|
|
271
|
+
continue
|
|
272
|
+
j = i
|
|
273
|
+
while j < nz and sizes[j] > 0:
|
|
274
|
+
j += 1
|
|
275
|
+
|
|
276
|
+
block = list(range(i, j))
|
|
277
|
+
c0 = cand_list[i]
|
|
278
|
+
m0 = c0.size
|
|
279
|
+
|
|
280
|
+
dp = np.full((m0,), big, dtype=float)
|
|
281
|
+
bp = [None] * (j - i)
|
|
282
|
+
|
|
283
|
+
node = lam_time * (np.abs(c0 - w_prev[i]) ** 2)
|
|
284
|
+
if edge_k > 0 and (i < edge_k or i >= nz - edge_k):
|
|
285
|
+
node = node + lam_asym * np.array([_asym_score(z_list[i], w) for w in c0], dtype=float)
|
|
286
|
+
if lam_im2 != 0.0:
|
|
287
|
+
node = node + lam_im2 * (np.imag(c0) ** 2)
|
|
288
|
+
dp = node
|
|
289
|
+
bp[0] = np.full((m0,), -1, dtype=int)
|
|
290
|
+
|
|
291
|
+
for kpos, idx in enumerate(block[1:], start=1):
|
|
292
|
+
ck = cand_list[idx]
|
|
293
|
+
mk = ck.size
|
|
294
|
+
new_dp = np.full((mk,), big, dtype=float)
|
|
295
|
+
new_bp = np.full((mk,), -1, dtype=int)
|
|
296
|
+
|
|
297
|
+
node = lam_time * (np.abs(ck - w_prev[idx]) ** 2)
|
|
298
|
+
if edge_k > 0 and (idx < edge_k or idx >= nz - edge_k):
|
|
299
|
+
node = node + lam_asym * np.array([_asym_score(z_list[idx], w) for w in ck], dtype=float)
|
|
300
|
+
if lam_im2 != 0.0:
|
|
301
|
+
node = node + lam_im2 * (np.imag(ck) ** 2)
|
|
302
|
+
|
|
303
|
+
prev_c = cand_list[idx - 1]
|
|
304
|
+
for q in range(mk):
|
|
305
|
+
vals = dp + lam_space * (np.abs(ck[q] - prev_c) ** 2)
|
|
306
|
+
best = int(np.argmin(vals))
|
|
307
|
+
new_dp[q] = float(vals[best] + node[q])
|
|
308
|
+
new_bp[q] = best
|
|
309
|
+
|
|
310
|
+
dp = new_dp
|
|
311
|
+
bp[kpos] = new_bp
|
|
312
|
+
|
|
313
|
+
end_q = int(np.argmin(dp))
|
|
314
|
+
for kpos in range(len(block) - 1, -1, -1):
|
|
315
|
+
idx = block[kpos]
|
|
316
|
+
chosen[idx] = cand_list[idx][end_q]
|
|
317
|
+
end_q = int(bp[kpos][end_q])
|
|
318
|
+
|
|
319
|
+
i = j
|
|
320
|
+
|
|
321
|
+
return chosen, ok
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
# =====================
|
|
325
|
+
# Main decompression API
|
|
326
|
+
# =====================
|
|
327
|
+
|
|
328
|
+
def decompress_newton(
|
|
329
|
+
z_list,
|
|
330
|
+
t_grid,
|
|
331
|
+
a_coeffs,
|
|
332
|
+
w0_list=None,
|
|
333
|
+
*,
|
|
334
|
+
dt_max=0.05,
|
|
335
|
+
return_success_rate=False,
|
|
336
|
+
viterbi=True,
|
|
337
|
+
viterbi_opt=None,
|
|
338
|
+
# edge-guided selection (optional)
|
|
339
|
+
edge_use=False,
|
|
340
|
+
edge_support=None,
|
|
341
|
+
edge_pad=0.0,
|
|
342
|
+
im_floor_rel=0.15,
|
|
343
|
+
w_min=1e-14,
|
|
344
|
+
# homotopy (eta) options
|
|
345
|
+
eta_hi=3.0,
|
|
346
|
+
n_eta=24,
|
|
347
|
+
anchor_radius=0.6,
|
|
348
|
+
# newton options for homotopy
|
|
349
|
+
max_iter=60,
|
|
350
|
+
tol=1e-12,
|
|
351
|
+
armijo=True,
|
|
352
|
+
min_lam=1e-4,
|
|
353
|
+
# herglotz convention
|
|
354
|
+
herglotz_tol=0.0,
|
|
355
|
+
**_,
|
|
356
|
+
):
|
|
357
|
+
z_list = np.asarray(z_list, dtype=np.complex128).ravel()
|
|
358
|
+
t_grid = np.asarray(t_grid, dtype=float).ravel()
|
|
359
|
+
if z_list.size == 0 or t_grid.size == 0:
|
|
360
|
+
raise ValueError("z_list and t_grid must be non-empty")
|
|
361
|
+
|
|
362
|
+
t_grid = np.unique(t_grid)
|
|
363
|
+
t_grid.sort()
|
|
364
|
+
if np.any(np.diff(t_grid) <= 0.0):
|
|
365
|
+
raise ValueError("t_grid must be strictly increasing")
|
|
366
|
+
|
|
367
|
+
nt = t_grid.size
|
|
368
|
+
nz = z_list.size
|
|
369
|
+
x = z_list.real
|
|
370
|
+
eta_lo = float(np.median(z_list.imag))
|
|
371
|
+
|
|
372
|
+
real_edges = None
|
|
373
|
+
if edge_use:
|
|
374
|
+
try:
|
|
375
|
+
from ._edge import evolve_edges, merge_edges
|
|
376
|
+
if edge_support is None:
|
|
377
|
+
raise ValueError("edge_support must be provided when edge_use=True")
|
|
378
|
+
complex_edges = evolve_edges(t_grid, a_coeffs, support=edge_support)
|
|
379
|
+
# merge_edges in your package expects edges array (nt, 2k) and returns (real_merged_edges, active_k)
|
|
380
|
+
real_edges, _active_k = merge_edges(complex_edges, t_grid)
|
|
381
|
+
except Exception:
|
|
382
|
+
real_edges = None
|
|
383
|
+
|
|
384
|
+
if w0_list is None:
|
|
385
|
+
w0_list = -1.0 / z_list
|
|
386
|
+
w_prev = np.asarray(w0_list, dtype=np.complex128).ravel()
|
|
387
|
+
if w_prev.size != nz:
|
|
388
|
+
raise ValueError("w0_list length must match z_list")
|
|
389
|
+
|
|
390
|
+
vopt = {} if viterbi_opt is None else dict(viterbi_opt)
|
|
391
|
+
lam_time = float(vopt.get("lam_time", 0.25))
|
|
392
|
+
lam_space = float(vopt.get("lam_space", 1.0))
|
|
393
|
+
lam_asym = float(vopt.get("lam_asym", 0.2))
|
|
394
|
+
lam_im2 = float(vopt.get("lam_im2", 0.0))
|
|
395
|
+
edge_k = int(vopt.get("edge_k", 8))
|
|
396
|
+
|
|
397
|
+
W = np.empty((nt, nz), dtype=np.complex128)
|
|
398
|
+
ok = np.ones((nt, nz), dtype=bool)
|
|
399
|
+
W[0, :] = w_prev
|
|
400
|
+
success = np.ones((nt,), dtype=float)
|
|
401
|
+
success[0] = 1.0
|
|
402
|
+
|
|
403
|
+
for it in range(1, nt):
|
|
404
|
+
t1 = float(t_grid[it])
|
|
405
|
+
t0 = float(t_grid[it - 1])
|
|
406
|
+
dt = t1 - t0
|
|
407
|
+
n_sub = max(1, int(np.ceil(abs(dt) / max(float(dt_max), 1e-12))))
|
|
408
|
+
sub_ts = np.linspace(t0, t1, n_sub + 1)[1:]
|
|
409
|
+
|
|
410
|
+
ok_row = np.ones((nz,), dtype=bool)
|
|
411
|
+
|
|
412
|
+
for t_sub in sub_ts:
|
|
413
|
+
anchors = np.empty((nz,), dtype=np.complex128)
|
|
414
|
+
anchor_ok = np.ones((nz,), dtype=bool)
|
|
415
|
+
for iz in range(nz):
|
|
416
|
+
w_a, ok_a = _physical_anchor_for_x(
|
|
417
|
+
float(x[iz]), float(t_sub), a_coeffs,
|
|
418
|
+
eta_hi=float(eta_hi), eta_lo=float(eta_lo),
|
|
419
|
+
n_eta=int(n_eta),
|
|
420
|
+
herglotz_tol=float(herglotz_tol),
|
|
421
|
+
max_iter=int(max_iter), tol=float(tol),
|
|
422
|
+
armijo=bool(armijo), min_lam=float(min_lam),
|
|
423
|
+
)
|
|
424
|
+
anchors[iz] = w_a
|
|
425
|
+
anchor_ok[iz] = ok_a
|
|
426
|
+
|
|
427
|
+
cand_list = []
|
|
428
|
+
for iz in range(nz):
|
|
429
|
+
z = z_list[iz]
|
|
430
|
+
coeff, roots = _roots_of_Q(z, float(t_sub), a_coeffs)
|
|
431
|
+
|
|
432
|
+
anc = anchors[iz]
|
|
433
|
+
im_floor = None
|
|
434
|
+
if real_edges is not None:
|
|
435
|
+
inside = _inside_support_mask(np.array([float(x[iz])]), real_edges[int(np.argmin(np.abs(t_grid - float(t_sub))))], pad=float(edge_pad))[0]
|
|
436
|
+
if inside:
|
|
437
|
+
# require a fraction of the max positive imaginary root to avoid collapsing early
|
|
438
|
+
im_pos = np.max(np.maximum(0.0, roots.imag)) if roots.size > 0 else 0.0
|
|
439
|
+
if im_pos > 0.0:
|
|
440
|
+
im_floor = float(im_floor_rel) * float(im_pos)
|
|
441
|
+
cands = _candidate_filter(
|
|
442
|
+
roots, z, herglotz_tol=float(herglotz_tol),
|
|
443
|
+
anchor=anc if anchor_ok[iz] else None,
|
|
444
|
+
anchor_radius=float(anchor_radius),
|
|
445
|
+
w_min=float(w_min),
|
|
446
|
+
im_floor=im_floor,
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
# fallback: take nearest to anchor
|
|
450
|
+
if cands.size == 0 and roots.size > 0:
|
|
451
|
+
roots = roots.astype(np.complex128, copy=False)
|
|
452
|
+
roots2 = roots[np.abs(roots) > float(w_min)]
|
|
453
|
+
if roots2.size == 0:
|
|
454
|
+
roots2 = roots
|
|
455
|
+
jj = int(np.argmin(np.abs(roots2 - anc)))
|
|
456
|
+
cands = np.array([roots2[jj]], dtype=np.complex128)
|
|
457
|
+
|
|
458
|
+
# include refined anchor if it lands on a root
|
|
459
|
+
if coeff.size > 1:
|
|
460
|
+
anc_ref, ok_ref = _newton_poly_root(coeff, anc, max_iter=max_iter, tol=tol,
|
|
461
|
+
armijo=armijo, min_lam=min_lam)
|
|
462
|
+
if ok_ref and _herglotz_ok(anc_ref, z, tol=herglotz_tol):
|
|
463
|
+
cands = np.unique(np.concatenate((cands, np.array([anc_ref], dtype=np.complex128))))
|
|
464
|
+
|
|
465
|
+
cand_list.append(cands)
|
|
466
|
+
|
|
467
|
+
if viterbi:
|
|
468
|
+
w_path, ok_path = _viterbi_path(
|
|
469
|
+
cand_list, z_list, w_prev,
|
|
470
|
+
lam_time, lam_space, lam_asym, lam_im2, edge_k
|
|
471
|
+
)
|
|
472
|
+
w_prev = w_path
|
|
473
|
+
ok_row &= ok_path
|
|
474
|
+
else:
|
|
475
|
+
new_row = np.empty((nz,), dtype=np.complex128)
|
|
476
|
+
for iz in range(nz):
|
|
477
|
+
c = cand_list[iz]
|
|
478
|
+
if c.size == 0:
|
|
479
|
+
new_row[iz] = w_prev[iz]
|
|
480
|
+
ok_row[iz] = False
|
|
481
|
+
continue
|
|
482
|
+
jj = int(np.argmin(np.abs(c - w_prev[iz]) ** 2))
|
|
483
|
+
new_row[iz] = c[jj]
|
|
484
|
+
w_prev = new_row
|
|
485
|
+
|
|
486
|
+
W[it, :] = w_prev
|
|
487
|
+
ok[it, :] = ok_row
|
|
488
|
+
success[it] = float(np.mean(ok_row))
|
|
489
|
+
|
|
490
|
+
if return_success_rate:
|
|
491
|
+
return W, ok, success
|
|
492
|
+
return W, ok
|