freealg 0.1.13__py3-none-any.whl → 0.1.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/__init__.py +3 -2
- freealg/__version__.py +1 -1
- freealg/_chebyshev.py +1 -10
- freealg/_decompress.py +89 -21
- freealg/_pade.py +23 -13
- freealg/_plot_util.py +6 -3
- freealg/_support.py +125 -24
- freealg/distributions/_kesten_mckay.py +13 -5
- freealg/distributions/_marchenko_pastur.py +10 -2
- freealg/distributions/_meixner.py +10 -2
- freealg/distributions/_wachter.py +10 -2
- freealg/distributions/_wigner.py +10 -2
- freealg/eigfree.py +170 -0
- freealg/freeform.py +80 -55
- {freealg-0.1.13.dist-info → freealg-0.1.15.dist-info}/METADATA +17 -7
- freealg-0.1.15.dist-info/RECORD +25 -0
- freealg-0.1.13.dist-info/RECORD +0 -24
- {freealg-0.1.13.dist-info → freealg-0.1.15.dist-info}/WHEEL +0 -0
- {freealg-0.1.13.dist-info → freealg-0.1.15.dist-info}/licenses/AUTHORS.txt +0 -0
- {freealg-0.1.13.dist-info → freealg-0.1.15.dist-info}/licenses/LICENSE.txt +0 -0
- {freealg-0.1.13.dist-info → freealg-0.1.15.dist-info}/top_level.txt +0 -0
freealg/__init__.py
CHANGED
|
@@ -6,9 +6,10 @@
|
|
|
6
6
|
# under the terms of the license found in the LICENSE.txt file in the root
|
|
7
7
|
# directory of this source tree.
|
|
8
8
|
|
|
9
|
-
from .freeform import FreeForm
|
|
9
|
+
from .freeform import FreeForm
|
|
10
|
+
from .eigfree import eigfree, condfree
|
|
10
11
|
from . import distributions
|
|
11
12
|
|
|
12
|
-
__all__ = ['FreeForm', 'distributions', 'eigfree']
|
|
13
|
+
__all__ = ['FreeForm', 'distributions', 'eigfree', 'condfree']
|
|
13
14
|
|
|
14
15
|
from .__version__ import __version__ # noqa: F401 E402
|
freealg/__version__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.1.
|
|
1
|
+
__version__ = "0.1.15"
|
freealg/_chebyshev.py
CHANGED
|
@@ -58,7 +58,6 @@ def chebyshev_sample_proj(eig, support, K=10, reg=0.0):
|
|
|
58
58
|
|
|
59
59
|
# Map to [–1,1] interval
|
|
60
60
|
t = (2 * eig - (lam_m + lam_p)) / (lam_p - lam_m)
|
|
61
|
-
N = eig.size
|
|
62
61
|
|
|
63
62
|
# Inner‐product norm of each U_k under w(t) = sqrt{1–t^2} is \\pi/2
|
|
64
63
|
norm = numpy.pi / 2
|
|
@@ -104,7 +103,7 @@ def chebyshev_kernel_proj(xs, pdf, support, K=10, reg=0.0):
|
|
|
104
103
|
|
|
105
104
|
for k in range(K + 1):
|
|
106
105
|
Pk = eval_chebyu(k, t) # U_k(t) on the grid
|
|
107
|
-
moment = numpy.trapezoid(Pk * pdf, xs)
|
|
106
|
+
moment = numpy.trapezoid(Pk * pdf, xs) # \int U_k(t) \rho(x) dx
|
|
108
107
|
|
|
109
108
|
if k == 0:
|
|
110
109
|
penalty = 0
|
|
@@ -225,14 +224,6 @@ def chebyshev_stieltjes(z, psi, support):
|
|
|
225
224
|
psi_zero = numpy.concatenate([[0], psi])
|
|
226
225
|
S = wynn_pade(psi_zero, J)
|
|
227
226
|
|
|
228
|
-
# build powers J^(k+1) for k=0..K
|
|
229
|
-
#K = len(psi) - 1
|
|
230
|
-
# shape: (..., K+1)
|
|
231
|
-
#Jpow = J[..., None] ** numpy.arange(1, K+2)
|
|
232
|
-
|
|
233
|
-
# sum psi_k * J^(k+1)
|
|
234
|
-
#S = numpy.sum(psi * Jpow, axis=-1)
|
|
235
|
-
|
|
236
227
|
# assemble m(z)
|
|
237
228
|
m_z = -2 / span * numpy.pi * S
|
|
238
229
|
|
freealg/_decompress.py
CHANGED
|
@@ -11,12 +11,18 @@
|
|
|
11
11
|
# =======
|
|
12
12
|
|
|
13
13
|
import numpy
|
|
14
|
-
# from scipy.integrate import solve_ivp
|
|
15
14
|
|
|
16
15
|
__all__ = ['decompress', 'reverse_characteristics']
|
|
17
16
|
|
|
17
|
+
|
|
18
|
+
# =============
|
|
19
|
+
# secant method
|
|
20
|
+
# =============
|
|
21
|
+
|
|
22
|
+
|
|
18
23
|
def secant_complex(f, z0, z1, a=0+0j, tol=1e-12, max_iter=100,
|
|
19
|
-
|
|
24
|
+
alpha=0.5, max_bt=1, eps=1e-30, step_factor=5.0,
|
|
25
|
+
post_smooth=True, jump_tol=10.0, verbose=False):
|
|
20
26
|
"""
|
|
21
27
|
Solves :math:``f(z) = a`` for many starting points simultaneously
|
|
22
28
|
using the secant method in the complex plane.
|
|
@@ -42,11 +48,20 @@ def secant_complex(f, z0, z1, a=0+0j, tol=1e-12, max_iter=100,
|
|
|
42
48
|
Back‑tracking shrink factor (``0 < alpha < 1``). Defaults to ``0.5``.
|
|
43
49
|
|
|
44
50
|
max_bt : int, optional
|
|
45
|
-
Maximum back‑tracking trials per iteration. Defaults to ``
|
|
51
|
+
Maximum back‑tracking trials per iteration. Defaults to ``0``.
|
|
46
52
|
|
|
47
53
|
eps : float, optional
|
|
48
54
|
Safeguard added to tiny denominators. Defaults to ``1e-30``.
|
|
49
55
|
|
|
56
|
+
post_smooth : bool, optional
|
|
57
|
+
If True (default) run a single vectorised clean-up pass that
|
|
58
|
+
re-solves points whose final root differs from the *nearest*
|
|
59
|
+
neighbour by more than ``jump_tol`` times the local median jump.
|
|
60
|
+
|
|
61
|
+
jump_tol : float, optional
|
|
62
|
+
Sensitivity of the clean-up pass; larger tolerance implies fewer
|
|
63
|
+
re-solves.
|
|
64
|
+
|
|
50
65
|
verbose : bool, optional
|
|
51
66
|
If *True*, prints progress every 10 iterations.
|
|
52
67
|
|
|
@@ -69,8 +84,8 @@ def secant_complex(f, z0, z1, a=0+0j, tol=1e-12, max_iter=100,
|
|
|
69
84
|
orig_shape = z0.shape
|
|
70
85
|
z0, z1, a = (x.ravel() for x in (z0, z1, a))
|
|
71
86
|
|
|
72
|
-
n_points
|
|
73
|
-
roots
|
|
87
|
+
n_points = z0.size
|
|
88
|
+
roots = z1.copy()
|
|
74
89
|
iterations = numpy.zeros(n_points, dtype=int)
|
|
75
90
|
|
|
76
91
|
f0 = f(z0) - a
|
|
@@ -87,9 +102,16 @@ def secant_complex(f, z0, z1, a=0+0j, tol=1e-12, max_iter=100,
|
|
|
87
102
|
# Secant step
|
|
88
103
|
denom = f1 - f0
|
|
89
104
|
denom = numpy.where(numpy.abs(denom) < eps, denom + eps, denom)
|
|
90
|
-
dz
|
|
91
|
-
|
|
92
|
-
|
|
105
|
+
dz = (z1 - z0) * f1 / denom
|
|
106
|
+
|
|
107
|
+
# Step-size limiter
|
|
108
|
+
prev_step = numpy.maximum(numpy.abs(z1 - z0), eps)
|
|
109
|
+
max_step = step_factor * prev_step
|
|
110
|
+
big = numpy.abs(dz) > max_step
|
|
111
|
+
dz[big] *= max_step[big] / numpy.abs(dz[big])
|
|
112
|
+
|
|
113
|
+
z2 = z1 - dz
|
|
114
|
+
f2 = f(z2) - a
|
|
93
115
|
|
|
94
116
|
# Line search by backtracking
|
|
95
117
|
worse = (numpy.abs(f2) >= numpy.abs(f1)) & active
|
|
@@ -130,18 +152,57 @@ def secant_complex(f, z0, z1, a=0+0j, tol=1e-12, max_iter=100,
|
|
|
130
152
|
residuals[remaining] = numpy.abs(f1[remaining])
|
|
131
153
|
iterations[remaining] = max_iter
|
|
132
154
|
|
|
155
|
+
# Optional clean-up pass
|
|
156
|
+
if post_smooth and n_points > 2:
|
|
157
|
+
# absolute jump to *nearest* neighbour (left or right)
|
|
158
|
+
diff_left = numpy.empty_like(roots)
|
|
159
|
+
diff_right = numpy.empty_like(roots)
|
|
160
|
+
diff_left[1:] = numpy.abs(roots[1:] - roots[:-1])
|
|
161
|
+
diff_right[:-1] = numpy.abs(roots[:-1] - roots[1:])
|
|
162
|
+
jump = numpy.minimum(diff_left, diff_right)
|
|
163
|
+
|
|
164
|
+
# ignore unconverged points
|
|
165
|
+
median_jump = numpy.median(jump[~remaining])
|
|
166
|
+
bad = (jump > jump_tol * median_jump) & ~remaining
|
|
167
|
+
|
|
168
|
+
if bad.any():
|
|
169
|
+
z_first_all = numpy.where(bad & (diff_left <= diff_right),
|
|
170
|
+
roots - diff_left,
|
|
171
|
+
roots + diff_right)
|
|
172
|
+
|
|
173
|
+
# keep only the offending indices
|
|
174
|
+
z_first = z_first_all[bad]
|
|
175
|
+
z_second = z_first + (roots[bad] - z_first) * 1e-2
|
|
176
|
+
|
|
177
|
+
# re-solve just the outliers in one vector call
|
|
178
|
+
new_root, new_res, new_iter = secant_complex(
|
|
179
|
+
f, z_first, z_second, a[bad],
|
|
180
|
+
tol=tol, max_iter=max_iter,
|
|
181
|
+
alpha=alpha, max_bt=max_bt,
|
|
182
|
+
eps=eps, step_factor=step_factor,
|
|
183
|
+
post_smooth=False, # avoid recursion
|
|
184
|
+
)
|
|
185
|
+
roots[bad] = new_root
|
|
186
|
+
residuals[bad] = new_res
|
|
187
|
+
iterations[bad] = iterations[bad] + new_iter
|
|
188
|
+
|
|
189
|
+
if verbose:
|
|
190
|
+
print(f"Clean-up: re-solved {bad.sum()} outliers")
|
|
191
|
+
|
|
133
192
|
return (
|
|
134
193
|
roots.reshape(orig_shape),
|
|
135
194
|
residuals.reshape(orig_shape),
|
|
136
195
|
iterations.reshape(orig_shape),
|
|
137
196
|
)
|
|
138
197
|
|
|
198
|
+
|
|
139
199
|
# ==========
|
|
140
200
|
# decompress
|
|
141
201
|
# ==========
|
|
142
202
|
|
|
143
|
-
|
|
144
|
-
|
|
203
|
+
|
|
204
|
+
def decompress(freeform, size, x=None, delta=1e-4, max_iter=500,
|
|
205
|
+
tolerance=1e-8):
|
|
145
206
|
"""
|
|
146
207
|
Free decompression of spectral density.
|
|
147
208
|
|
|
@@ -201,34 +262,35 @@ def decompress(freeform, size, x=None, delta=1e-6, max_iter=500,
|
|
|
201
262
|
alpha = size / freeform.n
|
|
202
263
|
m = freeform._eval_stieltjes
|
|
203
264
|
# Lower and upper bound on new support
|
|
204
|
-
hilb_lb = (1 / m(freeform.lam_m + delta * 1j)
|
|
205
|
-
hilb_ub = (1 / m(freeform.lam_p + delta * 1j)
|
|
265
|
+
hilb_lb = (1 / m(freeform.lam_m + delta * 1j)).real
|
|
266
|
+
hilb_ub = (1 / m(freeform.lam_p + delta * 1j)).real
|
|
206
267
|
lb = freeform.lam_m - (alpha - 1) * hilb_lb
|
|
207
268
|
ub = freeform.lam_p - (alpha - 1) * hilb_ub
|
|
208
269
|
|
|
209
270
|
# Create x if not given
|
|
210
|
-
|
|
271
|
+
on_grid = (x is None)
|
|
272
|
+
if on_grid:
|
|
211
273
|
radius = 0.5 * (ub - lb)
|
|
212
274
|
center = 0.5 * (ub + lb)
|
|
213
275
|
scale = 1.25
|
|
214
276
|
x_min = numpy.floor(center - radius * scale)
|
|
215
277
|
x_max = numpy.ceil(center + radius * scale)
|
|
216
278
|
x = numpy.linspace(x_min, x_max, 500)
|
|
279
|
+
else:
|
|
280
|
+
x = numpy.asarray(x)
|
|
217
281
|
|
|
218
|
-
# Ensure that input is an array
|
|
219
|
-
x = numpy.asarray(x)
|
|
220
282
|
target = x + delta * 1j
|
|
221
283
|
if numpy.isclose(alpha, 1.0):
|
|
222
284
|
return freeform.density(x), x, freeform.support
|
|
223
285
|
|
|
224
286
|
# Characteristic curve map
|
|
225
287
|
def _char_z(z):
|
|
226
|
-
return z + (1 / m(z)
|
|
288
|
+
return z + (1 / m(z)) * (1 - alpha)
|
|
227
289
|
|
|
228
|
-
z0 = numpy.full(target.shape, numpy.mean(freeform.support) +
|
|
290
|
+
z0 = numpy.full(target.shape, numpy.mean(freeform.support) + .1j,
|
|
229
291
|
dtype=numpy.complex128)
|
|
230
|
-
z1 = z0 -
|
|
231
|
-
|
|
292
|
+
z1 = z0 - .2j
|
|
293
|
+
|
|
232
294
|
roots, _, _ = secant_complex(
|
|
233
295
|
_char_z, z0, z1,
|
|
234
296
|
a=target,
|
|
@@ -238,9 +300,15 @@ def decompress(freeform, size, x=None, delta=1e-6, max_iter=500,
|
|
|
238
300
|
|
|
239
301
|
# Plemelj's formula
|
|
240
302
|
z = roots
|
|
241
|
-
char_s = m(z)
|
|
303
|
+
char_s = m(z) / alpha
|
|
242
304
|
rho = numpy.maximum(0, char_s.imag / numpy.pi)
|
|
243
305
|
rho[numpy.isnan(rho) | numpy.isinf(rho)] = 0
|
|
306
|
+
if on_grid:
|
|
307
|
+
x, rho = x.ravel(), rho.ravel()
|
|
308
|
+
# dx = x[1] - x[0]
|
|
309
|
+
# left_idx, right_idx = support_from_density(dx, rho)
|
|
310
|
+
# x, rho = x[left_idx-1:right_idx+1], rho[left_idx-1:right_idx+1]
|
|
311
|
+
rho = rho / numpy.trapezoid(rho, x)
|
|
244
312
|
|
|
245
313
|
return rho.reshape(*x.shape), x, (lb, ub)
|
|
246
314
|
|
|
@@ -260,7 +328,7 @@ def reverse_characteristics(freeform, z_inits, T, iterations=500,
|
|
|
260
328
|
m = freeform._eval_stieltjes
|
|
261
329
|
|
|
262
330
|
def _char_z(z, t):
|
|
263
|
-
return z + (1 / m(z)
|
|
331
|
+
return z + (1 / m(z)) * (1 - numpy.exp(t))
|
|
264
332
|
|
|
265
333
|
target_z, target_t = numpy.meshgrid(z_inits, t_eval)
|
|
266
334
|
|
freealg/_pade.py
CHANGED
|
@@ -236,9 +236,10 @@ def _eval_rational(z, c, D, poles, resid):
|
|
|
236
236
|
|
|
237
237
|
return c + D * z + term
|
|
238
238
|
|
|
239
|
-
|
|
240
|
-
#
|
|
241
|
-
#
|
|
239
|
+
|
|
240
|
+
# =========
|
|
241
|
+
# Wynn pade
|
|
242
|
+
# =========
|
|
242
243
|
|
|
243
244
|
@numba.jit(nopython=True, parallel=True)
|
|
244
245
|
def wynn_pade(coeffs, x):
|
|
@@ -248,48 +249,57 @@ def wynn_pade(coeffs, x):
|
|
|
248
249
|
returns a function handle that computes the Pade approximant at any x
|
|
249
250
|
using Wynn's epsilon algorithm.
|
|
250
251
|
|
|
251
|
-
Parameters
|
|
252
|
-
|
|
252
|
+
Parameters
|
|
253
|
+
----------
|
|
254
|
+
|
|
255
|
+
coeffs (list or array):
|
|
256
|
+
Coefficients [a0, a1, a2, ...] of the power series.
|
|
253
257
|
|
|
254
|
-
Returns
|
|
255
|
-
|
|
258
|
+
Returns
|
|
259
|
+
-------
|
|
260
|
+
|
|
261
|
+
function:
|
|
262
|
+
A function approximant(x) that returns the approximated value f(x).
|
|
256
263
|
"""
|
|
264
|
+
|
|
257
265
|
# Number of coefficients
|
|
258
266
|
xn = x.ravel()
|
|
259
267
|
d = len(xn)
|
|
260
268
|
N = len(coeffs)
|
|
261
|
-
|
|
269
|
+
|
|
262
270
|
# Compute the partial sums s_n = sum_{i=0}^n a_i * x^i for n=0,...,N-1
|
|
263
271
|
eps = numpy.zeros((N+1, N, d), dtype=numpy.complex128)
|
|
264
272
|
for i in numba.prange(d):
|
|
265
273
|
partial_sum = 0.0
|
|
266
274
|
for n in range(N):
|
|
267
275
|
partial_sum += coeffs[n] * (xn[i] ** n)
|
|
268
|
-
eps[0,n,i] = partial_sum
|
|
276
|
+
eps[0, n, i] = partial_sum
|
|
269
277
|
|
|
270
278
|
for i in numba.prange(d):
|
|
271
279
|
for k in range(1, N+1):
|
|
272
280
|
for j in range(N - k):
|
|
273
|
-
delta = eps[k-1, j+1,i] - eps[k-1, j,i]
|
|
281
|
+
delta = eps[k-1, j+1, i] - eps[k-1, j, i]
|
|
274
282
|
if delta == 0:
|
|
275
283
|
rec_delta = numpy.inf
|
|
276
284
|
elif numpy.isinf(delta) or numpy.isnan(delta):
|
|
277
285
|
rec_delta = 0.0
|
|
278
286
|
else:
|
|
279
287
|
rec_delta = 1.0 / delta
|
|
280
|
-
eps[k,j,i] = rec_delta
|
|
288
|
+
eps[k, j, i] = rec_delta
|
|
281
289
|
if k > 1:
|
|
282
|
-
eps[k,j,i] += eps[k-2,j+1,i]
|
|
290
|
+
eps[k, j, i] += eps[k-2, j+1, i]
|
|
283
291
|
|
|
284
292
|
if (N % 2) == 0:
|
|
285
293
|
N -= 1
|
|
286
|
-
|
|
294
|
+
|
|
287
295
|
return eps[N-1, 0, :].reshape(x.shape)
|
|
288
296
|
|
|
297
|
+
|
|
289
298
|
# ========
|
|
290
299
|
# fit pade
|
|
291
300
|
# ========
|
|
292
301
|
|
|
302
|
+
|
|
293
303
|
def fit_pade(x, f, lam_m, lam_p, p=1, q=2, odd_side='left', pade_reg=0.0,
|
|
294
304
|
safety=1.0, max_outer=40, xtol=1e-12, ftol=1e-12, optimizer='ls',
|
|
295
305
|
verbose=0):
|
freealg/_plot_util.py
CHANGED
|
@@ -139,7 +139,7 @@ def _auto_bins(array, method='scott', factor=5):
|
|
|
139
139
|
# ============
|
|
140
140
|
|
|
141
141
|
def plot_density(x, rho, eig=None, support=None, label='',
|
|
142
|
-
title='Spectral
|
|
142
|
+
title='Spectral Density', latex=False, save=False):
|
|
143
143
|
"""
|
|
144
144
|
"""
|
|
145
145
|
|
|
@@ -147,8 +147,11 @@ def plot_density(x, rho, eig=None, support=None, label='',
|
|
|
147
147
|
|
|
148
148
|
fig, ax = plt.subplots(figsize=(6, 2.7))
|
|
149
149
|
|
|
150
|
-
if
|
|
151
|
-
|
|
150
|
+
if eig is not None:
|
|
151
|
+
if support is not None:
|
|
152
|
+
lam_m, lam_p = support
|
|
153
|
+
else:
|
|
154
|
+
lam_m, lam_p = min(eig), max(eig)
|
|
152
155
|
bins = numpy.linspace(lam_m, lam_p, _auto_bins(eig))
|
|
153
156
|
_ = ax.hist(eig, bins, density=True, color='silver',
|
|
154
157
|
edgecolor='none', label='Histogram')
|
freealg/_support.py
CHANGED
|
@@ -1,31 +1,126 @@
|
|
|
1
|
+
# SPDX-License-Identifier: BSD-3-Clause
|
|
2
|
+
# SPDX-FileType: SOURCE
|
|
3
|
+
#
|
|
4
|
+
# This program is free software: you can redistribute it and/or modify it under
|
|
5
|
+
# the terms of the license found in the LICENSE.txt file in the root directory
|
|
6
|
+
# of this source tree.
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# =======
|
|
10
|
+
# Imports
|
|
11
|
+
# =======
|
|
12
|
+
|
|
1
13
|
import numpy
|
|
14
|
+
import numba
|
|
2
15
|
from scipy.stats import gaussian_kde
|
|
3
16
|
|
|
4
|
-
|
|
17
|
+
|
|
18
|
+
@numba.njit(numba.types.UniTuple(numba.types.int64, 2)(
|
|
19
|
+
numba.types.float64,
|
|
20
|
+
numba.types.float64[::1]
|
|
21
|
+
))
|
|
22
|
+
def support_from_density(dx, density):
|
|
23
|
+
"""
|
|
24
|
+
Estimates the support from a collection of noisy observations of a
|
|
25
|
+
density over a grid of x-values with mesh spacing dx.
|
|
26
|
+
"""
|
|
27
|
+
n = density.shape[0]
|
|
28
|
+
target = 1.0 / dx
|
|
29
|
+
|
|
30
|
+
# 1) compute total_sum once
|
|
31
|
+
total_sum = 0.0
|
|
32
|
+
for t in range(n):
|
|
33
|
+
total_sum += density[t]
|
|
34
|
+
|
|
35
|
+
# 2) set up our “best‐so‐far” trackers
|
|
36
|
+
large = 1e300
|
|
37
|
+
best_nonneg_sum = large
|
|
38
|
+
best_nonneg_idx = -1
|
|
39
|
+
best_nonpos_sum = -large
|
|
40
|
+
best_nonpos_idx = -1
|
|
41
|
+
|
|
42
|
+
# 3) seed with first element (i.e. prefix_sum for k=1)
|
|
43
|
+
prefix_sum = density[0]
|
|
44
|
+
if prefix_sum >= 0.0:
|
|
45
|
+
best_nonneg_sum, best_nonneg_idx = prefix_sum, 1
|
|
46
|
+
else:
|
|
47
|
+
best_nonpos_sum, best_nonpos_idx = prefix_sum, 1
|
|
48
|
+
|
|
49
|
+
# 4) sweep j from 2...n–1, updating prefix_sum on the fly
|
|
50
|
+
optimal_i, optimal_j = 1, 2
|
|
51
|
+
minimal_cost = large
|
|
52
|
+
|
|
53
|
+
for j in range(2, n):
|
|
54
|
+
# extend prefix_sum to cover density[0]...density[j-1]
|
|
55
|
+
prefix_sum += density[j-1]
|
|
56
|
+
|
|
57
|
+
# cost for [0...i], [i...j]
|
|
58
|
+
diff_mid = prefix_sum - target
|
|
59
|
+
if diff_mid >= 0.0 and best_nonneg_sum <= diff_mid:
|
|
60
|
+
cost12 = diff_mid
|
|
61
|
+
i_cand = best_nonneg_idx
|
|
62
|
+
elif diff_mid < 0.0 and best_nonpos_sum >= diff_mid:
|
|
63
|
+
cost12 = -diff_mid
|
|
64
|
+
i_cand = best_nonpos_idx
|
|
65
|
+
else:
|
|
66
|
+
cost_using_nonpos = diff_mid - 2.0 * best_nonpos_sum
|
|
67
|
+
cost_using_nonneg = 2.0 * best_nonneg_sum - diff_mid
|
|
68
|
+
if cost_using_nonpos < cost_using_nonneg:
|
|
69
|
+
cost12, i_cand = cost_using_nonpos, best_nonpos_idx
|
|
70
|
+
else:
|
|
71
|
+
cost12, i_cand = cost_using_nonneg, best_nonneg_idx
|
|
72
|
+
|
|
73
|
+
# cost for [j...n]
|
|
74
|
+
cost3 = total_sum - prefix_sum
|
|
75
|
+
if cost3 < 0.0:
|
|
76
|
+
cost3 = -cost3
|
|
77
|
+
|
|
78
|
+
# total and maybe update best split
|
|
79
|
+
total_cost = cost12 + cost3
|
|
80
|
+
if total_cost < minimal_cost:
|
|
81
|
+
minimal_cost = total_cost
|
|
82
|
+
optimal_i, optimal_j = i_cand, j
|
|
83
|
+
|
|
84
|
+
# update our prefix‐sum trackers
|
|
85
|
+
if prefix_sum >= 0.0:
|
|
86
|
+
if prefix_sum < best_nonneg_sum:
|
|
87
|
+
best_nonneg_sum, best_nonneg_idx = prefix_sum, j
|
|
88
|
+
else:
|
|
89
|
+
if prefix_sum > best_nonpos_sum:
|
|
90
|
+
best_nonpos_sum, best_nonpos_idx = prefix_sum, j
|
|
91
|
+
|
|
92
|
+
return optimal_i, optimal_j
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def detect_support(eigs, method='asymp', k=None, p=0.001, **kwargs):
|
|
5
96
|
"""
|
|
6
97
|
Estimates the support of the eigenvalue density.
|
|
7
98
|
|
|
8
99
|
Parameters
|
|
9
100
|
----------
|
|
10
|
-
method : {``'range'``, ``'
|
|
11
|
-
``'interior_smooth'``}, \
|
|
12
|
-
default= ``'
|
|
101
|
+
method : {``'range'``, ``'asymp'``, ``'jackknife'``, ``'regression'``,
|
|
102
|
+
``'interior'``, ``'interior_smooth'``}, \
|
|
103
|
+
default= ``'asymp'``
|
|
13
104
|
The method of support estimation:
|
|
14
105
|
|
|
15
|
-
* ``'range'``: no estimation; the support is the range of the
|
|
106
|
+
* ``'range'``: no estimation; the support is the range of the
|
|
107
|
+
eigenvalues.
|
|
108
|
+
* ``'asymp'``: assume the relative error in the min/max estimator is
|
|
109
|
+
1/n.
|
|
16
110
|
* ``'jackknife'``: estimates the support using Quenouille's [1]
|
|
17
|
-
jackknife estimator. Fast and simple, more accurate than the
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
111
|
+
jackknife estimator. Fast and simple, more accurate than the
|
|
112
|
+
range.
|
|
113
|
+
* ``'regression'``: estimates the support by performing a regression
|
|
114
|
+
under the assumption that the edge behavior is of square-root
|
|
115
|
+
type. Often most accurate.
|
|
21
116
|
* ``'interior'``: estimates a support assuming the range overestimates;
|
|
22
117
|
uses quantiles (p, 1-p).
|
|
23
|
-
* ``'interior_smooth'``: same as ``'interior'`` but using kernel
|
|
24
|
-
estimation.
|
|
118
|
+
* ``'interior_smooth'``: same as ``'interior'`` but using kernel
|
|
119
|
+
density estimation.
|
|
25
120
|
|
|
26
121
|
k : int, default = None
|
|
27
|
-
Number of extreme order statistics to use for ``method='regression'``.
|
|
28
|
-
|
|
122
|
+
Number of extreme order statistics to use for ``method='regression'``.
|
|
123
|
+
|
|
29
124
|
p : float, default=0.001
|
|
30
125
|
The edges of the support of the distribution is detected by the
|
|
31
126
|
:math:`p`-quantile on the left and :math:`(1-p)`-quantile on the right
|
|
@@ -36,28 +131,33 @@ def detect_support(eigs, method='interior_smooth', k = None, p = 0.001, **kwargs
|
|
|
36
131
|
References
|
|
37
132
|
----------
|
|
38
133
|
|
|
39
|
-
.. [1] Quenouille, M. H. (1949, July). Approximate tests of correlation in
|
|
40
|
-
|
|
41
|
-
|
|
134
|
+
.. [1] Quenouille, M. H. (1949, July). Approximate tests of correlation in
|
|
135
|
+
time-series. In Mathematical Proceedings of the Cambridge
|
|
136
|
+
Philosophical Society (Vol. 45, No. 3, pp. 483-484). Cambridge
|
|
137
|
+
University Press.
|
|
42
138
|
"""
|
|
43
139
|
|
|
44
|
-
if method=='range':
|
|
140
|
+
if method == 'range':
|
|
45
141
|
lam_m = eigs.min()
|
|
46
142
|
lam_p = eigs.max()
|
|
47
143
|
|
|
48
|
-
elif method=='
|
|
144
|
+
elif method == 'asymp':
|
|
145
|
+
lam_m = eigs.min() - abs(eigs.min()) / len(eigs)
|
|
146
|
+
lam_p = eigs.max() + abs(eigs.max()) / len(eigs)
|
|
147
|
+
|
|
148
|
+
elif method == 'jackknife':
|
|
49
149
|
x, n = numpy.sort(eigs), len(eigs)
|
|
50
|
-
lam_m = x[0]
|
|
150
|
+
lam_m = x[0] - (n - 1)/n * (x[1] - x[0])
|
|
51
151
|
lam_p = x[-1] + (n - 1)/n * (x[-1] - x[-2])
|
|
52
152
|
|
|
53
|
-
elif method=='regression':
|
|
153
|
+
elif method == 'regression':
|
|
54
154
|
x, n = numpy.sort(eigs), len(eigs)
|
|
55
155
|
if k is None:
|
|
56
156
|
k = int(round(n ** (2/3)))
|
|
57
157
|
k = max(5, min(k, n // 2))
|
|
58
158
|
|
|
59
159
|
# The theoretical cdf near the edge behaves like const*(x - a)^{3/2},
|
|
60
|
-
# so (i/n)
|
|
160
|
+
# so (i/n) ~ (x - a)^{3/2} -> x ~ a + const*(i/n)^{2/3}.
|
|
61
161
|
y = ((numpy.arange(1, k + 1) - 0.5) / n) ** (2 / 3)
|
|
62
162
|
|
|
63
163
|
# Left edge: regress x_{(i)} on y
|
|
@@ -66,10 +166,10 @@ def detect_support(eigs, method='interior_smooth', k = None, p = 0.001, **kwargs
|
|
|
66
166
|
# Right edge: regress x_{(n-i+1)} on y
|
|
67
167
|
_, lam_p = numpy.polyfit(y, x[-k:][::-1], 1)
|
|
68
168
|
|
|
69
|
-
elif method=='interior':
|
|
169
|
+
elif method == 'interior':
|
|
70
170
|
lam_m, lam_p = numpy.quantile(eigs, [p, 1-p])
|
|
71
|
-
|
|
72
|
-
elif method=='interior_smooth':
|
|
171
|
+
|
|
172
|
+
elif method == 'interior_smooth':
|
|
73
173
|
kde = gaussian_kde(eigs)
|
|
74
174
|
xs = numpy.linspace(eigs.min(), eigs.max(), 1000)
|
|
75
175
|
fs = kde(xs)
|
|
@@ -79,6 +179,7 @@ def detect_support(eigs, method='interior_smooth', k = None, p = 0.001, **kwargs
|
|
|
79
179
|
|
|
80
180
|
lam_m = numpy.interp(p, cdf, xs)
|
|
81
181
|
lam_p = numpy.interp(1-p, cdf, xs)
|
|
182
|
+
|
|
82
183
|
else:
|
|
83
184
|
raise NotImplementedError("Unknown method")
|
|
84
185
|
|
|
@@ -110,7 +110,7 @@ class KestenMcKay(object):
|
|
|
110
110
|
# density
|
|
111
111
|
# =======
|
|
112
112
|
|
|
113
|
-
def density(self, x=None, plot=False, latex=False, save=False):
|
|
113
|
+
def density(self, x=None, plot=False, latex=False, save=False, eig=None):
|
|
114
114
|
"""
|
|
115
115
|
Density of distribution.
|
|
116
116
|
|
|
@@ -137,6 +137,10 @@ class KestenMcKay(object):
|
|
|
137
137
|
assumed to the save filename (with the file extension). This option
|
|
138
138
|
is relevant only if ``plot=True``.
|
|
139
139
|
|
|
140
|
+
eig : numpy.array, default=None
|
|
141
|
+
A collection of eigenvalues to compare to via histogram. This
|
|
142
|
+
option is relevant only if ``plot=True``.
|
|
143
|
+
|
|
140
144
|
Returns
|
|
141
145
|
-------
|
|
142
146
|
|
|
@@ -173,7 +177,11 @@ class KestenMcKay(object):
|
|
|
173
177
|
numpy.sqrt(4.0 * (self.d - 1.0) - x[mask]**2)
|
|
174
178
|
|
|
175
179
|
if plot:
|
|
176
|
-
|
|
180
|
+
if eig is not None:
|
|
181
|
+
label = 'Theoretical'
|
|
182
|
+
else:
|
|
183
|
+
label = ''
|
|
184
|
+
plot_density(x, rho, label=label, latex=latex, save=save, eig=eig)
|
|
177
185
|
|
|
178
186
|
return rho
|
|
179
187
|
|
|
@@ -539,9 +547,9 @@ class KestenMcKay(object):
|
|
|
539
547
|
|
|
540
548
|
return samples
|
|
541
549
|
|
|
542
|
-
#
|
|
543
|
-
#
|
|
544
|
-
#
|
|
550
|
+
# ===============
|
|
551
|
+
# haar orthogonal
|
|
552
|
+
# ===============
|
|
545
553
|
|
|
546
554
|
def _haar_orthogonal(self, n, k, seed=None):
|
|
547
555
|
"""
|
|
@@ -108,7 +108,7 @@ class MarchenkoPastur(object):
|
|
|
108
108
|
# density
|
|
109
109
|
# =======
|
|
110
110
|
|
|
111
|
-
def density(self, x=None, plot=False, latex=False, save=False):
|
|
111
|
+
def density(self, x=None, plot=False, latex=False, save=False, eig=None):
|
|
112
112
|
"""
|
|
113
113
|
Density of distribution.
|
|
114
114
|
|
|
@@ -135,6 +135,10 @@ class MarchenkoPastur(object):
|
|
|
135
135
|
assumed to the save filename (with the file extension). This option
|
|
136
136
|
is relevant only if ``plot=True``.
|
|
137
137
|
|
|
138
|
+
eig : numpy.array, default=None
|
|
139
|
+
A collection of eigenvalues to compare to via histogram. This
|
|
140
|
+
option is relevant only if ``plot=True``.
|
|
141
|
+
|
|
138
142
|
Returns
|
|
139
143
|
-------
|
|
140
144
|
|
|
@@ -171,7 +175,11 @@ class MarchenkoPastur(object):
|
|
|
171
175
|
numpy.sqrt((self.lam_p - x[mask]) * (x[mask] - self.lam_m))
|
|
172
176
|
|
|
173
177
|
if plot:
|
|
174
|
-
|
|
178
|
+
if eig is not None:
|
|
179
|
+
label = 'Theoretical'
|
|
180
|
+
else:
|
|
181
|
+
label = ''
|
|
182
|
+
plot_density(x, rho, label=label, latex=latex, save=save, eig=eig)
|
|
175
183
|
|
|
176
184
|
return rho
|
|
177
185
|
|
|
@@ -114,7 +114,7 @@ class Meixner(object):
|
|
|
114
114
|
# density
|
|
115
115
|
# =======
|
|
116
116
|
|
|
117
|
-
def density(self, x=None, plot=False, latex=False, save=False):
|
|
117
|
+
def density(self, x=None, plot=False, latex=False, save=False, eig=None):
|
|
118
118
|
"""
|
|
119
119
|
Density of distribution.
|
|
120
120
|
|
|
@@ -141,6 +141,10 @@ class Meixner(object):
|
|
|
141
141
|
assumed to the save filename (with the file extension). This option
|
|
142
142
|
is relevant only if ``plot=True``.
|
|
143
143
|
|
|
144
|
+
eig : numpy.array, default=None
|
|
145
|
+
A collection of eigenvalues to compare to via histogram. This
|
|
146
|
+
option is relevant only if ``plot=True``.
|
|
147
|
+
|
|
144
148
|
Returns
|
|
145
149
|
-------
|
|
146
150
|
|
|
@@ -188,7 +192,11 @@ class Meixner(object):
|
|
|
188
192
|
rho[mask] = numer[mask] / denom[mask]
|
|
189
193
|
|
|
190
194
|
if plot:
|
|
191
|
-
|
|
195
|
+
if eig is not None:
|
|
196
|
+
label = 'Theoretical'
|
|
197
|
+
else:
|
|
198
|
+
label = ''
|
|
199
|
+
plot_density(x, rho, label=label, latex=latex, save=save, eig=eig)
|
|
192
200
|
|
|
193
201
|
return rho
|
|
194
202
|
|
|
@@ -115,7 +115,7 @@ class Wachter(object):
|
|
|
115
115
|
# density
|
|
116
116
|
# =======
|
|
117
117
|
|
|
118
|
-
def density(self, x=None, plot=False, latex=False, save=False):
|
|
118
|
+
def density(self, x=None, plot=False, latex=False, save=False, eig=None):
|
|
119
119
|
"""
|
|
120
120
|
Density of distribution.
|
|
121
121
|
|
|
@@ -142,6 +142,10 @@ class Wachter(object):
|
|
|
142
142
|
assumed to the save filename (with the file extension). This option
|
|
143
143
|
is relevant only if ``plot=True``.
|
|
144
144
|
|
|
145
|
+
eig : numpy.array, default=None
|
|
146
|
+
A collection of eigenvalues to compare to via histogram. This
|
|
147
|
+
option is relevant only if ``plot=True``.
|
|
148
|
+
|
|
145
149
|
Returns
|
|
146
150
|
-------
|
|
147
151
|
|
|
@@ -179,7 +183,11 @@ class Wachter(object):
|
|
|
179
183
|
numpy.sqrt((self.lam_p - x[mask]) * (x[mask] - self.lam_m))
|
|
180
184
|
|
|
181
185
|
if plot:
|
|
182
|
-
|
|
186
|
+
if eig is not None:
|
|
187
|
+
label = 'Theoretical'
|
|
188
|
+
else:
|
|
189
|
+
label = ''
|
|
190
|
+
plot_density(x, rho, label=label, latex=latex, save=save, eig=eig)
|
|
183
191
|
|
|
184
192
|
return rho
|
|
185
193
|
|
freealg/distributions/_wigner.py
CHANGED
|
@@ -96,7 +96,7 @@ class Wigner(object):
|
|
|
96
96
|
# density
|
|
97
97
|
# =======
|
|
98
98
|
|
|
99
|
-
def density(self, x=None, plot=False, latex=False, save=False):
|
|
99
|
+
def density(self, x=None, plot=False, latex=False, save=False, eig=None):
|
|
100
100
|
"""
|
|
101
101
|
Density of distribution.
|
|
102
102
|
|
|
@@ -123,6 +123,10 @@ class Wigner(object):
|
|
|
123
123
|
assumed to the save filename (with the file extension). This option
|
|
124
124
|
is relevant only if ``plot=True``.
|
|
125
125
|
|
|
126
|
+
eig : numpy.array, default=None
|
|
127
|
+
A collection of eigenvalues to compare to via histogram. This
|
|
128
|
+
option is relevant only if ``plot=True``.
|
|
129
|
+
|
|
126
130
|
Returns
|
|
127
131
|
-------
|
|
128
132
|
|
|
@@ -159,7 +163,11 @@ class Wigner(object):
|
|
|
159
163
|
numpy.sqrt(self.r**2 - x[mask]**2)
|
|
160
164
|
|
|
161
165
|
if plot:
|
|
162
|
-
|
|
166
|
+
if eig is not None:
|
|
167
|
+
label = 'Theoretical'
|
|
168
|
+
else:
|
|
169
|
+
label = ''
|
|
170
|
+
plot_density(x, rho, label=label, latex=latex, save=save, eig=eig)
|
|
163
171
|
|
|
164
172
|
return rho
|
|
165
173
|
|
freealg/eigfree.py
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright 2025, 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 ._util import compute_eig
|
|
16
|
+
from .freeform import FreeForm
|
|
17
|
+
|
|
18
|
+
__all__ = ['eigfree', 'condfree']
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# ========
|
|
22
|
+
# eig free
|
|
23
|
+
# ========
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def eigfree(A, N=None, psd=None, plots=False):
|
|
27
|
+
"""
|
|
28
|
+
Estimate the eigenvalues of a matrix.
|
|
29
|
+
|
|
30
|
+
This function estimates the eigenvalues of the matrix :math:`\\mathbf{A}`
|
|
31
|
+
or a larger matrix containing :math:`\\mathbf{A}` using free decompression.
|
|
32
|
+
|
|
33
|
+
Parameters
|
|
34
|
+
----------
|
|
35
|
+
|
|
36
|
+
A : numpy.ndarray
|
|
37
|
+
The symmetric real-valued matrix :math:`\\mathbf{A}` whose eigenvalues
|
|
38
|
+
(or those of a matrix containing :math:`\\mathbf{A}`) are to be
|
|
39
|
+
computed.
|
|
40
|
+
|
|
41
|
+
N : int, default=None
|
|
42
|
+
The size of the matrix containing :math:`\\mathbf{A}` to estimate
|
|
43
|
+
eigenvalues of. If None, returns estimates of the eigenvalues of
|
|
44
|
+
:math:`\\mathbf{A}` itself.
|
|
45
|
+
|
|
46
|
+
psd: bool, default=None
|
|
47
|
+
Determines whether the matrix is positive-semidefinite (PSD; all
|
|
48
|
+
eigenvalues are non-negative). If None, the matrix is considered PSD if
|
|
49
|
+
all sampled eigenvalues are positive.
|
|
50
|
+
|
|
51
|
+
plots : bool, default=False
|
|
52
|
+
Print out all relevant plots for diagnosing eigenvalue accuracy.
|
|
53
|
+
|
|
54
|
+
Notes
|
|
55
|
+
-----
|
|
56
|
+
|
|
57
|
+
This is a convenience function for the FreeForm class with some effective
|
|
58
|
+
defaults that work well for common random matrix ensembles. For improved
|
|
59
|
+
performance and plotting utilites, consider finetuning parameters using
|
|
60
|
+
the FreeForm class.
|
|
61
|
+
|
|
62
|
+
References
|
|
63
|
+
----------
|
|
64
|
+
|
|
65
|
+
.. [1] Reference.
|
|
66
|
+
|
|
67
|
+
Examples
|
|
68
|
+
--------
|
|
69
|
+
|
|
70
|
+
.. code-block:: python
|
|
71
|
+
|
|
72
|
+
>>> from freealg import condfree
|
|
73
|
+
>>> from freealg.distributions import MarchenkoPastur
|
|
74
|
+
>>> mp = MarchenkoPastur(1/50)
|
|
75
|
+
>>> A = mp.matrix(3000)
|
|
76
|
+
>>> eigs = eigfree(A)
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
if A.ndim != 2 or A.shape[0] != A.shape[1]:
|
|
80
|
+
raise RuntimeError("Only square matrices are permitted.")
|
|
81
|
+
n = A.shape[0]
|
|
82
|
+
|
|
83
|
+
if N is None:
|
|
84
|
+
N = n
|
|
85
|
+
|
|
86
|
+
# Size of sample matrix
|
|
87
|
+
n_s = int(80*(1 + numpy.log(n)))
|
|
88
|
+
# If matrix is not large enough, return eigenvalues
|
|
89
|
+
if n < n_s:
|
|
90
|
+
return compute_eig(A)
|
|
91
|
+
# Number of samples
|
|
92
|
+
num_samples = int(10 * (n / n_s)**0.5)
|
|
93
|
+
|
|
94
|
+
# Collect eigenvalue samples
|
|
95
|
+
samples = []
|
|
96
|
+
for _ in range(num_samples):
|
|
97
|
+
indices = numpy.random.choice(n, n_s, replace=False)
|
|
98
|
+
samples.append(compute_eig(A[numpy.ix_(indices, indices)]))
|
|
99
|
+
samples = numpy.concatenate(samples).ravel()
|
|
100
|
+
|
|
101
|
+
# If all eigenvalues are positive, set PSD flag
|
|
102
|
+
if psd is None:
|
|
103
|
+
psd = samples.min() > 0
|
|
104
|
+
|
|
105
|
+
ff = FreeForm(samples)
|
|
106
|
+
# Since we are resampling, we need to provide the correct matrix size
|
|
107
|
+
ff.n = n_s
|
|
108
|
+
|
|
109
|
+
# Perform fit and estimate eigenvalues
|
|
110
|
+
order = 1 + int(len(samples)**.2)
|
|
111
|
+
ff.fit(method='chebyshev', K=order, projection='sample',
|
|
112
|
+
force=True, plot=False, latex=False, save=False)
|
|
113
|
+
|
|
114
|
+
if plots:
|
|
115
|
+
ff.density(plot=True)
|
|
116
|
+
ff.stieltjes(plot=True)
|
|
117
|
+
|
|
118
|
+
_, _, eigs = ff.decompress(N, plot=plots)
|
|
119
|
+
|
|
120
|
+
if psd:
|
|
121
|
+
eigs = numpy.abs(eigs)
|
|
122
|
+
eigs.sort()
|
|
123
|
+
|
|
124
|
+
return eigs
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
# ========
|
|
128
|
+
# cond free
|
|
129
|
+
# ========
|
|
130
|
+
|
|
131
|
+
def condfree(A, N=None):
|
|
132
|
+
"""
|
|
133
|
+
Estimate the condition number of a positive-definite matrix.
|
|
134
|
+
|
|
135
|
+
This function estimates the condition number of the matrix
|
|
136
|
+
:math:`\\mathbf{A}` or a larger matrix containing :math:`\\mathbf{A}`
|
|
137
|
+
using free decompression.
|
|
138
|
+
|
|
139
|
+
Parameters
|
|
140
|
+
----------
|
|
141
|
+
|
|
142
|
+
A : numpy.ndarray
|
|
143
|
+
The symmetric real-valued matrix :math:`\\mathbf{A}` whose condition
|
|
144
|
+
number (or that of a matrix containing :math:`\\mathbf{A}`) are to be
|
|
145
|
+
computed.
|
|
146
|
+
|
|
147
|
+
N : int, default=None
|
|
148
|
+
The size of the matrix containing :math:`\\mathbf{A}` to estimate
|
|
149
|
+
eigenvalues of. If None, returns estimates of the eigenvalues of
|
|
150
|
+
:math:`\\mathbf{A}` itself.
|
|
151
|
+
|
|
152
|
+
Notes
|
|
153
|
+
-----
|
|
154
|
+
|
|
155
|
+
This is a convenience function using the eigfree procedure.
|
|
156
|
+
|
|
157
|
+
Examples
|
|
158
|
+
--------
|
|
159
|
+
|
|
160
|
+
.. code-block:: python
|
|
161
|
+
|
|
162
|
+
>>> from freealg import condfree
|
|
163
|
+
>>> from freealg.distributions import MarchenkoPastur
|
|
164
|
+
>>> mp = MarchenkoPastur(1/50)
|
|
165
|
+
>>> A = mp.matrix(3000)
|
|
166
|
+
>>> condfree(A)
|
|
167
|
+
"""
|
|
168
|
+
|
|
169
|
+
eigs = eigfree(A, N)
|
|
170
|
+
return eigs.max() / eigs.min()
|
freealg/freeform.py
CHANGED
|
@@ -28,7 +28,7 @@ from ._decompress import decompress
|
|
|
28
28
|
from ._sample import qmc_sample
|
|
29
29
|
from ._support import detect_support
|
|
30
30
|
|
|
31
|
-
__all__ = ['FreeForm'
|
|
31
|
+
__all__ = ['FreeForm']
|
|
32
32
|
|
|
33
33
|
|
|
34
34
|
# =========
|
|
@@ -75,15 +75,15 @@ class FreeForm(object):
|
|
|
75
75
|
Eigenvalues of the matrix
|
|
76
76
|
|
|
77
77
|
support: tuple
|
|
78
|
-
The predicted (or given) support :math:`(
|
|
79
|
-
eigenvalue density.
|
|
78
|
+
The predicted (or given) support :math:`(\\lambda_{\\min},
|
|
79
|
+
\\lambda_{\\max})` of the eigenvalue density.
|
|
80
80
|
|
|
81
81
|
psi : numpy.array
|
|
82
82
|
Jacobi coefficients.
|
|
83
83
|
|
|
84
|
-
n
|
|
85
|
-
|
|
86
|
-
|
|
84
|
+
n : int
|
|
85
|
+
Initial array size (assuming a square matrix when :math:`\\mathbf{A}` is
|
|
86
|
+
2D).
|
|
87
87
|
|
|
88
88
|
Methods
|
|
89
89
|
-------
|
|
@@ -390,10 +390,10 @@ class FreeForm(object):
|
|
|
390
390
|
x_supp = numpy.linspace(self.lam_m, self.lam_p, 1000)
|
|
391
391
|
g_supp = 2.0 * numpy.pi * self.hilbert(x_supp)
|
|
392
392
|
self._pade_sol = fit_pade(x_supp, g_supp, self.lam_m, self.lam_p,
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
393
|
+
p=pade_p, q=pade_q, odd_side=odd_side,
|
|
394
|
+
pade_reg=pade_reg, safety=1.0,
|
|
395
|
+
max_outer=40, xtol=1e-12, ftol=1e-12,
|
|
396
|
+
optimizer=optimizer, verbose=0)
|
|
397
397
|
|
|
398
398
|
if plot:
|
|
399
399
|
g_supp_approx = eval_pade(x_supp[None, :], self._pade_sol)[0, :]
|
|
@@ -449,7 +449,8 @@ class FreeForm(object):
|
|
|
449
449
|
"""
|
|
450
450
|
|
|
451
451
|
if self.psi is None:
|
|
452
|
-
raise RuntimeError('The spectral density needs to be fit using
|
|
452
|
+
raise RuntimeError('The spectral density needs to be fit using ' +
|
|
453
|
+
'the .fit() function.')
|
|
453
454
|
|
|
454
455
|
# Create x if not given
|
|
455
456
|
if x is None:
|
|
@@ -543,7 +544,8 @@ class FreeForm(object):
|
|
|
543
544
|
"""
|
|
544
545
|
|
|
545
546
|
if self.psi is None:
|
|
546
|
-
raise RuntimeError('The spectral density needs to be fit using
|
|
547
|
+
raise RuntimeError('The spectral density needs to be fit using ' +
|
|
548
|
+
'the .fit() function.')
|
|
547
549
|
|
|
548
550
|
# Create x if not given
|
|
549
551
|
if x is None:
|
|
@@ -605,8 +607,10 @@ class FreeForm(object):
|
|
|
605
607
|
|
|
606
608
|
def stieltjes(self, x=None, y=None, plot=False, latex=False, save=False):
|
|
607
609
|
"""
|
|
608
|
-
Compute Stieltjes transform of the spectral density
|
|
609
|
-
|
|
610
|
+
Compute Stieltjes transform of the spectral density on a grid.
|
|
611
|
+
|
|
612
|
+
This function evaluates Stieltjes transform on an array of points, or
|
|
613
|
+
over a 2D Cartesian grid on the complex plane.
|
|
610
614
|
|
|
611
615
|
Parameters
|
|
612
616
|
----------
|
|
@@ -665,11 +669,11 @@ class FreeForm(object):
|
|
|
665
669
|
"""
|
|
666
670
|
|
|
667
671
|
if self.psi is None:
|
|
668
|
-
raise RuntimeError('The spectral density needs to be fit using
|
|
669
|
-
|
|
672
|
+
raise RuntimeError('The spectral density needs to be fit using ' +
|
|
673
|
+
'the .fit() function.')
|
|
670
674
|
|
|
671
|
-
# Determine whether the Stieltjes transform is to be computed on
|
|
672
|
-
#
|
|
675
|
+
# Determine whether the Stieltjes transform is to be computed on a
|
|
676
|
+
# Cartesian grid
|
|
673
677
|
cartesian = plot | (y is not None)
|
|
674
678
|
|
|
675
679
|
# Create x if not given
|
|
@@ -693,8 +697,8 @@ class FreeForm(object):
|
|
|
693
697
|
z = x_grid + 1j * y_grid # shape (Ny, Nx)
|
|
694
698
|
else:
|
|
695
699
|
z = x
|
|
696
|
-
|
|
697
|
-
m1, m2 = self._eval_stieltjes(z)
|
|
700
|
+
|
|
701
|
+
m1, m2 = self._eval_stieltjes(z, branches=True)
|
|
698
702
|
|
|
699
703
|
if plot:
|
|
700
704
|
plot_stieltjes(x, y, m1, m2, self.support, latex=latex, save=save)
|
|
@@ -705,7 +709,7 @@ class FreeForm(object):
|
|
|
705
709
|
# eval stieltjes
|
|
706
710
|
# ==============
|
|
707
711
|
|
|
708
|
-
def _eval_stieltjes(self, z):
|
|
712
|
+
def _eval_stieltjes(self, z, branches=False):
|
|
709
713
|
"""
|
|
710
714
|
Compute Stieltjes transform of the spectral density.
|
|
711
715
|
|
|
@@ -716,12 +720,18 @@ class FreeForm(object):
|
|
|
716
720
|
The z values in the complex plan where the Stieltjes transform is
|
|
717
721
|
evaluated.
|
|
718
722
|
|
|
723
|
+
branches : bool, default = False
|
|
724
|
+
Return both the principal and secondary branches of the Stieltjes
|
|
725
|
+
transform. The default ``branches=False`` will return only
|
|
726
|
+
the secondary branch.
|
|
727
|
+
|
|
719
728
|
|
|
720
729
|
Returns
|
|
721
730
|
-------
|
|
722
731
|
|
|
723
732
|
m_p : numpy.ndarray
|
|
724
|
-
The Stieltjes transform on the principal branch
|
|
733
|
+
The Stieltjes transform on the principal branch if
|
|
734
|
+
``branches=True``.
|
|
725
735
|
|
|
726
736
|
m_m : numpy.ndarray
|
|
727
737
|
The Stieltjes transform continued to the secondary branch.
|
|
@@ -737,14 +747,15 @@ class FreeForm(object):
|
|
|
737
747
|
z = z.reshape(-1, 1)
|
|
738
748
|
|
|
739
749
|
# # Set the number of bases as the number of x points insides support
|
|
740
|
-
# mask_sup = numpy.logical_and(z.real >= self.lam_m,
|
|
750
|
+
# mask_sup = numpy.logical_and(z.real >= self.lam_m,
|
|
751
|
+
# z.real <= self.lam_p)
|
|
741
752
|
# n_base = 2 * numpy.sum(mask_sup)
|
|
742
753
|
|
|
743
754
|
# Stieltjes function
|
|
744
755
|
if self.method == 'jacobi':
|
|
745
756
|
stieltjes = partial(jacobi_stieltjes, psi=self.psi,
|
|
746
757
|
support=self.support, alpha=self.alpha,
|
|
747
|
-
beta=self.beta)
|
|
758
|
+
beta=self.beta) # n_base = n_base
|
|
748
759
|
elif self.method == 'chebyshev':
|
|
749
760
|
stieltjes = partial(chebyshev_stieltjes, psi=self.psi,
|
|
750
761
|
support=self.support)
|
|
@@ -760,31 +771,34 @@ class FreeForm(object):
|
|
|
760
771
|
m1[mask_p] = stieltjes(z[mask_p].reshape(-1, 1)).ravel()
|
|
761
772
|
|
|
762
773
|
# Lower half-plane, use Schwarz reflection
|
|
763
|
-
|
|
764
|
-
|
|
774
|
+
z_conj = numpy.conjugate(z[mask_m].reshape(-1, 1))
|
|
775
|
+
m1[mask_m] = numpy.conjugate(stieltjes(z_conj)).ravel()
|
|
765
776
|
|
|
766
777
|
# Second Riemann sheet
|
|
767
778
|
m2[mask_p] = m1[mask_p]
|
|
768
779
|
m2[mask_m] = -m1[mask_m] + self._glue(
|
|
769
780
|
z[mask_m].reshape(-1, 1)).ravel()
|
|
770
|
-
|
|
771
|
-
else:
|
|
772
|
-
m2[:] = stieltjes(z.reshape(-1,1)).reshape(*m2.shape)
|
|
773
|
-
m1[mask_p] = m2[mask_p]
|
|
774
|
-
m1[mask_m] = numpy.conjugate(
|
|
775
|
-
stieltjes(numpy.conjugate(z[mask_m].reshape(-1,1)))
|
|
776
|
-
).ravel()
|
|
777
781
|
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
782
|
+
else:
|
|
783
|
+
m2[:] = stieltjes(z.reshape(-1, 1)).reshape(*m2.shape)
|
|
784
|
+
if branches:
|
|
785
|
+
m1[mask_p] = m2[mask_p]
|
|
786
|
+
m1[mask_m] = numpy.conjugate(
|
|
787
|
+
stieltjes(numpy.conjugate(z[mask_m].reshape(-1, 1)))
|
|
788
|
+
).ravel()
|
|
789
|
+
|
|
790
|
+
if not branches:
|
|
791
|
+
return m2.reshape(*shape)
|
|
792
|
+
else:
|
|
793
|
+
m1, m2 = m1.reshape(*shape), m2.reshape(*shape)
|
|
794
|
+
return m1, m2
|
|
781
795
|
|
|
782
796
|
# ==========
|
|
783
797
|
# decompress
|
|
784
798
|
# ==========
|
|
785
799
|
|
|
786
800
|
def decompress(self, size, x=None, max_iter=500, eigvals=True,
|
|
787
|
-
|
|
801
|
+
tolerance=1e-9, seed=None, plot=False,
|
|
788
802
|
latex=False, save=False):
|
|
789
803
|
"""
|
|
790
804
|
Free decompression of spectral density.
|
|
@@ -805,9 +819,6 @@ class FreeForm(object):
|
|
|
805
819
|
eigvals: bool, default=True
|
|
806
820
|
Return estimated (sampled) eigenvalues as well as the density.
|
|
807
821
|
|
|
808
|
-
step_size: float, default=0.1
|
|
809
|
-
Step size for Newton iterations.
|
|
810
|
-
|
|
811
822
|
tolerance: float, default=1e-9
|
|
812
823
|
Tolerance for the solution obtained by the Newton solver. Also
|
|
813
824
|
used for the finite difference approximation to the derivative.
|
|
@@ -880,14 +891,15 @@ class FreeForm(object):
|
|
|
880
891
|
else:
|
|
881
892
|
return x, rho
|
|
882
893
|
|
|
883
|
-
|
|
894
|
+
|
|
895
|
+
def eigfree(A, N=None, psd=None, plots=False):
|
|
884
896
|
"""
|
|
885
897
|
Estimate the eigenvalues of a matrix :math:`\\mathbf{A}` or a larger matrix
|
|
886
898
|
containing :math:`\\mathbf{A}` using free decompression.
|
|
887
899
|
|
|
888
900
|
This is a convenience function for the FreeForm class with some effective
|
|
889
901
|
defaults that work well for common random matrix ensembles. For improved
|
|
890
|
-
performance and plotting utilites, consider finetuning parameters using
|
|
902
|
+
performance and plotting utilites, consider finetuning parameters using
|
|
891
903
|
the FreeForm class.
|
|
892
904
|
|
|
893
905
|
Parameters
|
|
@@ -895,18 +907,22 @@ def eigfree(A, N = None, psd = None):
|
|
|
895
907
|
|
|
896
908
|
A : numpy.ndarray
|
|
897
909
|
The symmetric real-valued matrix :math:`\\mathbf{A}` whose eigenvalues
|
|
898
|
-
(or those of a matrix containing :math:`\\mathbf{A}`) are to be
|
|
910
|
+
(or those of a matrix containing :math:`\\mathbf{A}`) are to be
|
|
911
|
+
computed.
|
|
899
912
|
|
|
900
913
|
N : int, default=None
|
|
901
914
|
The size of the matrix containing :math:`\\mathbf{A}` to estimate
|
|
902
915
|
eigenvalues of. If None, returns estimates of the eigenvalues of
|
|
903
916
|
:math:`\\mathbf{A}` itself.
|
|
904
917
|
|
|
905
|
-
psd: bool, default=None
|
|
906
|
-
Determines whether the matrix is positive-semidefinite (PSD; all
|
|
918
|
+
psd : bool, default=None
|
|
919
|
+
Determines whether the matrix is positive-semidefinite (PSD; all
|
|
907
920
|
eigenvalues are non-negative). If None, the matrix is considered PSD if
|
|
908
921
|
all sampled eigenvalues are positive.
|
|
909
922
|
|
|
923
|
+
plots : bool, default=False
|
|
924
|
+
Print out all relevant plots for diagnosing eigenvalue accuracy.
|
|
925
|
+
|
|
910
926
|
Notes
|
|
911
927
|
-----
|
|
912
928
|
|
|
@@ -924,20 +940,24 @@ def eigfree(A, N = None, psd = None):
|
|
|
924
940
|
|
|
925
941
|
>>> from freealg import FreeForm
|
|
926
942
|
"""
|
|
943
|
+
if A.ndim != 2 or A.shape[0] != A.shape[1]:
|
|
944
|
+
raise RuntimeError("Only square matrices are permitted.")
|
|
927
945
|
n = A.shape[0]
|
|
928
|
-
|
|
946
|
+
|
|
947
|
+
if N is None:
|
|
948
|
+
N = n
|
|
949
|
+
|
|
929
950
|
# Size of sample matrix
|
|
930
951
|
n_s = int(80*(1 + numpy.log(n)))
|
|
931
|
-
|
|
932
952
|
# If matrix is not large enough, return eigenvalues
|
|
933
953
|
if n < n_s:
|
|
934
954
|
return compute_eig(A)
|
|
935
|
-
|
|
936
|
-
if N is None:
|
|
937
|
-
N = n
|
|
938
|
-
|
|
939
955
|
# Number of samples
|
|
940
956
|
num_samples = int(10 * (n / n_s)**0.5)
|
|
957
|
+
# else:
|
|
958
|
+
# # Use the entire matrix given
|
|
959
|
+
# n_s = n
|
|
960
|
+
# num_samples = 1
|
|
941
961
|
|
|
942
962
|
# Collect eigenvalue samples
|
|
943
963
|
samples = []
|
|
@@ -956,12 +976,17 @@ def eigfree(A, N = None, psd = None):
|
|
|
956
976
|
|
|
957
977
|
# Perform fit and estimate eigenvalues
|
|
958
978
|
order = 1 + int(len(samples)**.2)
|
|
959
|
-
ff.fit(method='chebyshev', K=order, projection='sample',
|
|
960
|
-
force=True, plot=False, latex=False, save=False
|
|
961
|
-
|
|
979
|
+
ff.fit(method='chebyshev', K=order, projection='sample',
|
|
980
|
+
force=True, plot=False, latex=False, save=False)
|
|
981
|
+
|
|
982
|
+
if plots:
|
|
983
|
+
ff.density(plot=True)
|
|
984
|
+
ff.stieltjes(plot=True)
|
|
985
|
+
|
|
986
|
+
_, _, eigs = ff.decompress(N, plot=plots)
|
|
962
987
|
|
|
963
988
|
if psd:
|
|
964
989
|
eigs = numpy.abs(eigs)
|
|
965
990
|
eigs.sort()
|
|
966
991
|
|
|
967
|
-
return eigs
|
|
992
|
+
return eigs
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: freealg
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.15
|
|
4
4
|
Summary: Free probability for large matrices
|
|
5
5
|
Keywords: leaderboard bot chat
|
|
6
6
|
Platform: Linux
|
|
@@ -50,6 +50,12 @@ Dynamic: summary
|
|
|
50
50
|
:width: 240
|
|
51
51
|
:class: custom-dark
|
|
52
52
|
|
|
53
|
+
`Paper <https://arxiv.org/abs/2506.11994>`__ |
|
|
54
|
+
`Slides <https://www.dropbox.com/scl/fi/03gjuyz17k9yhsqy0isoz/free_decomporession_slides.pdf?rlkey=8f82mhciyl2ju02l7hv1md5li&st=26xmhjga&dl=0>`__ |
|
|
55
|
+
`Docs <https://ameli.github.io/freealg>`__
|
|
56
|
+
|
|
57
|
+
.. `Slides <https://ameli.github.io/freealg/_static/data/slides.pdf>`__ |
|
|
58
|
+
|
|
53
59
|
*freealg* is a Python package that employs **free** probability to evaluate the spectral
|
|
54
60
|
densities of large matrix **form**\ s. The fundamental algorithm employed by *freealg* is
|
|
55
61
|
**free decompression**, which extrapolates from the empirical spectral densities of small
|
|
@@ -120,15 +126,19 @@ requests and bug reports.
|
|
|
120
126
|
How to Cite
|
|
121
127
|
===========
|
|
122
128
|
|
|
123
|
-
If you use this work, please cite the `arXiv paper <https://arxiv.org/abs/2506.11994
|
|
129
|
+
If you use this work, please cite the `arXiv paper <https://arxiv.org/abs/2506.11994>`__.
|
|
124
130
|
|
|
125
131
|
.. code::
|
|
126
132
|
|
|
127
|
-
@article{
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
133
|
+
@article{spectral2025,
|
|
134
|
+
title={Spectral Estimation with Free Decompression},
|
|
135
|
+
author={Siavash Ameli and Chris van der Heide and Liam Hodgkinson and Michael W. Mahoney},
|
|
136
|
+
year={2025},
|
|
137
|
+
eprint={2506.11994},
|
|
138
|
+
archivePrefix={arXiv},
|
|
139
|
+
primaryClass={stat.ML},
|
|
140
|
+
url={https://arxiv.org/abs/2506.11994},
|
|
141
|
+
journal={arXiv preprint arXiv:2506.11994},
|
|
132
142
|
}
|
|
133
143
|
|
|
134
144
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
freealg/__init__.py,sha256=oYfXRQgu--OhZAY9cIVdfEHDVz6XQmqzTsIqIFAlPws,570
|
|
2
|
+
freealg/__version__.py,sha256=qb0TalpSt1CbprnFyeLUKqgrqNtmnk9IoQQ7umAoXVY,23
|
|
3
|
+
freealg/_chebyshev.py,sha256=dsAj3YEpmkzB65smluZ0Fi5IZSdpnQXBSIuKMg19grA,5523
|
|
4
|
+
freealg/_damp.py,sha256=k2vtBtWOxQBf4qXaWu_En81lQBXbEO4QbxxWpvuVhdE,1802
|
|
5
|
+
freealg/_decompress.py,sha256=Q19AmpqxBWcrbFSniDOnh9ethOLm5_7bqDLXUNGCxk4,10372
|
|
6
|
+
freealg/_jacobi.py,sha256=AT4ONSHGGDxVKE3MGMLyMR8uDFiO-e9u3x5udYfdJJk,5635
|
|
7
|
+
freealg/_pade.py,sha256=wer31W6c6lDCag4THKGdW69r5d7uimsH_Y53wJih144,15185
|
|
8
|
+
freealg/_plot_util.py,sha256=U4alp7Pzg315_7jJdu1UB0tIUcxUovQgHDHsUYoa2Z0,19728
|
|
9
|
+
freealg/_sample.py,sha256=ckC75eqv-mRP1F5BnhvsjfLTaoAzHK8bebl9bCRZYDo,2561
|
|
10
|
+
freealg/_support.py,sha256=LIM_VWH8TzLJlp_q5A0ql-xawPUNyH2YI9ZKBlHHuzo,6122
|
|
11
|
+
freealg/_util.py,sha256=PWLXcsTb0-FinGWvNiY12D-f4CHQB5bP_W3ThqfY4FY,3681
|
|
12
|
+
freealg/eigfree.py,sha256=ssS6t6b5wSgZNOo3ydA0MxvnhJivbYH8bwdKNmD81FE,4571
|
|
13
|
+
freealg/freeform.py,sha256=j1oECmOyc-v6o7bIrbW844QZqAG6hJm6NjGvLXRMvEo,31263
|
|
14
|
+
freealg/distributions/__init__.py,sha256=t_yZyEkW_W_tSV9IvgYXtVASxD2BEdiNVXcV2ebMy8M,579
|
|
15
|
+
freealg/distributions/_kesten_mckay.py,sha256=210RF2OQEYLZBeLB6wmbdHnZPs_9ldDNHm_FMlg5tis,19881
|
|
16
|
+
freealg/distributions/_marchenko_pastur.py,sha256=kchFccRMuVF2Cus_99vdEwuRimkHzEUV8xt5kZFg7ZI,16994
|
|
17
|
+
freealg/distributions/_meixner.py,sha256=ws7t_EUa7V0s97dgMQIJLv1b6qMLqf9fLLbTJQudf_8,17512
|
|
18
|
+
freealg/distributions/_wachter.py,sha256=Hna_MXqAPjuRkeilLPMf4Xg_3C6tTu5oZLEQnA-RuE4,16897
|
|
19
|
+
freealg/distributions/_wigner.py,sha256=SxgPLtvIVBi9m4De-oBD0x6-2Je_eBqpDrpDYcoLuis,15871
|
|
20
|
+
freealg-0.1.15.dist-info/licenses/AUTHORS.txt,sha256=0b67Nz4_JgIzUupHJTAZxu5QdSUM_HRM_X_w4xCb17o,30
|
|
21
|
+
freealg-0.1.15.dist-info/licenses/LICENSE.txt,sha256=J-EEYEtxb3VVf_Bn1TYfWnpY5lMFIM15iLDDcnaDTPA,1443
|
|
22
|
+
freealg-0.1.15.dist-info/METADATA,sha256=mvdtAxG-4t3-rThaz_lqpG3B_Ca8Z2LrpekEocrxVRk,4497
|
|
23
|
+
freealg-0.1.15.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
24
|
+
freealg-0.1.15.dist-info/top_level.txt,sha256=eR2wrgYwDdnnJ9Zf5PruPqe4kQav0GMvRsqct6y00Q8,8
|
|
25
|
+
freealg-0.1.15.dist-info/RECORD,,
|
freealg-0.1.13.dist-info/RECORD
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
freealg/__init__.py,sha256=YqewBd3fq4nm-L3oGcExhEDR2wtVcrtggkSGzfpDqr4,528
|
|
2
|
-
freealg/__version__.py,sha256=khDKUuWafURKVs5EAZkpOMiUHI2-V7axlqrWLPUpuZo,23
|
|
3
|
-
freealg/_chebyshev.py,sha256=oDJtRCwKEHazitzVBDbfdQz1jkyfsJOJfcAfo-PNkNo,5745
|
|
4
|
-
freealg/_damp.py,sha256=k2vtBtWOxQBf4qXaWu_En81lQBXbEO4QbxxWpvuVhdE,1802
|
|
5
|
-
freealg/_decompress.py,sha256=vTnxV_7XPYUVVnWCnALCrZkQT77IAPm_CzrWMgiTiqg,7986
|
|
6
|
-
freealg/_jacobi.py,sha256=AT4ONSHGGDxVKE3MGMLyMR8uDFiO-e9u3x5udYfdJJk,5635
|
|
7
|
-
freealg/_pade.py,sha256=yREJYSmnWaVUNRBNxjuQUqeLe_XSaGa9_VzV6HG5RkA,15164
|
|
8
|
-
freealg/_plot_util.py,sha256=BOYre8FPhrxmW1VRj3I40dCjWTFqUBTInmXc3wFunKQ,19648
|
|
9
|
-
freealg/_sample.py,sha256=ckC75eqv-mRP1F5BnhvsjfLTaoAzHK8bebl9bCRZYDo,2561
|
|
10
|
-
freealg/_support.py,sha256=A8hUjfKnSkHm09KLcEkeEXeTieKjhH-sVPd7I3_p4VE,3100
|
|
11
|
-
freealg/_util.py,sha256=PWLXcsTb0-FinGWvNiY12D-f4CHQB5bP_W3ThqfY4FY,3681
|
|
12
|
-
freealg/freeform.py,sha256=_HDgchJaeryUTgywobSM4Yr8SjXi6pRVG8kQkTKDdMM,30375
|
|
13
|
-
freealg/distributions/__init__.py,sha256=t_yZyEkW_W_tSV9IvgYXtVASxD2BEdiNVXcV2ebMy8M,579
|
|
14
|
-
freealg/distributions/_kesten_mckay.py,sha256=HDMjbM1AcNxlwrpYeGmRqcbP10QsLI5RCeKvjVK3tOk,19566
|
|
15
|
-
freealg/distributions/_marchenko_pastur.py,sha256=th921hlEEtTbnHnRyBgT54a_e-9ZzAl9rB78O9FjorY,16688
|
|
16
|
-
freealg/distributions/_meixner.py,sha256=ItE0zYG2vhyUkObxbx4bDZaJ0BHVQWPzAJGLdMz10l4,17206
|
|
17
|
-
freealg/distributions/_wachter.py,sha256=lw70PT3TZlCf7mHU8IqoygXFUWB4IL57obkng0_ZGeI,16591
|
|
18
|
-
freealg/distributions/_wigner.py,sha256=2ZSPjgmDr9q9qiz6jO6yhXFo4ALHfxK1f0EzolzhRNE,15565
|
|
19
|
-
freealg-0.1.13.dist-info/licenses/AUTHORS.txt,sha256=0b67Nz4_JgIzUupHJTAZxu5QdSUM_HRM_X_w4xCb17o,30
|
|
20
|
-
freealg-0.1.13.dist-info/licenses/LICENSE.txt,sha256=J-EEYEtxb3VVf_Bn1TYfWnpY5lMFIM15iLDDcnaDTPA,1443
|
|
21
|
-
freealg-0.1.13.dist-info/METADATA,sha256=83nEmVJt6xwHPu6o0qlYN0JQ_zYuqgIPgQBvHsAdvkE,4029
|
|
22
|
-
freealg-0.1.13.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
23
|
-
freealg-0.1.13.dist-info/top_level.txt,sha256=eR2wrgYwDdnnJ9Zf5PruPqe4kQav0GMvRsqct6y00Q8,8
|
|
24
|
-
freealg-0.1.13.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|