freealg 0.1.11__py3-none-any.whl → 0.7.12__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 +8 -2
- freealg/__version__.py +1 -1
- freealg/_algebraic_form/__init__.py +12 -0
- freealg/_algebraic_form/_branch_points.py +288 -0
- freealg/_algebraic_form/_constraints.py +139 -0
- freealg/_algebraic_form/_continuation_algebraic.py +706 -0
- freealg/_algebraic_form/_decompress.py +641 -0
- freealg/_algebraic_form/_decompress2.py +204 -0
- freealg/_algebraic_form/_edge.py +330 -0
- freealg/_algebraic_form/_homotopy.py +323 -0
- freealg/_algebraic_form/_moments.py +448 -0
- freealg/_algebraic_form/_sheets_util.py +145 -0
- freealg/_algebraic_form/_support.py +309 -0
- freealg/_algebraic_form/algebraic_form.py +1232 -0
- freealg/_free_form/__init__.py +16 -0
- freealg/{_chebyshev.py → _free_form/_chebyshev.py} +75 -43
- freealg/_free_form/_decompress.py +993 -0
- freealg/_free_form/_density_util.py +243 -0
- freealg/_free_form/_jacobi.py +359 -0
- freealg/_free_form/_linalg.py +508 -0
- freealg/{_pade.py → _free_form/_pade.py} +42 -208
- freealg/{_plot_util.py → _free_form/_plot_util.py} +37 -22
- freealg/{_sample.py → _free_form/_sample.py} +58 -22
- freealg/_free_form/_series.py +454 -0
- freealg/_free_form/_support.py +214 -0
- freealg/_free_form/free_form.py +1362 -0
- freealg/_geometric_form/__init__.py +13 -0
- freealg/_geometric_form/_continuation_genus0.py +175 -0
- freealg/_geometric_form/_continuation_genus1.py +275 -0
- freealg/_geometric_form/_elliptic_functions.py +174 -0
- freealg/_geometric_form/_sphere_maps.py +63 -0
- freealg/_geometric_form/_torus_maps.py +118 -0
- freealg/_geometric_form/geometric_form.py +1094 -0
- freealg/_util.py +56 -110
- freealg/distributions/__init__.py +7 -1
- freealg/distributions/_chiral_block.py +494 -0
- freealg/distributions/_deformed_marchenko_pastur.py +726 -0
- freealg/distributions/_deformed_wigner.py +386 -0
- freealg/distributions/_kesten_mckay.py +29 -15
- freealg/distributions/_marchenko_pastur.py +224 -95
- freealg/distributions/_meixner.py +47 -37
- freealg/distributions/_wachter.py +29 -17
- freealg/distributions/_wigner.py +27 -14
- freealg/visualization/__init__.py +12 -0
- freealg/visualization/_glue_util.py +32 -0
- freealg/visualization/_rgb_hsv.py +125 -0
- freealg-0.7.12.dist-info/METADATA +172 -0
- freealg-0.7.12.dist-info/RECORD +53 -0
- {freealg-0.1.11.dist-info → freealg-0.7.12.dist-info}/WHEEL +1 -1
- freealg/_decompress.py +0 -180
- freealg/_jacobi.py +0 -218
- freealg/_support.py +0 -85
- freealg/freeform.py +0 -967
- freealg-0.1.11.dist-info/METADATA +0 -140
- freealg-0.1.11.dist-info/RECORD +0 -24
- /freealg/{_damp.py → _free_form/_damp.py} +0 -0
- {freealg-0.1.11.dist-info → freealg-0.7.12.dist-info}/licenses/AUTHORS.txt +0 -0
- {freealg-0.1.11.dist-info → freealg-0.7.12.dist-info}/licenses/LICENSE.txt +0 -0
- {freealg-0.1.11.dist-info → freealg-0.7.12.dist-info}/top_level.txt +0 -0
freealg/_decompress.py
DELETED
|
@@ -1,180 +0,0 @@
|
|
|
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
|
-
|
|
13
|
-
import numpy
|
|
14
|
-
# from scipy.integrate import solve_ivp
|
|
15
|
-
|
|
16
|
-
__all__ = ['decompress', 'reverse_characteristics']
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
# ==========
|
|
20
|
-
# decompress
|
|
21
|
-
# ==========
|
|
22
|
-
|
|
23
|
-
def decompress(freeform, size, x=None, delta=1e-4, iterations=500,
|
|
24
|
-
step_size=0.1, tolerance=1e-4):
|
|
25
|
-
"""
|
|
26
|
-
Free decompression of spectral density.
|
|
27
|
-
|
|
28
|
-
Parameters
|
|
29
|
-
----------
|
|
30
|
-
|
|
31
|
-
freeform : FreeForm
|
|
32
|
-
The initial freeform object of matrix to be decompressed
|
|
33
|
-
|
|
34
|
-
size : int
|
|
35
|
-
Size of the decompressed matrix.
|
|
36
|
-
|
|
37
|
-
x : numpy.array, default=None
|
|
38
|
-
Positions where density to be evaluated at. If `None`, an interval
|
|
39
|
-
slightly larger than the support interval will be used.
|
|
40
|
-
|
|
41
|
-
delta: float, default=1e-4
|
|
42
|
-
Size of the perturbation into the upper half plane for Plemelj's
|
|
43
|
-
formula.
|
|
44
|
-
|
|
45
|
-
iterations: int, default=500
|
|
46
|
-
Maximum number of Newton iterations.
|
|
47
|
-
|
|
48
|
-
step_size: float, default=0.1
|
|
49
|
-
Step size for Newton iterations.
|
|
50
|
-
|
|
51
|
-
tolerance: float, default=1e-4
|
|
52
|
-
Tolerance for the solution obtained by the Newton solver. Also
|
|
53
|
-
used for the finite difference approximation to the derivative.
|
|
54
|
-
|
|
55
|
-
Returns
|
|
56
|
-
-------
|
|
57
|
-
|
|
58
|
-
rho : numpy.array
|
|
59
|
-
Spectral density
|
|
60
|
-
|
|
61
|
-
See Also
|
|
62
|
-
--------
|
|
63
|
-
|
|
64
|
-
density
|
|
65
|
-
stieltjes
|
|
66
|
-
|
|
67
|
-
Notes
|
|
68
|
-
-----
|
|
69
|
-
|
|
70
|
-
Work in progress.
|
|
71
|
-
|
|
72
|
-
References
|
|
73
|
-
----------
|
|
74
|
-
|
|
75
|
-
.. [1] tbd
|
|
76
|
-
|
|
77
|
-
Examples
|
|
78
|
-
--------
|
|
79
|
-
|
|
80
|
-
.. code-block:: python
|
|
81
|
-
|
|
82
|
-
>>> from freealg import FreeForm
|
|
83
|
-
"""
|
|
84
|
-
|
|
85
|
-
alpha = size / freeform.n
|
|
86
|
-
m = freeform._eval_stieltjes
|
|
87
|
-
# Lower and upper bound on new support
|
|
88
|
-
hilb_lb = (1 / m(freeform.lam_m + delta * 1j)[1]).real
|
|
89
|
-
hilb_ub = (1 / m(freeform.lam_p + delta * 1j)[1]).real
|
|
90
|
-
lb = freeform.lam_m - (alpha - 1) * hilb_lb
|
|
91
|
-
ub = freeform.lam_p - (alpha - 1) * hilb_ub
|
|
92
|
-
|
|
93
|
-
# Create x if not given
|
|
94
|
-
if x is None:
|
|
95
|
-
radius = 0.5 * (ub - lb)
|
|
96
|
-
center = 0.5 * (ub + lb)
|
|
97
|
-
scale = 1.25
|
|
98
|
-
x_min = numpy.floor(center - radius * scale)
|
|
99
|
-
x_max = numpy.ceil(center + radius * scale)
|
|
100
|
-
x = numpy.linspace(x_min, x_max, 500)
|
|
101
|
-
|
|
102
|
-
def _char_z(z):
|
|
103
|
-
return z + (1 / m(z)[1]) * (1 - alpha)
|
|
104
|
-
|
|
105
|
-
# Ensure that input is an array
|
|
106
|
-
x = numpy.asarray(x)
|
|
107
|
-
|
|
108
|
-
target = x + delta * 1j
|
|
109
|
-
|
|
110
|
-
z = numpy.full(target.shape, numpy.mean(freeform.support) - .1j,
|
|
111
|
-
dtype=numpy.complex128)
|
|
112
|
-
|
|
113
|
-
# Broken Newton steps can produce a lot of warnings. Removing them
|
|
114
|
-
# for now.
|
|
115
|
-
with numpy.errstate(all='ignore'):
|
|
116
|
-
for _ in range(iterations):
|
|
117
|
-
objective = _char_z(z) - target
|
|
118
|
-
mask = numpy.abs(objective) >= tolerance
|
|
119
|
-
if not numpy.any(mask):
|
|
120
|
-
break
|
|
121
|
-
z_m = z[mask]
|
|
122
|
-
|
|
123
|
-
# Perform finite difference approximation
|
|
124
|
-
dfdz = _char_z(z_m+tolerance) - _char_z(z_m-tolerance)
|
|
125
|
-
dfdz /= 2*tolerance
|
|
126
|
-
dfdz[dfdz == 0] = 1.0
|
|
127
|
-
|
|
128
|
-
# Perform Newton step
|
|
129
|
-
z[mask] = z_m - step_size * objective[mask] / dfdz
|
|
130
|
-
|
|
131
|
-
# Plemelj's formula
|
|
132
|
-
char_s = m(z)[1] / alpha
|
|
133
|
-
rho = numpy.maximum(0, char_s.imag / numpy.pi)
|
|
134
|
-
rho[numpy.isnan(rho) | numpy.isinf(rho)] = 0
|
|
135
|
-
rho = rho.reshape(*x.shape)
|
|
136
|
-
|
|
137
|
-
return rho, x, (lb, ub)
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
# =======================
|
|
141
|
-
# reverse characteristics
|
|
142
|
-
# =======================
|
|
143
|
-
|
|
144
|
-
def reverse_characteristics(freeform, z_inits, T, iterations=500,
|
|
145
|
-
step_size=0.1, tolerance=1e-8):
|
|
146
|
-
"""
|
|
147
|
-
"""
|
|
148
|
-
|
|
149
|
-
t_span = (0, T)
|
|
150
|
-
t_eval = numpy.linspace(t_span[0], t_span[1], 50)
|
|
151
|
-
|
|
152
|
-
m = freeform._eval_stieltjes
|
|
153
|
-
|
|
154
|
-
def _char_z(z, t):
|
|
155
|
-
return z + (1 / m(z)[1]) * (1 - numpy.exp(t))
|
|
156
|
-
|
|
157
|
-
target_z, target_t = numpy.meshgrid(z_inits, t_eval)
|
|
158
|
-
|
|
159
|
-
z = numpy.full(target_z.shape, numpy.mean(freeform.support) - .1j,
|
|
160
|
-
dtype=numpy.complex128)
|
|
161
|
-
|
|
162
|
-
# Broken Newton steps can produce a lot of warnings. Removing them for now.
|
|
163
|
-
with numpy.errstate(all='ignore'):
|
|
164
|
-
for _ in range(iterations):
|
|
165
|
-
objective = _char_z(z, target_t) - target_z
|
|
166
|
-
mask = numpy.abs(objective) >= tolerance
|
|
167
|
-
if not numpy.any(mask):
|
|
168
|
-
break
|
|
169
|
-
z_m = z[mask]
|
|
170
|
-
t_m = target_t[mask]
|
|
171
|
-
|
|
172
|
-
# Perform finite difference approximation
|
|
173
|
-
dfdz = _char_z(z_m+tolerance, t_m) - _char_z(z_m-tolerance, t_m)
|
|
174
|
-
dfdz /= 2*tolerance
|
|
175
|
-
dfdz[dfdz == 0] = 1.0
|
|
176
|
-
|
|
177
|
-
# Perform Newton step
|
|
178
|
-
z[mask] = z_m - step_size * objective[mask] / dfdz
|
|
179
|
-
|
|
180
|
-
return z
|
freealg/_jacobi.py
DELETED
|
@@ -1,218 +0,0 @@
|
|
|
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 scipy.special import eval_jacobi, roots_jacobi
|
|
16
|
-
from scipy.special import gammaln, beta as Beta
|
|
17
|
-
|
|
18
|
-
__all__ = ['jacobi_sample_proj', 'jacobi_kernel_proj', 'jacobi_approx',
|
|
19
|
-
'jacobi_stieltjes']
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
# ==============
|
|
23
|
-
# jacobi sq norm
|
|
24
|
-
# ==============
|
|
25
|
-
|
|
26
|
-
def jacobi_sq_norm(k, alpha, beta):
|
|
27
|
-
"""
|
|
28
|
-
Norm of P_k
|
|
29
|
-
Special-case k = 0 to avoid gamma(0) issues when alpha + beta + 1 = 0.
|
|
30
|
-
"""
|
|
31
|
-
|
|
32
|
-
if k == 0:
|
|
33
|
-
return 2.0**(alpha + beta + 1) * Beta(alpha + 1, beta + 1)
|
|
34
|
-
|
|
35
|
-
# Use logs instead to avoid overflow in gamma function.
|
|
36
|
-
lg_num = (alpha + beta + 1) * numpy.log(2.0) \
|
|
37
|
-
+ gammaln(k + alpha + 1) \
|
|
38
|
-
+ gammaln(k + beta + 1)
|
|
39
|
-
|
|
40
|
-
lg_den = numpy.log(2*k + alpha + beta + 1) \
|
|
41
|
-
+ gammaln(k + 1) \
|
|
42
|
-
+ gammaln(k + alpha + beta + 1)
|
|
43
|
-
|
|
44
|
-
return numpy.exp(lg_num - lg_den)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
# ==================
|
|
48
|
-
# jacobi sample proj
|
|
49
|
-
# ==================
|
|
50
|
-
|
|
51
|
-
def jacobi_sample_proj(eig, support, K=10, alpha=0.0, beta=0.0, reg=0.0):
|
|
52
|
-
"""
|
|
53
|
-
"""
|
|
54
|
-
|
|
55
|
-
lam_m, lam_p = support
|
|
56
|
-
|
|
57
|
-
# Convert to [-1, 1] interval
|
|
58
|
-
x = (2.0 * eig - (lam_p + lam_m)) / (lam_p - lam_m)
|
|
59
|
-
|
|
60
|
-
psi = numpy.empty(K + 1)
|
|
61
|
-
|
|
62
|
-
# Empirical moments and coefficients
|
|
63
|
-
for k in range(K + 1):
|
|
64
|
-
moment = numpy.mean(eval_jacobi(k, alpha, beta, x))
|
|
65
|
-
N_k = jacobi_sq_norm(k, alpha, beta) # normalization
|
|
66
|
-
|
|
67
|
-
if k == 0:
|
|
68
|
-
# Do not penalize at k=0, as this keeps unit mass.
|
|
69
|
-
# k=0 has unit mass, while k>0 has zero mass by orthogonality.
|
|
70
|
-
penalty = 0
|
|
71
|
-
else:
|
|
72
|
-
penalty = reg * (k / (K + 1))**2
|
|
73
|
-
|
|
74
|
-
# Add regularization on the diagonal
|
|
75
|
-
psi[k] = moment / (N_k + penalty)
|
|
76
|
-
|
|
77
|
-
return psi
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
# ==================
|
|
81
|
-
# jacobi kernel proj
|
|
82
|
-
# ==================
|
|
83
|
-
|
|
84
|
-
def jacobi_kernel_proj(xs, pdf, support, K=10, alpha=0.0, beta=0.0, reg=0.0):
|
|
85
|
-
"""
|
|
86
|
-
Same moments as `jacobi_proj`, but the target is a *continuous* density
|
|
87
|
-
given on a grid (xs, pdf).
|
|
88
|
-
"""
|
|
89
|
-
|
|
90
|
-
lam_m, lam_p = support
|
|
91
|
-
t = (2.0 * xs - (lam_p + lam_m)) / (lam_p - lam_m) # map to [-1,1]
|
|
92
|
-
psi = numpy.empty(K + 1)
|
|
93
|
-
|
|
94
|
-
for k in range(K + 1):
|
|
95
|
-
Pk = eval_jacobi(k, alpha, beta, t)
|
|
96
|
-
N_k = jacobi_sq_norm(k, alpha, beta)
|
|
97
|
-
|
|
98
|
-
# \int P_k(t) w(t) \rho(t) dt. w(t) cancels with pdf already being rho
|
|
99
|
-
moment = numpy.trapz(Pk * pdf, xs)
|
|
100
|
-
|
|
101
|
-
if k == 0:
|
|
102
|
-
penalty = 0
|
|
103
|
-
else:
|
|
104
|
-
penalty = reg * (k / (K + 1))**2
|
|
105
|
-
|
|
106
|
-
psi[k] = moment / (N_k + penalty)
|
|
107
|
-
|
|
108
|
-
return psi
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
# =============
|
|
112
|
-
# jacobi approx
|
|
113
|
-
# =============
|
|
114
|
-
|
|
115
|
-
def jacobi_approx(x, psi, support, alpha=0.0, beta=0.0):
|
|
116
|
-
"""
|
|
117
|
-
Reconstruct Jacobi approximation.
|
|
118
|
-
|
|
119
|
-
Parameters
|
|
120
|
-
----------
|
|
121
|
-
|
|
122
|
-
psi : array_like, shape (K+1, )
|
|
123
|
-
Jacobi expansion coefficients.
|
|
124
|
-
|
|
125
|
-
x : array_like
|
|
126
|
-
Points (in original eigenvalue scale) to evaluate at.
|
|
127
|
-
|
|
128
|
-
support : tuple (lam_m, lam_p)
|
|
129
|
-
|
|
130
|
-
alpha : float
|
|
131
|
-
Jacobi parameter.
|
|
132
|
-
|
|
133
|
-
beta : float
|
|
134
|
-
Jacobi parameter.
|
|
135
|
-
|
|
136
|
-
Returns
|
|
137
|
-
-------
|
|
138
|
-
|
|
139
|
-
rho : ndarray
|
|
140
|
-
"""
|
|
141
|
-
|
|
142
|
-
lam_m, lam_p = support
|
|
143
|
-
t = (2 * x - (lam_p + lam_m)) / (lam_p - lam_m)
|
|
144
|
-
w = (1 - t)**alpha * (1 + t)**beta
|
|
145
|
-
P = numpy.vstack([eval_jacobi(k, alpha, beta, t) for k in range(len(psi))])
|
|
146
|
-
|
|
147
|
-
rho_t = w * (psi @ P) # density in t–variable
|
|
148
|
-
rho_x = rho_t * (2.0 / (lam_p - lam_m)) # back to x–variable
|
|
149
|
-
|
|
150
|
-
return rho_x
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
# ================
|
|
154
|
-
# jacobi stieltjes
|
|
155
|
-
# ================
|
|
156
|
-
|
|
157
|
-
def jacobi_stieltjes(z, psi, support, alpha=0.0, beta=0.0, n_base=40):
|
|
158
|
-
"""
|
|
159
|
-
Compute m(z) = sum_k psi_k * m_k(z) where
|
|
160
|
-
|
|
161
|
-
m_k(z) = \\int w^{(alpha, beta)}(t) P_k^{(alpha, beta)}(t) / (u(z)-t) dt
|
|
162
|
-
|
|
163
|
-
Each m_k is evaluated *separately* with a Gauss–Jacobi rule sized
|
|
164
|
-
for that k. This follows the user's request: 1 quadrature rule per P_k.
|
|
165
|
-
|
|
166
|
-
Parameters
|
|
167
|
-
----------
|
|
168
|
-
|
|
169
|
-
z : complex or ndarray
|
|
170
|
-
|
|
171
|
-
psi : (K+1,) array_like
|
|
172
|
-
|
|
173
|
-
support : (lambda_minus, lambda_plus)
|
|
174
|
-
|
|
175
|
-
alpha, beta : float
|
|
176
|
-
|
|
177
|
-
n_base : int
|
|
178
|
-
Minimum quadrature size. For degree-k polynomial we use
|
|
179
|
-
n_quad = max(n_base, k+1).
|
|
180
|
-
|
|
181
|
-
Returns
|
|
182
|
-
-------
|
|
183
|
-
|
|
184
|
-
m1 : ndarray (same shape as z)
|
|
185
|
-
|
|
186
|
-
m12 : ndarray (same shape as z)
|
|
187
|
-
"""
|
|
188
|
-
|
|
189
|
-
z = numpy.asarray(z, dtype=numpy.complex128)
|
|
190
|
-
lam_minus, lam_plus = support
|
|
191
|
-
span = lam_plus - lam_minus
|
|
192
|
-
centre = 0.5 * (lam_plus + lam_minus)
|
|
193
|
-
u_z = (2.0 / span) * (z - centre) # map z -> u
|
|
194
|
-
|
|
195
|
-
m_total = numpy.zeros_like(z, dtype=numpy.complex128)
|
|
196
|
-
|
|
197
|
-
for k, psi_k in enumerate(psi):
|
|
198
|
-
# Select quadrature size tailored to this P_k
|
|
199
|
-
n_quad = max(n_base, k + 1)
|
|
200
|
-
t_nodes, w_nodes = roots_jacobi(n_quad, alpha, beta) # (n_quad,)
|
|
201
|
-
|
|
202
|
-
# Evaluate P_k at the quadrature nodes
|
|
203
|
-
P_k_nodes = eval_jacobi(k, alpha, beta, t_nodes) # (n_quad,)
|
|
204
|
-
|
|
205
|
-
# Integrand values at nodes: w_nodes already include the weight
|
|
206
|
-
integrand = w_nodes * P_k_nodes # (n_quad,)
|
|
207
|
-
|
|
208
|
-
# Broadcast over z: shape (n_quad, ...) / ...
|
|
209
|
-
diff = u_z[None, ...] - t_nodes[:, None, None] # (n_quad, Ny, Nx)
|
|
210
|
-
m_k = (integrand[:, None, None] / diff).sum(axis=0)
|
|
211
|
-
|
|
212
|
-
# Accumulate with factor 2/span
|
|
213
|
-
m_total += psi_k * (2.0 / span) * m_k
|
|
214
|
-
|
|
215
|
-
# We use a negative sign convention
|
|
216
|
-
m_total = -m_total
|
|
217
|
-
|
|
218
|
-
return m_total
|
freealg/_support.py
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import numpy
|
|
2
|
-
from scipy.stats import gaussian_kde
|
|
3
|
-
|
|
4
|
-
def detect_support(eigs, method='interior_smooth', k = None, p = 0.001, **kwargs):
|
|
5
|
-
"""
|
|
6
|
-
Estimates the support of the eigenvalue density.
|
|
7
|
-
|
|
8
|
-
Parameters
|
|
9
|
-
----------
|
|
10
|
-
method : {``'range'``, ``'jackknife'``, ``'regression'``, ``'interior'``,
|
|
11
|
-
``'interior_smooth'``}, \
|
|
12
|
-
default= ``'jackknife'``
|
|
13
|
-
The method of support estimation:
|
|
14
|
-
|
|
15
|
-
* ``'range'``: no estimation; the support is the range of the eigenvalues
|
|
16
|
-
* ``'jackknife'``: estimates the support using Quenouille's [1]
|
|
17
|
-
jackknife estimator. Fast and simple, more accurate than the range.
|
|
18
|
-
* ``'regression'``: estimates the support by performing a regression under
|
|
19
|
-
the assumption that the edge behavior is of square-root type. Often
|
|
20
|
-
most accurate.
|
|
21
|
-
* ``'interior'``: estimates a support assuming the range overestimates;
|
|
22
|
-
uses quantiles (p, 1-p).
|
|
23
|
-
* ``'interior_smooth'``: same as ``'interior'`` but using kernel density
|
|
24
|
-
estimation.
|
|
25
|
-
|
|
26
|
-
k : int, default = None
|
|
27
|
-
Number of extreme order statistics to use for ``method='regression'``.
|
|
28
|
-
|
|
29
|
-
p : float, default=0.001
|
|
30
|
-
The edges of the support of the distribution is detected by the
|
|
31
|
-
:math:`p`-quantile on the left and :math:`(1-p)`-quantile on the right
|
|
32
|
-
where ``method='interior'`` or ``method='interior_smooth'``.
|
|
33
|
-
This value should be between 0 and 1, ideally a small number close to
|
|
34
|
-
zero.
|
|
35
|
-
|
|
36
|
-
References
|
|
37
|
-
----------
|
|
38
|
-
|
|
39
|
-
.. [1] Quenouille, M. H. (1949, July). Approximate tests of correlation in time-series.
|
|
40
|
-
In Mathematical Proceedings of the Cambridge Philosophical Society (Vol. 45, No. 3,
|
|
41
|
-
pp. 483-484). Cambridge University Press.
|
|
42
|
-
"""
|
|
43
|
-
|
|
44
|
-
if method=='range':
|
|
45
|
-
lam_m = eigs.min()
|
|
46
|
-
lam_p = eigs.max()
|
|
47
|
-
|
|
48
|
-
elif method=='jackknife':
|
|
49
|
-
x, n = numpy.sort(eigs), len(eigs)
|
|
50
|
-
lam_m = x[0] - (n - 1)/n * (x[1] - x[0])
|
|
51
|
-
lam_p = x[-1] + (n - 1)/n * (x[-1] - x[-2])
|
|
52
|
-
|
|
53
|
-
elif method=='regression':
|
|
54
|
-
x, n = numpy.sort(eigs), len(eigs)
|
|
55
|
-
if k is None:
|
|
56
|
-
k = int(round(n ** (2/3)))
|
|
57
|
-
k = max(5, min(k, n // 2))
|
|
58
|
-
|
|
59
|
-
# The theoretical cdf near the edge behaves like const*(x - a)^{3/2},
|
|
60
|
-
# so (i/n) ≈ (x - a)^{3/2} ⇒ x ≈ a + const*(i/n)^{2/3}.
|
|
61
|
-
y = ((numpy.arange(1, k + 1) - 0.5) / n) ** (2 / 3)
|
|
62
|
-
|
|
63
|
-
# Left edge: regress x_{(i)} on y
|
|
64
|
-
_, lam_m = numpy.polyfit(y, x[:k], 1)
|
|
65
|
-
|
|
66
|
-
# Right edge: regress x_{(n-i+1)} on y
|
|
67
|
-
_, lam_p = numpy.polyfit(y, x[-k:][::-1], 1)
|
|
68
|
-
|
|
69
|
-
elif method=='interior':
|
|
70
|
-
lam_m, lam_p = numpy.quantile(eigs, [p, 1-p])
|
|
71
|
-
|
|
72
|
-
elif method=='interior_smooth':
|
|
73
|
-
kde = gaussian_kde(eigs)
|
|
74
|
-
xs = numpy.linspace(eigs.min(), eigs.max(), 1000)
|
|
75
|
-
fs = kde(xs)
|
|
76
|
-
|
|
77
|
-
cdf = numpy.cumsum(fs)
|
|
78
|
-
cdf /= cdf[-1]
|
|
79
|
-
|
|
80
|
-
lam_m = numpy.interp(p, cdf, xs)
|
|
81
|
-
lam_p = numpy.interp(1-p, cdf, xs)
|
|
82
|
-
else:
|
|
83
|
-
raise NotImplementedError("Unknown method")
|
|
84
|
-
|
|
85
|
-
return lam_m, lam_p
|