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
|
@@ -0,0 +1,172 @@
|
|
|
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
|
+
from ._continuation_algebraic import powers
|
|
16
|
+
|
|
17
|
+
__all__ = ['build_time_grid', 'eval_P_partials']
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# ===============
|
|
21
|
+
# build time grid
|
|
22
|
+
# ===============
|
|
23
|
+
|
|
24
|
+
def build_time_grid(sizes, n0, min_n_times=0):
|
|
25
|
+
"""
|
|
26
|
+
sizes: list/array of requested matrix sizes (e.g. [2000,3000,4000,8000])
|
|
27
|
+
n0: initial size (self.n)
|
|
28
|
+
min_n_times: minimum number of time points to run Newton sweep on
|
|
29
|
+
|
|
30
|
+
Returns
|
|
31
|
+
-------
|
|
32
|
+
t_all: sorted time grid to run solver on
|
|
33
|
+
idx_req: indices of requested times inside t_all (same order as sizes)
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
sizes = numpy.asarray(sizes, dtype=float)
|
|
37
|
+
alpha = sizes / float(n0)
|
|
38
|
+
t_req = numpy.log(alpha)
|
|
39
|
+
|
|
40
|
+
# Always include t=0 and T=max(t_req)
|
|
41
|
+
T = float(numpy.max(t_req)) if t_req.size else 0.0
|
|
42
|
+
base = numpy.unique(numpy.r_[0.0, t_req, T])
|
|
43
|
+
t_all = numpy.sort(base)
|
|
44
|
+
|
|
45
|
+
# Add points only if needed: split largest gaps
|
|
46
|
+
N = int(min_n_times) if min_n_times is not None else 0
|
|
47
|
+
while t_all.size < N and t_all.size >= 2:
|
|
48
|
+
gaps = numpy.diff(t_all)
|
|
49
|
+
k = int(numpy.argmax(gaps))
|
|
50
|
+
mid = 0.5 * (t_all[k] + t_all[k+1])
|
|
51
|
+
t_all = numpy.sort(numpy.unique(numpy.r_[t_all, mid]))
|
|
52
|
+
|
|
53
|
+
# Map each requested time to an index in t_all (stable, no float drama)
|
|
54
|
+
# (t_req values came from same construction, so they should match exactly;
|
|
55
|
+
# still: use searchsorted + assert)
|
|
56
|
+
idx_req = numpy.searchsorted(t_all, t_req)
|
|
57
|
+
# optional sanity:
|
|
58
|
+
# assert numpy.allclose(t_all[idx_req], t_req, rtol=0, atol=0)
|
|
59
|
+
|
|
60
|
+
return t_all, idx_req
|
|
61
|
+
|
|
62
|
+
|
|
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
|
+
|
|
109
|
+
z = numpy.asarray(z, dtype=complex)
|
|
110
|
+
m = numpy.asarray(m, dtype=complex)
|
|
111
|
+
|
|
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)
|
freealg/_algebraic_form/_edge.py
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
import numpy
|
|
15
15
|
from ._continuation_algebraic import eval_roots
|
|
16
|
-
from .
|
|
16
|
+
from ._decompress_util import eval_P_partials
|
|
17
17
|
|
|
18
18
|
__all__ = ['evolve_edges', 'merge_edges']
|
|
19
19
|
|
|
@@ -129,65 +129,28 @@ def _init_edge_point_from_support(x_edge, a_coeffs, eta=1e-3):
|
|
|
129
129
|
# evolve edges
|
|
130
130
|
# ============
|
|
131
131
|
|
|
132
|
-
def evolve_edges(
|
|
133
|
-
|
|
132
|
+
def evolve_edges(
|
|
133
|
+
t_grid,
|
|
134
|
+
a_coeffs,
|
|
135
|
+
support=None,
|
|
136
|
+
eta=1e-3,
|
|
137
|
+
dt_max=0.1,
|
|
138
|
+
max_iter=30,
|
|
139
|
+
tol=1e-12,
|
|
140
|
+
return_preimage=False):
|
|
134
141
|
"""
|
|
135
|
-
Evolve spectral edges under free decompression using the fitted
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
At time t, edges are computed as critical values of the FD map restricted
|
|
139
|
-
to the spectral curve P(zeta,y)=0. We solve for (zeta(t), y(t)):
|
|
142
|
+
Evolve spectral edges under free decompression using the fitted polynomial
|
|
143
|
+
P.
|
|
140
144
|
|
|
145
|
+
Solves for (zeta(t), y(t)) on the spectral curve:
|
|
141
146
|
P(zeta,y) = 0,
|
|
142
147
|
y^2 * Py(zeta,y) - (exp(t)-1) * Pzeta(zeta,y) = 0,
|
|
143
148
|
|
|
144
|
-
then
|
|
149
|
+
then maps to physical coordinate:
|
|
145
150
|
z_edge(t) = zeta - (exp(t)-1)/y.
|
|
146
151
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
t_grid : array_like of float
|
|
150
|
-
Strictly increasing time grid.
|
|
151
|
-
a_coeffs : ndarray
|
|
152
|
-
Coefficients defining P(zeta,y).
|
|
153
|
-
support : list of (float, float), optional
|
|
154
|
-
List of intervals [(a1,b1),...,(ak,bk)] at t=0. If provided, these
|
|
155
|
-
endpoints are used as labels/initial guesses and all are tracked.
|
|
156
|
-
If omitted, this function currently raises ValueError (auto-detection
|
|
157
|
-
is intentionally not implemented here to avoid fragile heuristics).
|
|
158
|
-
eta : float, optional
|
|
159
|
-
Small imaginary part used only to pick an initial physical root near
|
|
160
|
-
each endpoint at t=0.
|
|
161
|
-
dt_max : float, optional
|
|
162
|
-
Maximum internal time step used for substepping in t.
|
|
163
|
-
max_iter : int, optional
|
|
164
|
-
Newton iterations per time step.
|
|
165
|
-
tol : float, optional
|
|
166
|
-
Tolerance for the 2x2 Newton solve.
|
|
167
|
-
|
|
168
|
-
Returns
|
|
169
|
-
-------
|
|
170
|
-
edges : ndarray, shape (len(t_grid), 2*k)
|
|
171
|
-
Tracked edges in the order [a1,b1,a2,b2,...] for each time.
|
|
172
|
-
ok : ndarray of bool, same shape as edges
|
|
173
|
-
Flags indicating whether each edge solve succeeded.
|
|
174
|
-
|
|
175
|
-
Notes
|
|
176
|
-
-----
|
|
177
|
-
The solve is done by continuation in time. If two edges merge, the Newton
|
|
178
|
-
system may become ill-conditioned near the merge time.
|
|
179
|
-
|
|
180
|
-
Examples
|
|
181
|
-
--------
|
|
182
|
-
.. code-block:: python
|
|
183
|
-
|
|
184
|
-
t_grid = numpy.linspace(0.0, 3.0, 61)
|
|
185
|
-
support = [(a1,b1)]
|
|
186
|
-
edges, ok = fd_evolve_edges(t_grid, a_coeffs, support=support,
|
|
187
|
-
eta=1e-3)
|
|
188
|
-
|
|
189
|
-
a_t = edges[:, 0]
|
|
190
|
-
b_t = edges[:, 1]
|
|
152
|
+
If return_preimage=True, also returns zeta_hist and y_hist of shape
|
|
153
|
+
(nt, 2k).
|
|
191
154
|
"""
|
|
192
155
|
|
|
193
156
|
t_grid = numpy.asarray(t_grid, dtype=float).ravel()
|
|
@@ -197,32 +160,43 @@ def evolve_edges(t_grid, a_coeffs, support=None, eta=1e-3, dt_max=0.1,
|
|
|
197
160
|
raise ValueError("t_grid must be strictly increasing.")
|
|
198
161
|
|
|
199
162
|
if support is None:
|
|
200
|
-
raise ValueError(
|
|
201
|
-
|
|
163
|
+
raise ValueError("support must be provided (auto-detection not " +
|
|
164
|
+
"implemented).")
|
|
202
165
|
|
|
203
|
-
# Flatten endpoints in
|
|
166
|
+
# Flatten endpoints in fixed order [a1,b1,a2,b2,...]
|
|
204
167
|
endpoints0 = []
|
|
205
168
|
for a, b in support:
|
|
206
169
|
endpoints0.append(float(a))
|
|
207
170
|
endpoints0.append(float(b))
|
|
208
171
|
|
|
209
172
|
m = len(endpoints0)
|
|
210
|
-
|
|
173
|
+
complex_edges = numpy.empty((t_grid.size, m), dtype=numpy.complex128)
|
|
211
174
|
ok = numpy.zeros((t_grid.size, m), dtype=bool)
|
|
212
175
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
176
|
+
if return_preimage:
|
|
177
|
+
zeta_hist = numpy.empty((t_grid.size, m), dtype=numpy.complex128)
|
|
178
|
+
y_hist = numpy.empty((t_grid.size, m), dtype=numpy.complex128)
|
|
179
|
+
else:
|
|
180
|
+
zeta_hist = None
|
|
181
|
+
y_hist = None
|
|
182
|
+
|
|
183
|
+
# Initialize (zeta,y) at t=0 from support endpoints
|
|
184
|
+
zeta = numpy.empty(m, dtype=numpy.complex128)
|
|
185
|
+
y = numpy.empty(m, dtype=numpy.complex128)
|
|
216
186
|
|
|
217
187
|
for j in range(m):
|
|
218
188
|
z0, y0, ok0 = _init_edge_point_from_support(endpoints0[j], a_coeffs,
|
|
219
189
|
eta=eta)
|
|
220
190
|
zeta[j] = z0
|
|
221
191
|
y[j] = y0
|
|
222
|
-
edges[0, j] = float(numpy.real(z0)) # at t=0, z_edge = zeta
|
|
223
192
|
ok[0, j] = ok0
|
|
193
|
+
complex_edges[0, j] = z0 # at t=0, tau-1 = 0 => z_edge = zeta
|
|
194
|
+
|
|
195
|
+
if return_preimage:
|
|
196
|
+
zeta_hist[0, :] = zeta
|
|
197
|
+
y_hist[0, :] = y
|
|
224
198
|
|
|
225
|
-
# Time
|
|
199
|
+
# Time stepping
|
|
226
200
|
for it in range(1, t_grid.size):
|
|
227
201
|
t0 = float(t_grid[it - 1])
|
|
228
202
|
t1 = float(t_grid[it])
|
|
@@ -236,19 +210,22 @@ def evolve_edges(t_grid, a_coeffs, support=None, eta=1e-3, dt_max=0.1,
|
|
|
236
210
|
t = t0 + dt * (ks / float(n_sub))
|
|
237
211
|
for j in range(m):
|
|
238
212
|
zeta[j], y[j], okj = _edge_newton_step(
|
|
239
|
-
t, zeta[j], y[j], a_coeffs,
|
|
240
|
-
max_iter=max_iter, tol=tol
|
|
213
|
+
t, zeta[j], y[j], a_coeffs, max_iter=max_iter, tol=tol
|
|
241
214
|
)
|
|
242
215
|
ok[it, j] = okj
|
|
243
216
|
|
|
244
217
|
tau = float(numpy.exp(t1))
|
|
245
218
|
c = tau - 1.0
|
|
246
|
-
|
|
219
|
+
complex_edges[it, :] = zeta - c / y
|
|
247
220
|
|
|
248
|
-
|
|
249
|
-
|
|
221
|
+
if return_preimage:
|
|
222
|
+
zeta_hist[it, :] = zeta
|
|
223
|
+
y_hist[it, :] = y
|
|
250
224
|
|
|
251
|
-
|
|
225
|
+
if return_preimage:
|
|
226
|
+
return complex_edges, ok, zeta_hist, y_hist
|
|
227
|
+
|
|
228
|
+
return complex_edges, ok
|
|
252
229
|
|
|
253
230
|
|
|
254
231
|
# ===========
|
|
@@ -282,6 +259,7 @@ def merge_edges(edges, tol=0.0):
|
|
|
282
259
|
active_k : ndarray, shape (nt,)
|
|
283
260
|
Number of remaining bulks (connected components) at each time.
|
|
284
261
|
"""
|
|
262
|
+
|
|
285
263
|
edges = numpy.asarray(edges, dtype=float)
|
|
286
264
|
nt, m = edges.shape
|
|
287
265
|
if m % 2 != 0:
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import numpy
|
|
6
6
|
from ._moments import AlgebraicStieltjesMoments
|
|
7
7
|
from tqdm import tqdm
|
|
8
|
+
from math import comb
|
|
8
9
|
|
|
9
10
|
__all__ = ['StieltjesPoly']
|
|
10
11
|
|
|
@@ -86,6 +87,8 @@ class StieltjesPoly(object):
|
|
|
86
87
|
Coefficient matrix defining P(z, m) in the monomial basis. For fixed
|
|
87
88
|
z, the coefficients of the polynomial in m are assembled from powers
|
|
88
89
|
of z.
|
|
90
|
+
mom : callable, optional
|
|
91
|
+
A callable providing raw moments ``m_k = mom(k)``
|
|
89
92
|
eps : float or None, optional
|
|
90
93
|
If Im(z) == 0, use z + i*eps as the boundary evaluation point.
|
|
91
94
|
If None and Im(z) == 0, eps is set to 1e-8 * max(1, |z|).
|
|
@@ -114,7 +117,7 @@ class StieltjesPoly(object):
|
|
|
114
117
|
None, eps is chosen per element as 1e-8 * max(1, |z|).
|
|
115
118
|
"""
|
|
116
119
|
|
|
117
|
-
def __init__(self, a, eps=None, height=2.0, steps=100, order=15):
|
|
120
|
+
def __init__(self, a, mom=None, eps=None, height=2.0, steps=100, order=15):
|
|
118
121
|
a = numpy.asarray(a)
|
|
119
122
|
if a.ndim != 2:
|
|
120
123
|
raise ValueError("a must be a 2D array.")
|
|
@@ -125,13 +128,28 @@ class StieltjesPoly(object):
|
|
|
125
128
|
self.height = height
|
|
126
129
|
self.steps = steps
|
|
127
130
|
self.order = order
|
|
131
|
+
if order < 3:
|
|
132
|
+
raise RuntimeError("order is too small, choose a larger value.")
|
|
128
133
|
|
|
129
|
-
|
|
130
|
-
|
|
134
|
+
if mom is None:
|
|
135
|
+
self.mom = AlgebraicStieltjesMoments(a)
|
|
136
|
+
else:
|
|
137
|
+
self.mom = mom
|
|
138
|
+
self.mu = numpy.array([self.mom(j) for j in range(self.order+1)])
|
|
139
|
+
self.rad = max([numpy.abs(self.mu[j] / self.mu[j-1])
|
|
140
|
+
for j in range(2, self.order+1)])
|
|
141
|
+
self.rad = 1.0 + self.height * self.rad
|
|
131
142
|
self.z0_p = 1j * self.rad
|
|
132
|
-
self.m0_p = self.
|
|
143
|
+
self.m0_p = self._moment_est(self.z0_p)
|
|
133
144
|
self.z0_m = -1j * self.rad
|
|
134
|
-
self.m0_m = self.
|
|
145
|
+
self.m0_m = self._moment_est(self.z0_m)
|
|
146
|
+
|
|
147
|
+
def _moment_est(self, z):
|
|
148
|
+
# Estimate Stieltjes transform (root) using moment
|
|
149
|
+
# expansion
|
|
150
|
+
z = numpy.asarray(z)
|
|
151
|
+
pows = z[..., numpy.newaxis]**(-numpy.arange(self.order+1)-1)
|
|
152
|
+
return -numpy.sum(pows * self.mu, axis=-1)
|
|
135
153
|
|
|
136
154
|
def _poly_coeffs_m(self, z_val):
|
|
137
155
|
z_powers = z_val ** numpy.arange(self.a_l)
|
|
@@ -142,7 +160,8 @@ class StieltjesPoly(object):
|
|
|
142
160
|
dtype=numpy.complex128)
|
|
143
161
|
return numpy.roots(coeffs)
|
|
144
162
|
|
|
145
|
-
def evaluate(self, z, eps=None, height=2.0, steps=100, order=15
|
|
163
|
+
def evaluate(self, z, eps=None, height=2.0, steps=100, order=15, extrap=2,
|
|
164
|
+
num_angles=1):
|
|
146
165
|
"""
|
|
147
166
|
Evaluate the Stieltjes-branch solution m(z) at a single point.
|
|
148
167
|
|
|
@@ -168,33 +187,46 @@ class StieltjesPoly(object):
|
|
|
168
187
|
if half_sign == 0.0:
|
|
169
188
|
half_sign = 1.0
|
|
170
189
|
|
|
171
|
-
#
|
|
172
|
-
#
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
190
|
+
# If z is outside radius of convergence, no homotopy
|
|
191
|
+
# necessary
|
|
192
|
+
if numpy.abs(z) > self.rad:
|
|
193
|
+
target = self._moment_est(z)
|
|
194
|
+
return select_root(self._poly_roots(z), z, target)
|
|
195
|
+
|
|
196
|
+
# z0 = z.real
|
|
197
|
+
# z0 = z0 + 1j*numpy.sqrt(self.rad**2 - z0**2)
|
|
198
|
+
# target = self._moment_est(z0)
|
|
199
|
+
# if half_sign > 0.0:
|
|
200
|
+
# z0 = self.z0_p
|
|
201
|
+
# target = self.m0_p
|
|
202
|
+
# else:
|
|
203
|
+
# z0 = self.z0_m
|
|
204
|
+
# target = self.m0_m
|
|
183
205
|
|
|
184
206
|
# Initialize at z0
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
207
|
+
res = 0
|
|
208
|
+
for theta in numpy.linspace(0, numpy.pi, num_angles+2)[1:-1]:
|
|
209
|
+
z0 = self.rad * numpy.exp(1j * theta) * half_sign
|
|
210
|
+
target = self._moment_est(z0)
|
|
211
|
+
coeffs = numpy.array([(-1)**k * comb(extrap, k + 1)
|
|
212
|
+
for k in range(extrap)])
|
|
213
|
+
w_prev = numpy.ones(extrap) * \
|
|
214
|
+
select_root(self._poly_roots(z0), z0, target)
|
|
215
|
+
|
|
216
|
+
# Straight-line homotopy continuation
|
|
217
|
+
for tau in numpy.linspace(0.0, 1.0, int(self.steps) + 1)[1:]:
|
|
218
|
+
z_tau = z0 + tau * (z_eval - z0)
|
|
219
|
+
target = numpy.dot(coeffs, w_prev)
|
|
220
|
+
w_prev[1:] = w_prev[0:-1]
|
|
221
|
+
w_prev[0] = select_root(self._poly_roots(z_tau), z_tau, target)
|
|
222
|
+
res += w_prev[0]
|
|
223
|
+
|
|
224
|
+
return res / num_angles
|
|
225
|
+
|
|
226
|
+
def __call__(self, z, progress=False, num_angles=1):
|
|
195
227
|
# Scalar fast-path
|
|
196
228
|
if numpy.isscalar(z):
|
|
197
|
-
return self.evaluate(z)
|
|
229
|
+
return self.evaluate(z, num_angles=num_angles)
|
|
198
230
|
|
|
199
231
|
# Array-like: evaluate elementwise, preserving shape
|
|
200
232
|
z_arr = numpy.asarray(z)
|
|
@@ -206,7 +238,7 @@ class StieltjesPoly(object):
|
|
|
206
238
|
else:
|
|
207
239
|
indices = numpy.ndindex(z_arr.shape)
|
|
208
240
|
for idx in indices:
|
|
209
|
-
out[idx] = self.evaluate(z_arr[idx])
|
|
241
|
+
out[idx] = self.evaluate(z_arr[idx], num_angles=num_angles)
|
|
210
242
|
|
|
211
243
|
return out
|
|
212
244
|
|