freealg 0.7.9__py3-none-any.whl → 0.7.11__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/_branch_points.py +288 -0
- freealg/_algebraic_form/_continuation_algebraic.py +1 -1
- freealg/_algebraic_form/_decompress.py +45 -1
- freealg/_algebraic_form/_homotopy.py +5 -5
- freealg/_algebraic_form/_moments.py +43 -25
- freealg/_algebraic_form/_support.py +309 -0
- freealg/_algebraic_form/algebraic_form.py +120 -46
- {freealg-0.7.9.dist-info → freealg-0.7.11.dist-info}/METADATA +1 -1
- {freealg-0.7.9.dist-info → freealg-0.7.11.dist-info}/RECORD +14 -13
- freealg/_algebraic_form/_discriminant.py +0 -226
- {freealg-0.7.9.dist-info → freealg-0.7.11.dist-info}/WHEEL +0 -0
- {freealg-0.7.9.dist-info → freealg-0.7.11.dist-info}/licenses/AUTHORS.txt +0 -0
- {freealg-0.7.9.dist-info → freealg-0.7.11.dist-info}/licenses/LICENSE.txt +0 -0
- {freealg-0.7.9.dist-info → freealg-0.7.11.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright 2026, Siavash Ameli <sameli@berkeley.edu>
|
|
2
|
+
# SPDX-License-Identifier: BSD-3-Clause
|
|
3
|
+
# SPDX-FileType: SOURCE
|
|
4
|
+
#
|
|
5
|
+
# This program is free software: you can redistribute it and/or modify it under
|
|
6
|
+
# the terms of the license found in the LICENSE.txt file in the root directory
|
|
7
|
+
# of this source tree.
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# =======
|
|
11
|
+
# Imports
|
|
12
|
+
# =======
|
|
13
|
+
|
|
14
|
+
import numpy
|
|
15
|
+
import numpy.polynomial.polynomial as poly
|
|
16
|
+
|
|
17
|
+
__all__ = ['compute_support']
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# ======================
|
|
21
|
+
# poly coeffs in m and z
|
|
22
|
+
# ======================
|
|
23
|
+
|
|
24
|
+
def _poly_coeffs_in_m_at_z(a_coeffs, z):
|
|
25
|
+
|
|
26
|
+
s = a_coeffs.shape[1] - 1
|
|
27
|
+
a = numpy.empty(s + 1, dtype=numpy.complex128)
|
|
28
|
+
for j in range(s + 1):
|
|
29
|
+
a[j] = poly.polyval(z, a_coeffs[:, j])
|
|
30
|
+
return a
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# ===============
|
|
34
|
+
# roots poly in m
|
|
35
|
+
# ===============
|
|
36
|
+
|
|
37
|
+
def _roots_poly_in_m(c_asc, tol=0.0):
|
|
38
|
+
|
|
39
|
+
c = numpy.asarray(c_asc, dtype=numpy.complex128).ravel()
|
|
40
|
+
if c.size <= 1:
|
|
41
|
+
return numpy.array([], dtype=numpy.complex128)
|
|
42
|
+
|
|
43
|
+
k = c.size - 1
|
|
44
|
+
while k > 0 and abs(c[k]) <= tol:
|
|
45
|
+
k -= 1
|
|
46
|
+
c = c[:k + 1]
|
|
47
|
+
if c.size <= 1:
|
|
48
|
+
return numpy.array([], dtype=numpy.complex128)
|
|
49
|
+
|
|
50
|
+
return numpy.roots(c[::-1])
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# ================
|
|
54
|
+
# dPdm coeffs at z
|
|
55
|
+
# ================
|
|
56
|
+
|
|
57
|
+
def _dPdm_coeffs_at_z(a_coeffs, z):
|
|
58
|
+
|
|
59
|
+
a = _poly_coeffs_in_m_at_z(a_coeffs, z)
|
|
60
|
+
s = a.size - 1
|
|
61
|
+
if s <= 0:
|
|
62
|
+
return numpy.array([0.0 + 0.0j], dtype=numpy.complex128)
|
|
63
|
+
d = numpy.empty(s, dtype=numpy.complex128)
|
|
64
|
+
for j in range(1, s + 1):
|
|
65
|
+
d[j - 1] = j * a[j]
|
|
66
|
+
return d
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# ==============
|
|
70
|
+
# P and partials
|
|
71
|
+
# ==============
|
|
72
|
+
|
|
73
|
+
def _P_and_partials(a_coeffs, z, m):
|
|
74
|
+
|
|
75
|
+
s = a_coeffs.shape[1] - 1
|
|
76
|
+
|
|
77
|
+
a = numpy.empty(s + 1, dtype=numpy.complex128)
|
|
78
|
+
da = numpy.empty(s + 1, dtype=numpy.complex128)
|
|
79
|
+
for j in range(s + 1):
|
|
80
|
+
a[j] = poly.polyval(z, a_coeffs[:, j])
|
|
81
|
+
da[j] = poly.polyval(z, poly.polyder(a_coeffs[:, j]))
|
|
82
|
+
|
|
83
|
+
mpow = 1.0 + 0.0j
|
|
84
|
+
P = 0.0 + 0.0j
|
|
85
|
+
Pz = 0.0 + 0.0j
|
|
86
|
+
for j in range(s + 1):
|
|
87
|
+
P += a[j] * mpow
|
|
88
|
+
Pz += da[j] * mpow
|
|
89
|
+
mpow *= m
|
|
90
|
+
|
|
91
|
+
Pm = 0.0 + 0.0j
|
|
92
|
+
Pmm = 0.0 + 0.0j
|
|
93
|
+
Pzm = 0.0 + 0.0j
|
|
94
|
+
for j in range(1, s + 1):
|
|
95
|
+
Pm += j * a[j] * (m ** (j - 1))
|
|
96
|
+
Pzm += j * da[j] * (m ** (j - 1))
|
|
97
|
+
for j in range(2, s + 1):
|
|
98
|
+
Pmm += j * (j - 1) * a[j] * (m ** (j - 2))
|
|
99
|
+
|
|
100
|
+
return P, Pz, Pm, Pzm, Pmm, a
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
# ===========
|
|
104
|
+
# newton edge
|
|
105
|
+
# ===========
|
|
106
|
+
|
|
107
|
+
def _newton_edge(a_coeffs, x0, m0, tol=1e-12, max_iter=50):
|
|
108
|
+
|
|
109
|
+
x = float(x0)
|
|
110
|
+
m = float(m0)
|
|
111
|
+
|
|
112
|
+
for _ in range(max_iter):
|
|
113
|
+
z = x + 0.0j
|
|
114
|
+
P, Pz, Pm, Pzm, Pmm, _ = _P_and_partials(a_coeffs, z, m)
|
|
115
|
+
|
|
116
|
+
f0 = float(numpy.real(P))
|
|
117
|
+
f1 = float(numpy.real(Pm))
|
|
118
|
+
|
|
119
|
+
j00 = float(numpy.real(Pz))
|
|
120
|
+
j01 = float(numpy.real(Pm))
|
|
121
|
+
j10 = float(numpy.real(Pzm))
|
|
122
|
+
j11 = float(numpy.real(Pmm))
|
|
123
|
+
|
|
124
|
+
det = j00 * j11 - j01 * j10
|
|
125
|
+
if det == 0.0 or (not numpy.isfinite(det)):
|
|
126
|
+
return x, m, False
|
|
127
|
+
|
|
128
|
+
dx = (-f0 * j11 + f1 * j01) / det
|
|
129
|
+
dm = (-j00 * f1 + j10 * f0) / det
|
|
130
|
+
|
|
131
|
+
x += dx
|
|
132
|
+
m += dm
|
|
133
|
+
|
|
134
|
+
if abs(dx) + abs(dm) < tol:
|
|
135
|
+
return x, m, True
|
|
136
|
+
|
|
137
|
+
return x, m, False
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
# =============
|
|
141
|
+
# cluster edges
|
|
142
|
+
# =============
|
|
143
|
+
|
|
144
|
+
def _cluster_edges(edges, x_tol):
|
|
145
|
+
|
|
146
|
+
if len(edges) == 0:
|
|
147
|
+
return numpy.array([], dtype=float)
|
|
148
|
+
|
|
149
|
+
edges = numpy.array(sorted(edges), dtype=float)
|
|
150
|
+
out = [edges[0]]
|
|
151
|
+
for e in edges[1:]:
|
|
152
|
+
if abs(e - out[-1]) > x_tol:
|
|
153
|
+
out.append(e)
|
|
154
|
+
return numpy.array(out, dtype=float)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
# =======================
|
|
158
|
+
# pick physical root at z
|
|
159
|
+
# =======================
|
|
160
|
+
|
|
161
|
+
def _pick_physical_root_at_z(a_coeffs, z, im_sign=+1):
|
|
162
|
+
|
|
163
|
+
a = _poly_coeffs_in_m_at_z(a_coeffs, z)
|
|
164
|
+
r = _roots_poly_in_m(a)
|
|
165
|
+
if r.size == 0:
|
|
166
|
+
return numpy.nan + 1j * numpy.nan
|
|
167
|
+
|
|
168
|
+
w_ref = -1.0 / z
|
|
169
|
+
idx = int(numpy.argmin(numpy.abs(r - w_ref)))
|
|
170
|
+
w = r[idx]
|
|
171
|
+
|
|
172
|
+
# optional strictness: if it violates Herglotz, declare failure
|
|
173
|
+
if not numpy.isfinite(w.real) or not numpy.isfinite(w.imag):
|
|
174
|
+
return w
|
|
175
|
+
if (im_sign * w.imag) <= 0.0:
|
|
176
|
+
return w
|
|
177
|
+
|
|
178
|
+
return w
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
# ===============
|
|
182
|
+
# compute support
|
|
183
|
+
# ===============
|
|
184
|
+
|
|
185
|
+
def compute_support(a_coeffs,
|
|
186
|
+
x_min,
|
|
187
|
+
x_max,
|
|
188
|
+
n_scan=4000,
|
|
189
|
+
y_eps=1e-3,
|
|
190
|
+
im_sign=+1,
|
|
191
|
+
root_tol=0.0,
|
|
192
|
+
edge_rel_tol=1e-6,
|
|
193
|
+
edge_x_cluster_tol=1e-3,
|
|
194
|
+
newton_tol=1e-12):
|
|
195
|
+
"""
|
|
196
|
+
Fast support from fitted polynomial using branch-point system P=0, Pm=0.
|
|
197
|
+
|
|
198
|
+
Returns
|
|
199
|
+
-------
|
|
200
|
+
support : list of (a,b)
|
|
201
|
+
info : dict (edges, rel_res_curve, etc.)
|
|
202
|
+
"""
|
|
203
|
+
|
|
204
|
+
a_coeffs = numpy.asarray(a_coeffs)
|
|
205
|
+
x_grid = numpy.linspace(float(x_min), float(x_max), int(n_scan))
|
|
206
|
+
|
|
207
|
+
# For each x, find best real critical point m (Pm=0) minimizing rel
|
|
208
|
+
# residual.
|
|
209
|
+
rel = numpy.full(x_grid.size, numpy.inf, dtype=float)
|
|
210
|
+
m_star = numpy.full(x_grid.size, numpy.nan, dtype=float)
|
|
211
|
+
|
|
212
|
+
for i, x in enumerate(x_grid):
|
|
213
|
+
z = x + 0.0j
|
|
214
|
+
dcoef = _dPdm_coeffs_at_z(a_coeffs, z)
|
|
215
|
+
mr = _roots_poly_in_m(dcoef, tol=root_tol)
|
|
216
|
+
|
|
217
|
+
best = numpy.inf
|
|
218
|
+
best_m = numpy.nan
|
|
219
|
+
|
|
220
|
+
for w in mr:
|
|
221
|
+
# accept nearly-real roots; numerical roots can have small imag
|
|
222
|
+
# part
|
|
223
|
+
if abs(w.imag) > 1e-6 * (1.0 + abs(w.real)):
|
|
224
|
+
continue
|
|
225
|
+
m = float(w.real)
|
|
226
|
+
P, _, _, _, _, a = _P_and_partials(a_coeffs, z, m)
|
|
227
|
+
|
|
228
|
+
denom = 1.0
|
|
229
|
+
am = 1.0
|
|
230
|
+
for j in range(a.size):
|
|
231
|
+
denom += abs(a[j]) * abs(am)
|
|
232
|
+
am *= m
|
|
233
|
+
|
|
234
|
+
r = abs(numpy.real(P)) / denom
|
|
235
|
+
if numpy.isfinite(r) and r < best:
|
|
236
|
+
best = float(r)
|
|
237
|
+
best_m = m
|
|
238
|
+
|
|
239
|
+
rel[i] = best
|
|
240
|
+
m_star[i] = best_m
|
|
241
|
+
|
|
242
|
+
# Pick candidate edges as local minima of rel(x), below an automatic scale.
|
|
243
|
+
rel_f = rel[numpy.isfinite(rel)]
|
|
244
|
+
if rel_f.size == 0:
|
|
245
|
+
return [], {"edges": numpy.array([], dtype=float), "n_edges": 0}
|
|
246
|
+
|
|
247
|
+
med = float(numpy.median(rel_f))
|
|
248
|
+
min_rel = float(numpy.min(rel_f))
|
|
249
|
+
|
|
250
|
+
# accept local minima up to a factor above the best one, but never abov
|
|
251
|
+
# background scale
|
|
252
|
+
thr = min(0.1 * med, max(float(edge_rel_tol), 1e4 * min_rel))
|
|
253
|
+
|
|
254
|
+
edges0 = []
|
|
255
|
+
seeds = []
|
|
256
|
+
|
|
257
|
+
for i in range(1, x_grid.size - 1):
|
|
258
|
+
if not numpy.isfinite(rel[i]):
|
|
259
|
+
continue
|
|
260
|
+
if rel[i] <= rel[i - 1] and rel[i] <= rel[i + 1] and rel[i] < thr and \
|
|
261
|
+
numpy.isfinite(m_star[i]):
|
|
262
|
+
edges0.append(float(x_grid[i]))
|
|
263
|
+
seeds.append((float(x_grid[i]), float(m_star[i])))
|
|
264
|
+
|
|
265
|
+
# Refine each seed by 2D Newton (x,m)
|
|
266
|
+
edges = []
|
|
267
|
+
for x0, m0 in seeds:
|
|
268
|
+
xe, me, ok = _newton_edge(a_coeffs, x0, m0, tol=newton_tol)
|
|
269
|
+
if ok and numpy.isfinite(xe) and numpy.isfinite(me):
|
|
270
|
+
edges.append(float(xe))
|
|
271
|
+
|
|
272
|
+
edges = _cluster_edges(edges, edge_x_cluster_tol)
|
|
273
|
+
edges.sort()
|
|
274
|
+
|
|
275
|
+
# Build support by testing midpoints between consecutive real edges
|
|
276
|
+
support = []
|
|
277
|
+
m_im_tol = 1e-10
|
|
278
|
+
|
|
279
|
+
for i in range(edges.size - 1):
|
|
280
|
+
a = float(edges[i])
|
|
281
|
+
b = float(edges[i + 1])
|
|
282
|
+
if b <= a:
|
|
283
|
+
continue
|
|
284
|
+
|
|
285
|
+
xmid = 0.5 * (a + b)
|
|
286
|
+
|
|
287
|
+
# roots of P(xmid, m) with real coefficients
|
|
288
|
+
a_m = _poly_coeffs_in_m_at_z(a_coeffs, xmid + 0.0j)
|
|
289
|
+
r = _roots_poly_in_m(a_m, tol=root_tol)
|
|
290
|
+
|
|
291
|
+
# interval is support iff there exists a non-real root (complex pair)
|
|
292
|
+
if numpy.any(numpy.abs(numpy.imag(r)) > m_im_tol):
|
|
293
|
+
support.append((a, b))
|
|
294
|
+
|
|
295
|
+
info = {
|
|
296
|
+
"edges": edges,
|
|
297
|
+
"n_edges": int(edges.size),
|
|
298
|
+
"support": support,
|
|
299
|
+
"n_support": int(len(support)),
|
|
300
|
+
"x_grid": x_grid,
|
|
301
|
+
"rel": rel,
|
|
302
|
+
"thr": float(thr),
|
|
303
|
+
"x_min": float(x_min),
|
|
304
|
+
"x_max": float(x_max),
|
|
305
|
+
"n_scan": int(n_scan),
|
|
306
|
+
"y_eps": float(y_eps),
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return support, info
|
|
@@ -18,10 +18,11 @@ from ._continuation_algebraic import sample_z_joukowski, \
|
|
|
18
18
|
filter_z_away_from_cuts, fit_polynomial_relation, \
|
|
19
19
|
sanity_check_stieltjes_branch, eval_P
|
|
20
20
|
from ._edge import evolve_edges, merge_edges
|
|
21
|
-
from ._decompress import decompress_newton
|
|
21
|
+
from ._decompress import build_time_grid, decompress_newton
|
|
22
22
|
from ._decompress2 import decompress_coeffs
|
|
23
23
|
from ._homotopy import StieltjesPoly
|
|
24
|
-
from .
|
|
24
|
+
from ._branch_points import compute_branch_points
|
|
25
|
+
from ._support import compute_support
|
|
25
26
|
from ._moments import MomentsESD
|
|
26
27
|
from .._free_form._support import supp
|
|
27
28
|
from .._free_form._plot_util import plot_density, plot_hilbert, plot_stieltjes
|
|
@@ -63,7 +64,7 @@ class AlgebraicForm(object):
|
|
|
63
64
|
* ``'complex128'``: 128-bit complex numbers, equivalent of two double
|
|
64
65
|
precision floating point.
|
|
65
66
|
* ``'complex256'``: 256-bit complex numbers, equivalent of two long
|
|
66
|
-
double precision floating point. This
|
|
67
|
+
double precision floating point. This option is only available on
|
|
67
68
|
Linux machines.
|
|
68
69
|
|
|
69
70
|
When using series acceleration methods (such as setting
|
|
@@ -133,9 +134,6 @@ class AlgebraicForm(object):
|
|
|
133
134
|
# init
|
|
134
135
|
# ====
|
|
135
136
|
|
|
136
|
-
# def __init__(self, A, support=None, delta=1e-6, dtype='complex128',
|
|
137
|
-
# **kwargs):
|
|
138
|
-
|
|
139
137
|
def __init__(self, A, support=None, delta=1e-5, dtype='complex128',
|
|
140
138
|
**kwargs):
|
|
141
139
|
"""
|
|
@@ -145,8 +143,9 @@ class AlgebraicForm(object):
|
|
|
145
143
|
self.A = None
|
|
146
144
|
self.eig = None
|
|
147
145
|
self._stieltjes = None
|
|
148
|
-
self.
|
|
146
|
+
self._moments = None
|
|
149
147
|
self.support = support
|
|
148
|
+
self.est_support = None # Estimated from polynmial after fitting
|
|
150
149
|
self.delta = delta # Offset above real axis to apply Plemelj formula
|
|
151
150
|
|
|
152
151
|
# Data type for complex arrays
|
|
@@ -155,6 +154,7 @@ class AlgebraicForm(object):
|
|
|
155
154
|
if hasattr(A, 'stieltjes') and callable(getattr(A, 'stieltjes', None)):
|
|
156
155
|
# This is one of the distribution objects, like MarchenkoPastur
|
|
157
156
|
self._stieltjes = A.stieltjes
|
|
157
|
+
self.support = A.support()
|
|
158
158
|
self.n = 1
|
|
159
159
|
|
|
160
160
|
elif callable(A):
|
|
@@ -180,18 +180,17 @@ class AlgebraicForm(object):
|
|
|
180
180
|
# Use empirical Stieltjes function
|
|
181
181
|
self._stieltjes = lambda z: \
|
|
182
182
|
numpy.mean(1.0/(self.eig-z[:, numpy.newaxis]), axis=-1)
|
|
183
|
-
self.
|
|
183
|
+
self._moments = MomentsESD(self.eig) # NOTE (never used)
|
|
184
184
|
|
|
185
|
-
#
|
|
186
|
-
if support is None:
|
|
185
|
+
# broad support
|
|
186
|
+
if self.support is None:
|
|
187
187
|
if self.eig is None:
|
|
188
188
|
raise RuntimeError("Support must be provided without data")
|
|
189
|
+
|
|
189
190
|
# Detect support
|
|
190
191
|
self.lam_m, self.lam_p = supp(self.eig, **kwargs)
|
|
191
|
-
self.
|
|
192
|
-
self.broad_support = self.support[0]
|
|
192
|
+
self.broad_support = (self.lam_m, self.lam_p)
|
|
193
193
|
else:
|
|
194
|
-
self.support = support
|
|
195
194
|
self.lam_m = min([s[0] for s in self.support])
|
|
196
195
|
self.lam_p = max([s[1] for s in self.support])
|
|
197
196
|
self.broad_support = (self.lam_m, self.lam_p)
|
|
@@ -251,7 +250,16 @@ class AlgebraicForm(object):
|
|
|
251
250
|
# self.cache.clear()
|
|
252
251
|
|
|
253
252
|
z_fits = []
|
|
254
|
-
|
|
253
|
+
|
|
254
|
+
# Sampling around support, or broad_support. This is only needed to
|
|
255
|
+
# ensure sampled points are not hiting the support itself is not used
|
|
256
|
+
# in any computation. If support is not known, use broad support.
|
|
257
|
+
if self.support is not None:
|
|
258
|
+
possible_support = self.support
|
|
259
|
+
else:
|
|
260
|
+
possible_support = self.broad_support
|
|
261
|
+
|
|
262
|
+
for sup in possible_support:
|
|
255
263
|
a, b = sup
|
|
256
264
|
|
|
257
265
|
for i in range(len(r)):
|
|
@@ -261,7 +269,7 @@ class AlgebraicForm(object):
|
|
|
261
269
|
z_fit = numpy.concatenate(z_fits)
|
|
262
270
|
|
|
263
271
|
# Remove points too close to any cut
|
|
264
|
-
z_fit = filter_z_away_from_cuts(z_fit,
|
|
272
|
+
z_fit = filter_z_away_from_cuts(z_fit, possible_support, y_eps=y_eps,
|
|
265
273
|
x_pad=x_pad)
|
|
266
274
|
|
|
267
275
|
# Fitting (w_inf = None means adaptive weight selection)
|
|
@@ -271,11 +279,11 @@ class AlgebraicForm(object):
|
|
|
271
279
|
triangular=triangular, normalize=normalize, mu=mu,
|
|
272
280
|
mu_reg=mu_reg)
|
|
273
281
|
|
|
274
|
-
# Compute global branhc points, zeros of leading a_j, and support
|
|
275
|
-
branch_points, a_s_zero, support = compute_singular_points(a_coeffs)
|
|
276
|
-
|
|
277
282
|
self.a_coeffs = a_coeffs
|
|
278
283
|
|
|
284
|
+
# Estimate support from the fitted polynomial
|
|
285
|
+
self.est_support, _ = self.estimate_support(a_coeffs)
|
|
286
|
+
|
|
279
287
|
# Reporting error
|
|
280
288
|
P_res = numpy.abs(eval_P(z_fit, m1_fit, a_coeffs))
|
|
281
289
|
res_max = numpy.max(P_res[numpy.isfinite(P_res)])
|
|
@@ -288,13 +296,11 @@ class AlgebraicForm(object):
|
|
|
288
296
|
eta=max(y_eps, 1e-2), n_x=128,
|
|
289
297
|
max_bad_frac=0.05)
|
|
290
298
|
|
|
291
|
-
status['branch_points'] = branch_points
|
|
292
|
-
status['a_s_zero'] = a_s_zero
|
|
293
299
|
status['res_max'] = float(res_max)
|
|
294
300
|
status['res_99_9'] = float(res_99_9)
|
|
295
301
|
status['fit_metrics'] = fit_metrics
|
|
296
302
|
self.status = status
|
|
297
|
-
self._stieltjes = StieltjesPoly(self.a_coeffs)
|
|
303
|
+
self._stieltjes = StieltjesPoly(self.a_coeffs) # NOTE overwrite init
|
|
298
304
|
|
|
299
305
|
if verbose:
|
|
300
306
|
print(f'fit residual max : {res_max:>0.4e}')
|
|
@@ -319,7 +325,64 @@ class AlgebraicForm(object):
|
|
|
319
325
|
else:
|
|
320
326
|
print('\nStieltjes sanity check: OK')
|
|
321
327
|
|
|
322
|
-
return a_coeffs,
|
|
328
|
+
return a_coeffs, self.est_support, status
|
|
329
|
+
|
|
330
|
+
# =====================
|
|
331
|
+
# inflate broad support
|
|
332
|
+
# =====================
|
|
333
|
+
|
|
334
|
+
def _inflate_broad_support(self, inflate=0.0):
|
|
335
|
+
"""
|
|
336
|
+
"""
|
|
337
|
+
|
|
338
|
+
min_supp, max_supp = self.broad_support
|
|
339
|
+
|
|
340
|
+
c_supp = 0.5 * (max_supp + min_supp)
|
|
341
|
+
r_supp = 0.5 * (max_supp - min_supp)
|
|
342
|
+
|
|
343
|
+
x_min = c_supp - r_supp * (1.0 + inflate)
|
|
344
|
+
x_max = c_supp + r_supp * (1.0 + inflate)
|
|
345
|
+
|
|
346
|
+
return x_min, x_max
|
|
347
|
+
|
|
348
|
+
# ================
|
|
349
|
+
# estimate support
|
|
350
|
+
# ================
|
|
351
|
+
|
|
352
|
+
def estimate_support(self, a_coeffs=None, n_scan=4000):
|
|
353
|
+
"""
|
|
354
|
+
"""
|
|
355
|
+
|
|
356
|
+
if a_coeffs is None:
|
|
357
|
+
if self.a_coeffs is None:
|
|
358
|
+
raise RuntimeError('Call "fit" first.')
|
|
359
|
+
else:
|
|
360
|
+
a_coeffs = self.a_coeffs
|
|
361
|
+
|
|
362
|
+
# Inflate a bit to make sure all points are searched
|
|
363
|
+
x_min, x_max = self._inflate_broad_support(inflate=0.2)
|
|
364
|
+
|
|
365
|
+
est_support, info = compute_support(a_coeffs, x_min=x_min, x_max=x_max,
|
|
366
|
+
n_scan=n_scan)
|
|
367
|
+
|
|
368
|
+
return est_support, info
|
|
369
|
+
|
|
370
|
+
# ======================
|
|
371
|
+
# estimate branch points
|
|
372
|
+
# ======================
|
|
373
|
+
|
|
374
|
+
def estimate_branch_points(self):
|
|
375
|
+
"""
|
|
376
|
+
Compute global branch points and zeros of leading a_j
|
|
377
|
+
"""
|
|
378
|
+
|
|
379
|
+
if self.a_coeffs is None:
|
|
380
|
+
raise RuntimeError('Call "fit" first.')
|
|
381
|
+
|
|
382
|
+
bp, leading_zeros, info = compute_branch_points(
|
|
383
|
+
self.a_coeffs, tol=1e-12, real_tol=None)
|
|
384
|
+
|
|
385
|
+
return bp, leading_zeros, info
|
|
323
386
|
|
|
324
387
|
# =============
|
|
325
388
|
# generate grid
|
|
@@ -462,11 +525,11 @@ class AlgebraicForm(object):
|
|
|
462
525
|
|
|
463
526
|
# Preallocate density to zero
|
|
464
527
|
hilb = -self._stieltjes(x).real / numpy.pi
|
|
465
|
-
|
|
528
|
+
|
|
466
529
|
if plot:
|
|
467
|
-
plot_hilbert(x, hilb, support=self.
|
|
530
|
+
plot_hilbert(x, hilb, support=self.broad_support, latex=latex,
|
|
468
531
|
save=save)
|
|
469
|
-
|
|
532
|
+
|
|
470
533
|
return hilb
|
|
471
534
|
|
|
472
535
|
# =========
|
|
@@ -531,7 +594,7 @@ class AlgebraicForm(object):
|
|
|
531
594
|
# Create x if not given
|
|
532
595
|
if x is None:
|
|
533
596
|
x = self._generate_grid(2.0, extend=2.0)[::2]
|
|
534
|
-
|
|
597
|
+
|
|
535
598
|
# Create y if not given
|
|
536
599
|
if (plot is False) and (y is None):
|
|
537
600
|
# Do not use a Cartesian grid. Create a 1D array z slightly above
|
|
@@ -544,13 +607,13 @@ class AlgebraicForm(object):
|
|
|
544
607
|
y = numpy.linspace(-1, 1, 200)
|
|
545
608
|
x_grid, y_grid = numpy.meshgrid(x.real, y.real)
|
|
546
609
|
z = x_grid + 1j * y_grid # shape (Ny, Nx)
|
|
547
|
-
|
|
610
|
+
|
|
548
611
|
m = self._stieltjes(z, progress=True)
|
|
549
|
-
|
|
612
|
+
|
|
550
613
|
if plot:
|
|
551
614
|
plot_stieltjes(x, y, m, m, self.broad_support, latex=latex,
|
|
552
615
|
save=save)
|
|
553
|
-
|
|
616
|
+
|
|
554
617
|
return m
|
|
555
618
|
|
|
556
619
|
# ==============
|
|
@@ -592,8 +655,9 @@ class AlgebraicForm(object):
|
|
|
592
655
|
|
|
593
656
|
def decompress(self, size, x=None, method='one', plot=False, latex=False,
|
|
594
657
|
save=False, verbose=False, newton_opt={
|
|
595
|
-
'
|
|
596
|
-
'
|
|
658
|
+
'min_n_times': 10, 'max_iter': 50, 'tol': 1e-12,
|
|
659
|
+
'armijo': 1e-4, 'min_lam': 1e-6, 'w_min': 1e-14,
|
|
660
|
+
'sweep': True}):
|
|
597
661
|
"""
|
|
598
662
|
Free decompression of spectral density.
|
|
599
663
|
"""
|
|
@@ -611,8 +675,10 @@ class AlgebraicForm(object):
|
|
|
611
675
|
alpha = numpy.atleast_1d(size) / self.n
|
|
612
676
|
|
|
613
677
|
# Lower and upper bound on new support
|
|
614
|
-
hilb_lb =
|
|
615
|
-
|
|
678
|
+
hilb_lb = \
|
|
679
|
+
(1.0 / self._stieltjes(self.lam_m + self.delta * 1j).item()).real
|
|
680
|
+
hilb_ub = \
|
|
681
|
+
(1.0 / self._stieltjes(self.lam_p + self.delta * 1j).item()).real
|
|
616
682
|
lb = self.lam_m - (numpy.max(alpha) - 1) * hilb_lb
|
|
617
683
|
ub = self.lam_p - (numpy.max(alpha) - 1) * hilb_ub
|
|
618
684
|
|
|
@@ -632,24 +698,23 @@ class AlgebraicForm(object):
|
|
|
632
698
|
# Query grid on the real axis + a small imaginary buffer
|
|
633
699
|
z_query = x + 1j * self.delta
|
|
634
700
|
|
|
635
|
-
# Initial condition at t=0 (physical branch)
|
|
701
|
+
# Initial condition at t = 0 (physical branch)
|
|
636
702
|
w0_list = self._stieltjes(z_query)
|
|
637
703
|
|
|
638
|
-
#
|
|
639
|
-
t =
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
t = numpy.concatenate([numpy.zeros(1), t])
|
|
704
|
+
# Ensure there are at least min_n_times time t, including requested
|
|
705
|
+
# times, and especially time t = 0
|
|
706
|
+
t_all, idx_req = build_time_grid(
|
|
707
|
+
size, self.n, min_n_time=newton_opt.get("min_n_time", 0))
|
|
643
708
|
|
|
644
709
|
# Evolve
|
|
645
710
|
W, ok = decompress_newton(
|
|
646
|
-
z_query,
|
|
711
|
+
z_query, t_all, self.a_coeffs,
|
|
647
712
|
w0_list=w0_list, **newton_opt)
|
|
648
713
|
|
|
649
|
-
|
|
714
|
+
rho_all = W.imag / numpy.pi
|
|
650
715
|
|
|
651
|
-
#
|
|
652
|
-
rho =
|
|
716
|
+
# return only the user-requested ones
|
|
717
|
+
rho = rho_all[idx_req]
|
|
653
718
|
|
|
654
719
|
if verbose:
|
|
655
720
|
print("success rate per t:", ok.mean(axis=1))
|
|
@@ -696,10 +761,19 @@ class AlgebraicForm(object):
|
|
|
696
761
|
Evolves spectral edges.
|
|
697
762
|
"""
|
|
698
763
|
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
764
|
+
if self.support is not None:
|
|
765
|
+
known_support = self.support
|
|
766
|
+
elif self.est_support is not None:
|
|
767
|
+
known_support = self.est_support
|
|
768
|
+
else:
|
|
769
|
+
raise RuntimeError('Call "fit" first.')
|
|
770
|
+
|
|
771
|
+
edges, ok_edges = evolve_edges(t, self.a_coeffs,
|
|
772
|
+
support=known_support, eta=eta,
|
|
773
|
+
dt_max=dt_max, max_iter=max_iter,
|
|
774
|
+
tol=tol)
|
|
702
775
|
|
|
776
|
+
# Remove spurious edges, where two edge cross and are no longer valid.
|
|
703
777
|
edges2, active_k = merge_edges(edges, tol=1e-4)
|
|
704
778
|
|
|
705
779
|
if verbose:
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
freealg/__init__.py,sha256=SjcYb6HWmaclnnM-m1eC1honZRyfNBWYDYBx23kSdjo,833
|
|
2
|
-
freealg/__version__.py,sha256=
|
|
2
|
+
freealg/__version__.py,sha256=wcKNUm4uZK839U_xaeQFOEaUVqbjHp-EU_6MMaml4qk,23
|
|
3
3
|
freealg/_util.py,sha256=RzccUCORgzrI9NdNqwMVugiHU0uDKkJFcIyjFMUOnv8,2518
|
|
4
4
|
freealg/_algebraic_form/__init__.py,sha256=MIB_jVgw2qI-JW_ypqaFSeNAB6c4GvpjNySnap_a6hg,398
|
|
5
|
+
freealg/_algebraic_form/_branch_points.py,sha256=jzvHszw7xFe9B15a5RZV3pGfCGtndvrKJ4GIX6F3qhc,7814
|
|
5
6
|
freealg/_algebraic_form/_constraints.py,sha256=37U7nvtCTocuS7l_nfUznkPi195PY7eXFzeiikrv3B0,2448
|
|
6
|
-
freealg/_algebraic_form/_continuation_algebraic.py,sha256=
|
|
7
|
-
freealg/_algebraic_form/_decompress.py,sha256=
|
|
7
|
+
freealg/_algebraic_form/_continuation_algebraic.py,sha256=vVHFlMJYeXm97pgwEceJB2rGJeGOVhk_Ywg6mjoIA-g,19390
|
|
8
|
+
freealg/_algebraic_form/_decompress.py,sha256=uKiq5jlwmOvGriptIDz97fQiKs_F10uH6eMX1Ix43PQ,22538
|
|
8
9
|
freealg/_algebraic_form/_decompress2.py,sha256=Ng9w9xmGe9M-DApp35IeNeQlvszfzT4NZx5BQn0lQ3I,2459
|
|
9
|
-
freealg/_algebraic_form/_discriminant.py,sha256=755pproom6-xThFARaH20m4GuBwwZS2rc0Y80Yg6NzY,5331
|
|
10
10
|
freealg/_algebraic_form/_edge.py,sha256=7l9QyLJDxaEY4WB6MCUFtfEZSf04wyHwH7YPHFJXSbM,10690
|
|
11
|
-
freealg/_algebraic_form/_homotopy.py,sha256=
|
|
12
|
-
freealg/_algebraic_form/_moments.py,sha256=
|
|
11
|
+
freealg/_algebraic_form/_homotopy.py,sha256=q5z8YmrT_8m7L3qw_4FD1Sd5eELIvAiAHr2ucOLW258,9508
|
|
12
|
+
freealg/_algebraic_form/_moments.py,sha256=u55RpvQhIMJFGsq8LZ3IlnTKxNgQPhwnPuYUS34YEyw,12400
|
|
13
13
|
freealg/_algebraic_form/_sheets_util.py,sha256=6OLzWQKu-gN8rxM2rbpbN8TjNZFmD8UJ-8t9kcZdkCo,4174
|
|
14
|
-
freealg/_algebraic_form/
|
|
14
|
+
freealg/_algebraic_form/_support.py,sha256=9go_3NjmesSW1e08CiDu8oflpGmAbsh9iZRidMvlARI,7951
|
|
15
|
+
freealg/_algebraic_form/algebraic_form.py,sha256=L5qJFYOX5Qm8LlrBv6YwyuTxROUdn6cCjfXoDklmrlQ,32962
|
|
15
16
|
freealg/_free_form/__init__.py,sha256=5cnSX7kHci3wKx6-BEFhmVY_NjjmQAq1JjWPTEqETTg,611
|
|
16
17
|
freealg/_free_form/_chebyshev.py,sha256=zkyVA8NLf7uUKlJdLz4ijd_SurdsqUgkA5nHGWSybaE,6916
|
|
17
18
|
freealg/_free_form/_damp.py,sha256=k2vtBtWOxQBf4qXaWu_En81lQBXbEO4QbxxWpvuVhdE,1802
|
|
@@ -44,9 +45,9 @@ freealg/distributions/_wigner.py,sha256=epgx6ne6R_7to5j6-QsWIAVFJQFquWMmYgnZYMN4
|
|
|
44
45
|
freealg/visualization/__init__.py,sha256=NLq_zwueF7ytZ8sl8zLPqm-AODxxXNvfMozHGmmklcE,435
|
|
45
46
|
freealg/visualization/_glue_util.py,sha256=2oKnEYjUOS4OZfivmciVLauVr53kyHMwi6c2zRKilTQ,693
|
|
46
47
|
freealg/visualization/_rgb_hsv.py,sha256=rEskxXxSlKKxIrHRslVkgxHtD010L3ge9YtcVsOPl8E,3650
|
|
47
|
-
freealg-0.7.
|
|
48
|
-
freealg-0.7.
|
|
49
|
-
freealg-0.7.
|
|
50
|
-
freealg-0.7.
|
|
51
|
-
freealg-0.7.
|
|
52
|
-
freealg-0.7.
|
|
48
|
+
freealg-0.7.11.dist-info/licenses/AUTHORS.txt,sha256=0b67Nz4_JgIzUupHJTAZxu5QdSUM_HRM_X_w4xCb17o,30
|
|
49
|
+
freealg-0.7.11.dist-info/licenses/LICENSE.txt,sha256=J-EEYEtxb3VVf_Bn1TYfWnpY5lMFIM15iLDDcnaDTPA,1443
|
|
50
|
+
freealg-0.7.11.dist-info/METADATA,sha256=Cinwx4ei_4R4aY743wVBq_DreuX8KPlwARHCWT--AWo,5517
|
|
51
|
+
freealg-0.7.11.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
|
|
52
|
+
freealg-0.7.11.dist-info/top_level.txt,sha256=eR2wrgYwDdnnJ9Zf5PruPqe4kQav0GMvRsqct6y00Q8,8
|
|
53
|
+
freealg-0.7.11.dist-info/RECORD,,
|