freealg 0.7.11__py3-none-any.whl → 0.7.14__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/__init__.py +2 -2
- freealg/__version__.py +1 -1
- freealg/_algebraic_form/__init__.py +2 -1
- freealg/_algebraic_form/_constraints.py +53 -12
- freealg/_algebraic_form/_cusp.py +357 -0
- freealg/_algebraic_form/_cusp_wrap.py +268 -0
- freealg/_algebraic_form/_decompress.py +330 -381
- freealg/_algebraic_form/_decompress2.py +120 -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/_edge.py +46 -68
- freealg/_algebraic_form/_homotopy.py +62 -30
- 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 +43 -57
- freealg/_algebraic_form/_support.py +132 -177
- freealg/_algebraic_form/algebraic_form.py +163 -30
- freealg/distributions/__init__.py +3 -1
- freealg/distributions/_compound_poisson.py +464 -0
- freealg/distributions/_deformed_marchenko_pastur.py +51 -0
- freealg/distributions/_deformed_wigner.py +44 -0
- {freealg-0.7.11.dist-info → freealg-0.7.14.dist-info}/METADATA +2 -1
- {freealg-0.7.11.dist-info → freealg-0.7.14.dist-info}/RECORD +36 -20
- {freealg-0.7.11.dist-info → freealg-0.7.14.dist-info}/WHEEL +1 -1
- {freealg-0.7.11.dist-info → freealg-0.7.14.dist-info}/licenses/AUTHORS.txt +0 -0
- {freealg-0.7.11.dist-info → freealg-0.7.14.dist-info}/licenses/LICENSE.txt +0 -0
- {freealg-0.7.11.dist-info → freealg-0.7.14.dist-info}/top_level.txt +0 -0
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
# =======
|
|
13
13
|
|
|
14
14
|
import numpy
|
|
15
|
-
from ._continuation_algebraic import powers
|
|
16
15
|
|
|
17
16
|
__all__ = ['build_time_grid', 'decompress_newton_old', 'decompress_newton']
|
|
18
17
|
|
|
@@ -21,11 +20,11 @@ __all__ = ['build_time_grid', 'decompress_newton_old', 'decompress_newton']
|
|
|
21
20
|
# build time grid
|
|
22
21
|
# ===============
|
|
23
22
|
|
|
24
|
-
def build_time_grid(sizes, n0,
|
|
23
|
+
def build_time_grid(sizes, n0, min_n_times=0):
|
|
25
24
|
"""
|
|
26
25
|
sizes: list/array of requested matrix sizes (e.g. [2000,3000,4000,8000])
|
|
27
26
|
n0: initial size (self.n)
|
|
28
|
-
|
|
27
|
+
min_n_times: minimum number of time points to run Newton sweep on
|
|
29
28
|
|
|
30
29
|
Returns
|
|
31
30
|
-------
|
|
@@ -43,7 +42,7 @@ def build_time_grid(sizes, n0, min_n_time=0):
|
|
|
43
42
|
t_all = numpy.sort(base)
|
|
44
43
|
|
|
45
44
|
# Add points only if needed: split largest gaps
|
|
46
|
-
N = int(
|
|
45
|
+
N = int(min_n_times) if min_n_times is not None else 0
|
|
47
46
|
while t_all.size < N and t_all.size >= 2:
|
|
48
47
|
gaps = numpy.diff(t_all)
|
|
49
48
|
k = int(numpy.argmax(gaps))
|
|
@@ -60,121 +59,8 @@ def build_time_grid(sizes, n0, min_n_time=0):
|
|
|
60
59
|
return t_all, idx_req
|
|
61
60
|
|
|
62
61
|
|
|
63
|
-
# ===============
|
|
64
|
-
# eval P partials
|
|
65
|
-
# ===============
|
|
66
|
-
|
|
67
|
-
def eval_P_partials(z, m, a_coeffs):
|
|
68
|
-
"""
|
|
69
|
-
Evaluate P(z,m) and its partial derivatives dP/dz and dP/dm.
|
|
70
|
-
|
|
71
|
-
This assumes P is represented by `a_coeffs` in the monomial basis
|
|
72
|
-
|
|
73
|
-
P(z, m) = sum_{j=0..s} a_j(z) * m^j,
|
|
74
|
-
a_j(z) = sum_{i=0..deg_z} a_coeffs[i, j] * z^i.
|
|
75
|
-
|
|
76
|
-
The function returns P, dP/dz, dP/dm with broadcasting over z and m.
|
|
77
|
-
|
|
78
|
-
Parameters
|
|
79
|
-
----------
|
|
80
|
-
z : complex or array_like of complex
|
|
81
|
-
First argument to P.
|
|
82
|
-
m : complex or array_like of complex
|
|
83
|
-
Second argument to P. Must be broadcast-compatible with `z`.
|
|
84
|
-
a_coeffs : ndarray, shape (deg_z+1, s+1)
|
|
85
|
-
Coefficient matrix for P in the monomial basis.
|
|
86
|
-
|
|
87
|
-
Returns
|
|
88
|
-
-------
|
|
89
|
-
P : complex or ndarray of complex
|
|
90
|
-
Value P(z,m).
|
|
91
|
-
Pz : complex or ndarray of complex
|
|
92
|
-
Partial derivative dP/dz evaluated at (z,m).
|
|
93
|
-
Pm : complex or ndarray of complex
|
|
94
|
-
Partial derivative dP/dm evaluated at (z,m).
|
|
95
|
-
|
|
96
|
-
Notes
|
|
97
|
-
-----
|
|
98
|
-
For scalar (z,m), this uses Horner evaluation for a_j(z) and then Horner
|
|
99
|
-
in m. For array inputs, it uses precomputed power tables via `_powers` for
|
|
100
|
-
simplicity.
|
|
101
|
-
|
|
102
|
-
Examples
|
|
103
|
-
--------
|
|
104
|
-
.. code-block:: python
|
|
105
|
-
|
|
106
|
-
P, Pz, Pm = eval_P_partials(1.0 + 1j, 0.2 + 0.3j, a_coeffs)
|
|
107
|
-
"""
|
|
108
62
|
|
|
109
|
-
z = numpy.asarray(z, dtype=complex)
|
|
110
|
-
m = numpy.asarray(m, dtype=complex)
|
|
111
63
|
|
|
112
|
-
deg_z = int(a_coeffs.shape[0] - 1)
|
|
113
|
-
s = int(a_coeffs.shape[1] - 1)
|
|
114
|
-
|
|
115
|
-
if (z.ndim == 0) and (m.ndim == 0):
|
|
116
|
-
zz = complex(z)
|
|
117
|
-
mm = complex(m)
|
|
118
|
-
|
|
119
|
-
a = numpy.empty(s + 1, dtype=complex)
|
|
120
|
-
ap = numpy.empty(s + 1, dtype=complex)
|
|
121
|
-
|
|
122
|
-
for j in range(s + 1):
|
|
123
|
-
c = a_coeffs[:, j]
|
|
124
|
-
|
|
125
|
-
val = 0.0 + 0.0j
|
|
126
|
-
for i in range(deg_z, -1, -1):
|
|
127
|
-
val = val * zz + c[i]
|
|
128
|
-
a[j] = val
|
|
129
|
-
|
|
130
|
-
dval = 0.0 + 0.0j
|
|
131
|
-
for i in range(deg_z, 0, -1):
|
|
132
|
-
dval = dval * zz + (i * c[i])
|
|
133
|
-
ap[j] = dval
|
|
134
|
-
|
|
135
|
-
p = a[s]
|
|
136
|
-
pm = 0.0 + 0.0j
|
|
137
|
-
for j in range(s - 1, -1, -1):
|
|
138
|
-
pm = pm * mm + p
|
|
139
|
-
p = p * mm + a[j]
|
|
140
|
-
|
|
141
|
-
pz = ap[s]
|
|
142
|
-
for j in range(s - 1, -1, -1):
|
|
143
|
-
pz = pz * mm + ap[j]
|
|
144
|
-
|
|
145
|
-
return p, pz, pm
|
|
146
|
-
|
|
147
|
-
shp = numpy.broadcast(z, m).shape
|
|
148
|
-
zz = numpy.broadcast_to(z, shp).ravel()
|
|
149
|
-
mm = numpy.broadcast_to(m, shp).ravel()
|
|
150
|
-
|
|
151
|
-
zp = powers(zz, deg_z)
|
|
152
|
-
mp = powers(mm, s)
|
|
153
|
-
|
|
154
|
-
dzp = numpy.zeros_like(zp)
|
|
155
|
-
for i in range(1, deg_z + 1):
|
|
156
|
-
dzp[:, i] = i * zp[:, i - 1]
|
|
157
|
-
|
|
158
|
-
P = numpy.zeros(zz.size, dtype=complex)
|
|
159
|
-
Pz = numpy.zeros(zz.size, dtype=complex)
|
|
160
|
-
Pm = numpy.zeros(zz.size, dtype=complex)
|
|
161
|
-
|
|
162
|
-
for j in range(s + 1):
|
|
163
|
-
aj = zp @ a_coeffs[:, j]
|
|
164
|
-
P += aj * mp[:, j]
|
|
165
|
-
|
|
166
|
-
ajp = dzp @ a_coeffs[:, j]
|
|
167
|
-
Pz += ajp * mp[:, j]
|
|
168
|
-
|
|
169
|
-
if j >= 1:
|
|
170
|
-
Pm += (j * aj) * mp[:, j - 1]
|
|
171
|
-
|
|
172
|
-
return P.reshape(shp), Pz.reshape(shp), Pm.reshape(shp)
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
# ==========
|
|
176
|
-
# fd solve w
|
|
177
|
-
# ==========
|
|
178
64
|
|
|
179
65
|
def fd_solve_w(z, t, a_coeffs, w_init, max_iter=50, tol=1e-12,
|
|
180
66
|
armijo=1e-4, min_lam=1e-6, w_min=1e-14):
|
|
@@ -245,256 +131,140 @@ def fd_solve_w(z, t, a_coeffs, w_init, max_iter=50, tol=1e-12,
|
|
|
245
131
|
want_pos_imag = (z.imag > 0.0)
|
|
246
132
|
|
|
247
133
|
for _ in range(max_iter):
|
|
248
|
-
if not numpy.isfinite(w.real) or not numpy.isfinite(w.imag):
|
|
249
|
-
return w, False
|
|
250
|
-
if abs(w) < w_min:
|
|
251
|
-
return w, False
|
|
252
|
-
if want_pos_imag and (w.imag <= 0.0):
|
|
253
|
-
return w, False
|
|
254
|
-
|
|
255
|
-
zeta = z + alpha / w
|
|
256
|
-
y = tau * w
|
|
257
|
-
|
|
258
|
-
F, Pz, Py = eval_P_partials(zeta, y, a_coeffs)
|
|
259
|
-
F = complex(F)
|
|
260
|
-
Pz = complex(Pz)
|
|
261
|
-
Py = complex(Py)
|
|
262
134
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
135
|
+
a = numpy.asarray(a_coeffs, dtype=numpy.complex128)
|
|
136
|
+
deg_z = a.shape[0] - 1
|
|
137
|
+
deg_m = a.shape[1] - 1
|
|
138
|
+
|
|
139
|
+
beta = tau - 1.0
|
|
140
|
+
|
|
141
|
+
# poly_y[p] stores coeff of y^p after clearing denominators
|
|
142
|
+
poly_y = numpy.zeros(deg_z + deg_m + 1, dtype=numpy.complex128)
|
|
143
|
+
|
|
144
|
+
# Build polynomial: sum_{i,j} a[i,j] (z + beta/y)^i y^j * y^{deg_z}
|
|
145
|
+
# Expand (z + beta/y)^i = sum_{k=0}^i C(i,k) z^{i-k} (beta/y)^k
|
|
146
|
+
# Term contributes to power p = deg_z + j - k.
|
|
147
|
+
from math import comb
|
|
148
|
+
for i in range(deg_z + 1):
|
|
149
|
+
for j in range(deg_m + 1):
|
|
150
|
+
aij = a[i, j]
|
|
151
|
+
if aij == 0:
|
|
152
|
+
continue
|
|
153
|
+
for k in range(i + 1):
|
|
154
|
+
p = deg_z + j - k
|
|
155
|
+
poly_y[p] += aij * comb(i, k) * (z ** (i - k)) * (beta ** k)
|
|
156
|
+
|
|
157
|
+
# numpy.roots expects highest degree first
|
|
158
|
+
coeffs = poly_y[::-1]
|
|
159
|
+
|
|
160
|
+
# If leading coefficients are ~0, trim (rare but safe)
|
|
161
|
+
nz_lead = numpy.flatnonzero(numpy.abs(coeffs) > 0)
|
|
162
|
+
if nz_lead.size == 0:
|
|
268
163
|
return w, False
|
|
164
|
+
coeffs = coeffs[nz_lead[0]:]
|
|
269
165
|
|
|
270
|
-
|
|
166
|
+
roots_y = numpy.roots(coeffs)
|
|
271
167
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
168
|
+
# Pick root with Im(w)>0 (if z in upper half-plane), closest to time seed
|
|
169
|
+
y_seed = tau * w_init
|
|
170
|
+
best = None
|
|
171
|
+
best_score = None
|
|
275
172
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
if abs(w_new) < w_min:
|
|
279
|
-
lam *= 0.5
|
|
280
|
-
continue
|
|
281
|
-
if want_pos_imag and (w_new.imag <= 0.0):
|
|
282
|
-
lam *= 0.5
|
|
173
|
+
for y in roots_y:
|
|
174
|
+
if not numpy.isfinite(y.real) or not numpy.isfinite(y.imag):
|
|
283
175
|
continue
|
|
284
176
|
|
|
285
|
-
|
|
286
|
-
y_new = tau * w_new
|
|
177
|
+
w_cand = y / tau
|
|
287
178
|
|
|
288
|
-
|
|
289
|
-
|
|
179
|
+
if want_pos_imag and (w_cand.imag <= 0.0):
|
|
180
|
+
continue
|
|
290
181
|
|
|
291
|
-
if abs(
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
182
|
+
if abs(w_cand) < w_min:
|
|
183
|
+
continue
|
|
184
|
+
|
|
185
|
+
# score: stick to time continuation
|
|
186
|
+
score = abs(y - y_seed)
|
|
295
187
|
|
|
296
|
-
|
|
188
|
+
if (best_score is None) or (score < best_score):
|
|
189
|
+
best = w_cand
|
|
190
|
+
best_score = score
|
|
297
191
|
|
|
298
|
-
if
|
|
192
|
+
if best is None:
|
|
299
193
|
return w, False
|
|
300
194
|
|
|
301
|
-
|
|
302
|
-
return w, (abs(F_end) <= 10.0 * tol)
|
|
195
|
+
w = complex(best)
|
|
303
196
|
|
|
197
|
+
# final residual check
|
|
198
|
+
F_end = eval_P_partials(z + alpha / w, tau * w, a_coeffs)[0]
|
|
199
|
+
return w, (abs(F_end) <= 1e3 * tol)
|
|
304
200
|
|
|
305
|
-
#
|
|
306
|
-
# decompress newton old
|
|
307
|
-
# =====================
|
|
201
|
+
# -------------------
|
|
308
202
|
|
|
309
|
-
def decompress_newton_old(z_list, t_grid, a_coeffs, w0_list=None,
|
|
310
|
-
dt_max=0.1, sweep=True, time_rel_tol=5.0,
|
|
311
|
-
max_iter=50, tol=1e-12, armijo=1e-4,
|
|
312
|
-
min_lam=1e-6, w_min=1e-14):
|
|
313
|
-
"""
|
|
314
|
-
Evolve w = m(t,z) on a fixed z grid and time grid using FD.
|
|
315
203
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
z_list : array_like of complex
|
|
319
|
-
Query points z (typically x + 1j*eta with eta > 0).
|
|
320
|
-
t_grid : array_like of float
|
|
321
|
-
Strictly increasing time grid.
|
|
322
|
-
a_coeffs : ndarray
|
|
323
|
-
Coefficients defining P(zeta,y) in the monomial basis used by eval_P.
|
|
324
|
-
w0_list : array_like of complex
|
|
325
|
-
Initial values at t_grid[0] (typically m0(z_list) on the physical
|
|
326
|
-
branch).
|
|
327
|
-
dt_max : float, optional
|
|
328
|
-
Maximum internal time step. Larger dt is handled by substepping.
|
|
329
|
-
sweep : bool, optional
|
|
330
|
-
If True, use spatial continuation (neighbor seeding) plus a
|
|
331
|
-
time-consistency check to prevent branch collapse. If False, solve
|
|
332
|
-
each z independently from the previous-time seed (faster but may
|
|
333
|
-
branch-switch for small eta).
|
|
334
|
-
time_rel_tol : float, optional
|
|
335
|
-
When sweep=True, if the neighbor-seeded solution differs from the
|
|
336
|
-
previous-time value w_prev by more than time_rel_tol*(1+|w_prev|), we
|
|
337
|
-
also solve using the previous-time seed and select the closer one.
|
|
338
|
-
max_iter : int, optional
|
|
339
|
-
Maximum Newton iterations in fd_solve_w.
|
|
340
|
-
tol : float, optional
|
|
341
|
-
Residual tolerance in fd_solve_w.
|
|
342
|
-
armijo : float, optional
|
|
343
|
-
Armijo parameter for backtracking in fd_solve_w.
|
|
344
|
-
min_lam : float, optional
|
|
345
|
-
Minimum damping factor in fd_solve_w backtracking.
|
|
346
|
-
w_min : float, optional
|
|
347
|
-
Minimum |w| allowed to avoid singularity.
|
|
348
|
-
|
|
349
|
-
Returns
|
|
350
|
-
-------
|
|
351
|
-
W : ndarray, shape (len(t_grid), len(z_list))
|
|
352
|
-
Evolved values w(t,z).
|
|
353
|
-
ok : ndarray of bool, same shape as W
|
|
354
|
-
Convergence flags from the final accepted solve at each point.
|
|
355
|
-
|
|
356
|
-
Notes
|
|
357
|
-
-----
|
|
358
|
-
For very small eta, the implicit FD equation can have multiple roots in the
|
|
359
|
-
upper half-plane. The sweep option is a branch-selection mechanism. The
|
|
360
|
-
time-consistency check is critical at large t to avoid propagating a
|
|
361
|
-
nearly-real spurious root across x.
|
|
204
|
+
F_end = eval_P_partials(z + alpha / w, tau * w, a_coeffs)[0]
|
|
205
|
+
return w, (abs(F_end) <= 10.0 * tol)
|
|
362
206
|
|
|
363
|
-
Examples
|
|
364
|
-
--------
|
|
365
|
-
.. code-block:: python
|
|
366
207
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
w0_list = m1_fn(z_query)
|
|
208
|
+
# ============
|
|
209
|
+
# NEW FUNCTION
|
|
210
|
+
# ============
|
|
371
211
|
|
|
372
|
-
|
|
373
|
-
W, ok = fd_evolve_on_grid(
|
|
374
|
-
z_query, t_grid, a_coeffs, w0_list=w0_list,
|
|
375
|
-
dt_max=0.1, sweep=True, time_rel_tol=5.0,
|
|
376
|
-
max_iter=50, tol=1e-12, armijo=1e-4, min_lam=1e-6, w_min=1e-14
|
|
377
|
-
)
|
|
378
|
-
rho = W.imag / numpy.pi
|
|
212
|
+
def fd_candidates_w(z, t, a_coeffs, w_min=1e-14):
|
|
379
213
|
"""
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
ok = numpy.zeros((nt, nz), dtype=bool)
|
|
387
|
-
|
|
388
|
-
if w0_list is None:
|
|
389
|
-
raise ValueError(
|
|
390
|
-
"w0_list must be provided (e.g. m1_fn(z_list) at t=0).")
|
|
391
|
-
w_prev = numpy.asarray(w0_list, dtype=complex).ravel()
|
|
392
|
-
if w_prev.size != nz:
|
|
393
|
-
raise ValueError("w0_list must have same size as z_list.")
|
|
394
|
-
|
|
395
|
-
W[0, :] = w_prev
|
|
396
|
-
ok[0, :] = True
|
|
397
|
-
|
|
398
|
-
sweep = bool(sweep)
|
|
399
|
-
time_rel_tol = float(time_rel_tol)
|
|
400
|
-
|
|
401
|
-
for it in range(1, nt):
|
|
402
|
-
t0 = float(t_grid[it - 1])
|
|
403
|
-
t1 = float(t_grid[it])
|
|
404
|
-
dt = t1 - t0
|
|
405
|
-
if dt <= 0.0:
|
|
406
|
-
raise ValueError("t_grid must be strictly increasing.")
|
|
214
|
+
Return candidate roots w solving P(z + alpha/w, tau*w)=0 with Im(w)>0 (if Im(z)>0).
|
|
215
|
+
"""
|
|
216
|
+
z = complex(z)
|
|
217
|
+
tau = float(numpy.exp(t))
|
|
218
|
+
alpha = 1.0 - 1.0 / tau
|
|
219
|
+
want_pos_imag = (z.imag > 0.0)
|
|
407
220
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
n_sub = 1
|
|
221
|
+
a = numpy.asarray(a_coeffs, dtype=numpy.complex128)
|
|
222
|
+
deg_z = a.shape[0] - 1
|
|
223
|
+
deg_m = a.shape[1] - 1
|
|
412
224
|
|
|
413
|
-
|
|
414
|
-
t = t0 + dt * (ks / float(n_sub))
|
|
225
|
+
beta = tau - 1.0 # since alpha/w = (tau-1)/(tau*w) = beta / y with y=tau*w
|
|
415
226
|
|
|
416
|
-
|
|
417
|
-
ok_row = numpy.zeros(nz, dtype=bool)
|
|
227
|
+
poly_y = numpy.zeros(deg_z + deg_m + 1, dtype=numpy.complex128)
|
|
418
228
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
max_iter=max_iter, tol=tol, armijo=armijo,
|
|
425
|
-
min_lam=min_lam, w_min=w_min
|
|
426
|
-
)
|
|
427
|
-
w_row[iz] = w
|
|
428
|
-
ok_row[iz] = success
|
|
429
|
-
|
|
430
|
-
w_prev = w_row
|
|
229
|
+
from math import comb
|
|
230
|
+
for i in range(deg_z + 1):
|
|
231
|
+
for j in range(deg_m + 1):
|
|
232
|
+
aij = a[i, j]
|
|
233
|
+
if aij == 0:
|
|
431
234
|
continue
|
|
235
|
+
for k in range(i + 1):
|
|
236
|
+
p = deg_z + j - k
|
|
237
|
+
poly_y[p] += aij * comb(i, k) * (z ** (i - k)) * (beta ** k)
|
|
238
|
+
|
|
239
|
+
coeffs = poly_y[::-1]
|
|
240
|
+
nz_lead = numpy.flatnonzero(numpy.abs(coeffs) > 0)
|
|
241
|
+
if nz_lead.size == 0:
|
|
242
|
+
return []
|
|
243
|
+
|
|
244
|
+
coeffs = coeffs[nz_lead[0]:]
|
|
245
|
+
roots_y = numpy.roots(coeffs)
|
|
246
|
+
|
|
247
|
+
cands = []
|
|
248
|
+
for y in roots_y:
|
|
249
|
+
if not numpy.isfinite(y.real) or not numpy.isfinite(y.imag):
|
|
250
|
+
continue
|
|
251
|
+
w = y / tau
|
|
252
|
+
if abs(w) < w_min:
|
|
253
|
+
continue
|
|
254
|
+
if want_pos_imag and (w.imag <= 0.0):
|
|
255
|
+
continue
|
|
256
|
+
# residual filter (optional but helps)
|
|
257
|
+
# -------------
|
|
258
|
+
# TEST
|
|
259
|
+
# F = eval_P_partials(z + alpha / w, tau * w, a_coeffs)[0]
|
|
260
|
+
# if abs(F) < 1e-6:
|
|
261
|
+
# cands.append(complex(w))
|
|
262
|
+
# ---------------
|
|
263
|
+
# TEST
|
|
264
|
+
cands.append(complex(w))
|
|
265
|
+
# ------------------
|
|
432
266
|
|
|
433
|
-
|
|
434
|
-
i0 = int(numpy.argmax(numpy.abs(numpy.imag(w_prev))))
|
|
435
|
-
|
|
436
|
-
w0, ok0 = fd_solve_w(
|
|
437
|
-
z_list[i0], t, a_coeffs, w_prev[i0],
|
|
438
|
-
max_iter=max_iter, tol=tol, armijo=armijo,
|
|
439
|
-
min_lam=min_lam, w_min=w_min
|
|
440
|
-
)
|
|
441
|
-
w_row[i0] = w0
|
|
442
|
-
ok_row[i0] = ok0
|
|
443
|
-
|
|
444
|
-
def solve_with_choice(iz, w_neighbor):
|
|
445
|
-
# First try neighbor-seeded Newton (spatial continuity).
|
|
446
|
-
w_a, ok_a = fd_solve_w(
|
|
447
|
-
z_list[iz], t, a_coeffs, w_neighbor,
|
|
448
|
-
max_iter=max_iter, tol=tol, armijo=armijo,
|
|
449
|
-
min_lam=min_lam, w_min=w_min
|
|
450
|
-
)
|
|
451
|
-
|
|
452
|
-
# Always keep a time-consistent fallback candidate.
|
|
453
|
-
w_b, ok_b = fd_solve_w(
|
|
454
|
-
z_list[iz], t, a_coeffs, w_prev[iz],
|
|
455
|
-
max_iter=max_iter, tol=tol, armijo=armijo,
|
|
456
|
-
min_lam=min_lam, w_min=w_min
|
|
457
|
-
)
|
|
458
|
-
|
|
459
|
-
if ok_a and ok_b:
|
|
460
|
-
# Prefer the root closer to previous-time value (time
|
|
461
|
-
# continuation).
|
|
462
|
-
da = abs(w_a - w_prev[iz])
|
|
463
|
-
db = abs(w_b - w_prev[iz])
|
|
464
|
-
|
|
465
|
-
# If neighbor result is wildly off, reject it.
|
|
466
|
-
if da > time_rel_tol * (1.0 + abs(w_prev[iz])):
|
|
467
|
-
return w_b, True
|
|
468
|
-
|
|
469
|
-
return (w_a, True) if (da <= db) else (w_b, True)
|
|
470
|
-
|
|
471
|
-
if ok_a:
|
|
472
|
-
# If only neighbor succeeded, still guard against extreme
|
|
473
|
-
# drift.
|
|
474
|
-
da = abs(w_a - w_prev[iz])
|
|
475
|
-
if da > time_rel_tol * (1.0 + abs(w_prev[iz])) and ok_b:
|
|
476
|
-
return w_b, True
|
|
477
|
-
return w_a, True
|
|
478
|
-
|
|
479
|
-
if ok_b:
|
|
480
|
-
return w_b, True
|
|
481
|
-
|
|
482
|
-
return w_a, False
|
|
483
|
-
|
|
484
|
-
# Sweep right
|
|
485
|
-
for iz in range(i0 + 1, nz):
|
|
486
|
-
w_row[iz], ok_row[iz] = solve_with_choice(iz, w_row[iz - 1])
|
|
487
|
-
|
|
488
|
-
# Sweep left
|
|
489
|
-
for iz in range(i0 - 1, -1, -1):
|
|
490
|
-
w_row[iz], ok_row[iz] = solve_with_choice(iz, w_row[iz + 1])
|
|
491
|
-
|
|
492
|
-
w_prev = w_row
|
|
493
|
-
|
|
494
|
-
W[it, :] = w_prev
|
|
495
|
-
ok[it, :] = ok_row
|
|
496
|
-
|
|
497
|
-
return W, ok
|
|
267
|
+
return cands
|
|
498
268
|
|
|
499
269
|
|
|
500
270
|
# =================
|
|
@@ -505,7 +275,7 @@ def decompress_newton(z_list, t_grid, a_coeffs, w0_list=None,
|
|
|
505
275
|
dt_max=0.1, sweep=True, time_rel_tol=5.0,
|
|
506
276
|
active_imag_eps=None, sweep_pad=20,
|
|
507
277
|
max_iter=50, tol=1e-12, armijo=1e-4,
|
|
508
|
-
min_lam=1e-6, w_min=1e-14):
|
|
278
|
+
min_lam=1e-6, w_min=1e-14, min_n_time=None):
|
|
509
279
|
"""
|
|
510
280
|
Evolve w = m(t,z) on a fixed z grid and time grid using FD.
|
|
511
281
|
|
|
@@ -576,41 +346,88 @@ def decompress_newton(z_list, t_grid, a_coeffs, w0_list=None,
|
|
|
576
346
|
active_imag_eps = 50.0 * eta0 if eta0 > 0.0 else 1e-10
|
|
577
347
|
active_imag_eps = float(active_imag_eps)
|
|
578
348
|
|
|
349
|
+
# --------------------------------------
|
|
350
|
+
# TEST
|
|
351
|
+
# def solve_with_choice(iz, w_seed):
|
|
352
|
+
# # Neighbor-seeded candidate (spatial continuity)
|
|
353
|
+
# w_a, ok_a = fd_solve_w(
|
|
354
|
+
# z_list[iz], t, a_coeffs, w_seed,
|
|
355
|
+
# max_iter=max_iter, tol=tol, armijo=armijo,
|
|
356
|
+
# min_lam=min_lam, w_min=w_min
|
|
357
|
+
# )
|
|
358
|
+
#
|
|
359
|
+
# # Time-seeded candidate (time continuation)
|
|
360
|
+
# w_b, ok_b = fd_solve_w(
|
|
361
|
+
# z_list[iz], t, a_coeffs, w_prev[iz],
|
|
362
|
+
# max_iter=max_iter, tol=tol, armijo=armijo,
|
|
363
|
+
# min_lam=min_lam, w_min=w_min
|
|
364
|
+
# )
|
|
365
|
+
#
|
|
366
|
+
# if ok_a and ok_b:
|
|
367
|
+
# da = abs(w_a - w_prev[iz])
|
|
368
|
+
# db = abs(w_b - w_prev[iz])
|
|
369
|
+
#
|
|
370
|
+
# # Reject neighbor result if it drifted too far in one step
|
|
371
|
+
# if da > time_rel_tol * (1.0 + abs(w_prev[iz])):
|
|
372
|
+
# return w_b, True
|
|
373
|
+
#
|
|
374
|
+
# return (w_a, True) if (da <= db) else (w_b, True)
|
|
375
|
+
#
|
|
376
|
+
# if ok_a:
|
|
377
|
+
# da = abs(w_a - w_prev[iz])
|
|
378
|
+
# if da > time_rel_tol * (1.0 + abs(w_prev[iz])) and ok_b:
|
|
379
|
+
# return w_b, True
|
|
380
|
+
# return w_a, True
|
|
381
|
+
#
|
|
382
|
+
# if ok_b:
|
|
383
|
+
# return w_b, True
|
|
384
|
+
#
|
|
385
|
+
# return w_a, False
|
|
386
|
+
# ----------------------------------------
|
|
387
|
+
# TEST
|
|
579
388
|
def solve_with_choice(iz, w_seed):
|
|
580
|
-
#
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
)
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
389
|
+
# candidate roots at this (t,z)
|
|
390
|
+
cands = fd_candidates_w(z_list[iz], t, a_coeffs, w_min=w_min)
|
|
391
|
+
|
|
392
|
+
# ---------------------
|
|
393
|
+
# TEST
|
|
394
|
+
if iz in (0, nz//2, nz-1):
|
|
395
|
+
ims = [float(w.imag) for w in cands]
|
|
396
|
+
print(f" iz={iz} ncand={len(cands)} Im(cands) min/med/max="
|
|
397
|
+
f"{(min(ims) if ims else None)}/"
|
|
398
|
+
f"{(numpy.median(ims) if ims else None)}/"
|
|
399
|
+
f"{(max(ims) if ims else None)}")
|
|
400
|
+
# ---------------------
|
|
401
|
+
|
|
402
|
+
if len(cands) == 0:
|
|
403
|
+
# fallback to your existing single-root solver
|
|
404
|
+
w, success = fd_solve_w(
|
|
405
|
+
z_list[iz], t, a_coeffs, w_prev[iz],
|
|
406
|
+
max_iter=max_iter, tol=tol, armijo=armijo,
|
|
407
|
+
min_lam=min_lam, w_min=w_min
|
|
408
|
+
)
|
|
409
|
+
return w, success
|
|
601
410
|
|
|
602
|
-
|
|
411
|
+
# cost = spatial continuity + time continuity (tune weights if needed)
|
|
412
|
+
w_time = w_prev[iz]
|
|
413
|
+
w_space = w_seed
|
|
414
|
+
best = None
|
|
415
|
+
best_cost = None
|
|
603
416
|
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
417
|
+
for w in cands:
|
|
418
|
+
# TEST
|
|
419
|
+
# cost = abs(w - w_space) + 0.25 * abs(w - w_time)
|
|
420
|
+
# TEST
|
|
421
|
+
# prefer continuity, but also prefer larger Im(w) to stay on the bulk branch
|
|
422
|
+
cost = abs(w - w_space) + 0.25 * abs(w - w_time) - 5.0 * w.imag
|
|
423
|
+
# --------------
|
|
609
424
|
|
|
610
|
-
|
|
611
|
-
|
|
425
|
+
if (best_cost is None) or (cost < best_cost):
|
|
426
|
+
best = w
|
|
427
|
+
best_cost = cost
|
|
612
428
|
|
|
613
|
-
return
|
|
429
|
+
return best, True
|
|
430
|
+
# ----------------------------------------
|
|
614
431
|
|
|
615
432
|
for it in range(1, nt):
|
|
616
433
|
t0 = float(t_grid[it - 1])
|
|
@@ -648,15 +465,107 @@ def decompress_newton(z_list, t_grid, a_coeffs, w0_list=None,
|
|
|
648
465
|
# Define "active" region from previous time: inside bulks
|
|
649
466
|
# Im(w_prev) is O(1), outside bulks Im(w_prev) is ~O(eta). Dilate
|
|
650
467
|
# by sweep_pad to allow edges to move.
|
|
468
|
+
|
|
469
|
+
# ------------------------------
|
|
470
|
+
# TEST
|
|
471
|
+
# active = (numpy.abs(numpy.imag(w_prev)) > active_imag_eps)
|
|
472
|
+
# active_pad = active.copy()
|
|
473
|
+
# if sweep_pad > 0 and numpy.any(active):
|
|
474
|
+
# idx = numpy.flatnonzero(active)
|
|
475
|
+
# for i in idx:
|
|
476
|
+
# lo = 0 if (i - sweep_pad) < 0 else (i - sweep_pad)
|
|
477
|
+
# hi = \
|
|
478
|
+
# nz if (i + sweep_pad + 1) > nz else (i + sweep_pad + 1)
|
|
479
|
+
# active_pad[lo:hi] = True
|
|
480
|
+
# ------------------------------
|
|
481
|
+
# TEST
|
|
651
482
|
active = (numpy.abs(numpy.imag(w_prev)) > active_imag_eps)
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
483
|
+
|
|
484
|
+
# Split active indices into contiguous blocks (bulks)
|
|
485
|
+
pad_label = -numpy.ones(nz, dtype=numpy.int64) # bulk id per index
|
|
486
|
+
active_pad = numpy.zeros(nz, dtype=bool)
|
|
487
|
+
|
|
488
|
+
idx = numpy.flatnonzero(active)
|
|
489
|
+
if idx.size > 0:
|
|
490
|
+
cuts = numpy.where(numpy.diff(idx) > 1)[0]
|
|
491
|
+
blocks = numpy.split(idx, cuts + 1)
|
|
492
|
+
|
|
493
|
+
# Build padded intervals + centers
|
|
494
|
+
centers = []
|
|
495
|
+
pads = []
|
|
496
|
+
for b in blocks:
|
|
497
|
+
centers.append(int((b[0] + b[-1]) // 2))
|
|
498
|
+
lo = int(max(0, b[0] - sweep_pad))
|
|
499
|
+
hi = int(min(nz - 1, b[-1] + sweep_pad))
|
|
500
|
+
pads.append((lo, hi))
|
|
501
|
+
|
|
502
|
+
# Union of padded regions
|
|
503
|
+
for lo, hi in pads:
|
|
504
|
+
active_pad[lo:hi + 1] = True
|
|
505
|
+
|
|
506
|
+
# Assign each padded index to the nearest bulk center (no overlap label)
|
|
507
|
+
idx_u = numpy.flatnonzero(active_pad)
|
|
508
|
+
c = numpy.asarray(centers, dtype=numpy.int64)
|
|
509
|
+
dist = numpy.abs(idx_u[:, None] - c[None, :])
|
|
510
|
+
winner = numpy.argmin(dist, axis=1).astype(numpy.int64)
|
|
511
|
+
pad_label[idx_u] = winner
|
|
512
|
+
# ------------------------------
|
|
513
|
+
|
|
514
|
+
# ------------------------------
|
|
515
|
+
# TEST
|
|
516
|
+
def _ranges(idxs):
|
|
517
|
+
if idxs.size == 0:
|
|
518
|
+
return []
|
|
519
|
+
cuts = numpy.where(numpy.diff(idxs) > 1)[0]
|
|
520
|
+
blocks = numpy.split(idxs, cuts + 1)
|
|
521
|
+
return [(int(b[0]), int(b[-1])) for b in blocks]
|
|
522
|
+
|
|
523
|
+
print(" pad_label>=0 ranges:", _ranges(numpy.flatnonzero(pad_label >= 0)))
|
|
524
|
+
print(" overlap(-2) ranges:", _ranges(numpy.flatnonzero(pad_label == -2)))
|
|
525
|
+
# ------------------------------
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
# ----------------------------------------------
|
|
530
|
+
|
|
531
|
+
# TEST
|
|
532
|
+
# eta = float(abs(z_list[0].imag))
|
|
533
|
+
#
|
|
534
|
+
# # Barrier: points that look like "gap" (tiny Im(w_prev))
|
|
535
|
+
# barrier_eps = 10.0 * eta # try 5*eta or 10*eta
|
|
536
|
+
# barrier = (numpy.abs(numpy.imag(w_prev)) <= barrier_eps)
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
# TEST
|
|
541
|
+
# -------------------------
|
|
542
|
+
# --- diagnostics ---
|
|
543
|
+
active = (numpy.abs(numpy.imag(w_prev)) > active_imag_eps)
|
|
544
|
+
idx_active = numpy.flatnonzero(active)
|
|
545
|
+
idx_pad = numpy.flatnonzero(active_pad)
|
|
546
|
+
|
|
547
|
+
def _ranges(idxs):
|
|
548
|
+
if idxs.size == 0:
|
|
549
|
+
return []
|
|
550
|
+
cuts = numpy.where(numpy.diff(idxs) > 1)[0]
|
|
551
|
+
blocks = numpy.split(idxs, cuts + 1)
|
|
552
|
+
return [(b[0], b[-1]) for b in blocks]
|
|
553
|
+
|
|
554
|
+
print(f"[t={t:.6g}] eta={z_list[0].imag:.2e} active_eps={active_imag_eps:.2e} "
|
|
555
|
+
f"active_n={idx_active.size}/{nz} pad_n={idx_pad.size}/{nz} "
|
|
556
|
+
f"active_ranges={_ranges(idx_active)} pad_ranges={_ranges(idx_pad)}")
|
|
557
|
+
|
|
558
|
+
# Track the physical “gap” region around between bulks by looking at low Im(w_prev)
|
|
559
|
+
gap = numpy.abs(numpy.imag(w_prev)) <= 5.0 * z_list[0].imag
|
|
560
|
+
ig = numpy.flatnonzero(gap)
|
|
561
|
+
if ig.size > 0:
|
|
562
|
+
print(f" gap_ranges(Im<=5eta)={_ranges(ig)} "
|
|
563
|
+
f"Im(w) min/med/max = {numpy.min(w_prev.imag):.3e}/"
|
|
564
|
+
f"{numpy.median(w_prev.imag):.3e}/{numpy.max(w_prev.imag):.3e}")
|
|
565
|
+
|
|
566
|
+
# ------------------
|
|
567
|
+
|
|
568
|
+
|
|
660
569
|
|
|
661
570
|
# Left-to-right: use neighbor seed only within padded active
|
|
662
571
|
# regions, so we don't propagate a branch across the gap between
|
|
@@ -665,16 +574,51 @@ def decompress_newton(z_list, t_grid, a_coeffs, w0_list=None,
|
|
|
665
574
|
if iz == 0:
|
|
666
575
|
w_seed = w_prev[iz]
|
|
667
576
|
else:
|
|
668
|
-
|
|
577
|
+
# TEST
|
|
578
|
+
# if active_pad[iz] and active_pad[iz - 1]:
|
|
579
|
+
# w_seed = w_row[iz - 1]
|
|
580
|
+
# else:
|
|
581
|
+
# w_seed = w_prev[iz]
|
|
582
|
+
# TEST
|
|
583
|
+
# if (active_pad[iz] and active_pad[iz - 1] and
|
|
584
|
+
# (not barrier[iz]) and (not barrier[iz - 1])):
|
|
585
|
+
# w_seed = w_row[iz - 1]
|
|
586
|
+
# else:
|
|
587
|
+
# w_seed = w_prev[iz]
|
|
588
|
+
# ----------------------
|
|
589
|
+
# TEST
|
|
590
|
+
# if (active_pad[iz] and active_pad[iz - 1] and
|
|
591
|
+
# (pad_label[iz] == pad_label[iz - 1]) and
|
|
592
|
+
# (pad_label[iz] >= 0)):
|
|
593
|
+
# -----------------
|
|
594
|
+
# TEST
|
|
595
|
+
if (active_pad[iz] and active_pad[iz - 1] and
|
|
596
|
+
(pad_label[iz] == pad_label[iz - 1]) and
|
|
597
|
+
(pad_label[iz] >= 0)):
|
|
669
598
|
w_seed = w_row[iz - 1]
|
|
670
599
|
else:
|
|
671
600
|
w_seed = w_prev[iz]
|
|
601
|
+
# ----------------------
|
|
602
|
+
|
|
672
603
|
|
|
673
604
|
w_row[iz], ok_row[iz] = solve_with_choice(iz, w_seed)
|
|
674
605
|
|
|
675
606
|
# Right-to-left refinement: helps stabilize left edges of bulks.
|
|
676
607
|
for iz in range(nz - 2, -1, -1):
|
|
677
|
-
|
|
608
|
+
# TEST
|
|
609
|
+
# if active_pad[iz] and active_pad[iz + 1]:
|
|
610
|
+
# TEST
|
|
611
|
+
# if (active_pad[iz] and active_pad[iz + 1] and
|
|
612
|
+
# (not barrier[iz]) and (not barrier[iz + 1])):
|
|
613
|
+
# TEST
|
|
614
|
+
# if (active_pad[iz] and active_pad[iz + 1] and
|
|
615
|
+
# (pad_label[iz] == pad_label[iz + 1]) and
|
|
616
|
+
# (pad_label[iz] >= 0)):
|
|
617
|
+
# TEST
|
|
618
|
+
if (active_pad[iz] and active_pad[iz + 1] and
|
|
619
|
+
(pad_label[iz] == pad_label[iz + 1]) and
|
|
620
|
+
(pad_label[iz] >= 0)):
|
|
621
|
+
|
|
678
622
|
w_seed = w_row[iz + 1]
|
|
679
623
|
w_new, ok_new = solve_with_choice(iz, w_seed)
|
|
680
624
|
if ok_new:
|
|
@@ -684,6 +628,11 @@ def decompress_newton(z_list, t_grid, a_coeffs, w0_list=None,
|
|
|
684
628
|
w_row[iz] = w_new
|
|
685
629
|
ok_row[iz] = True
|
|
686
630
|
|
|
631
|
+
|
|
632
|
+
|
|
633
|
+
# TEST
|
|
634
|
+
print(f'solved_ok={ok_row.sum()}/{nz} (this substep)')
|
|
635
|
+
|
|
687
636
|
w_prev = w_row
|
|
688
637
|
|
|
689
638
|
W[it, :] = w_prev
|