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/freeform.py
DELETED
|
@@ -1,967 +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.stats import gaussian_kde
|
|
16
|
-
# from statsmodels.nonparametric.kde import KDEUnivariate
|
|
17
|
-
from functools import partial
|
|
18
|
-
from ._util import compute_eig, beta_kde, force_density
|
|
19
|
-
from ._jacobi import jacobi_sample_proj, jacobi_kernel_proj, jacobi_approx, \
|
|
20
|
-
jacobi_stieltjes
|
|
21
|
-
from ._chebyshev import chebyshev_sample_proj, chebyshev_kernel_proj, \
|
|
22
|
-
chebyshev_approx, chebyshev_stieltjes
|
|
23
|
-
from ._damp import jackson_damping, lanczos_damping, fejer_damping, \
|
|
24
|
-
exponential_damping, parzen_damping
|
|
25
|
-
from ._plot_util import plot_fit, plot_density, plot_hilbert, plot_stieltjes
|
|
26
|
-
from ._pade import fit_pade, eval_pade
|
|
27
|
-
from ._decompress import decompress
|
|
28
|
-
from ._sample import qmc_sample
|
|
29
|
-
from ._support import detect_support
|
|
30
|
-
|
|
31
|
-
__all__ = ['FreeForm', 'eigfree']
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
# =========
|
|
35
|
-
# Free Form
|
|
36
|
-
# =========
|
|
37
|
-
|
|
38
|
-
class FreeForm(object):
|
|
39
|
-
"""
|
|
40
|
-
Free probability for large matrices.
|
|
41
|
-
|
|
42
|
-
Parameters
|
|
43
|
-
----------
|
|
44
|
-
|
|
45
|
-
A : numpy.ndarray
|
|
46
|
-
The 2D symmetric :math:`\\mathbf{A}`. The eigenvalues of this will be
|
|
47
|
-
computed upon calling this class. If a 1D array provided, it is
|
|
48
|
-
assumed to be the eigenvalues of :math:`\\mathbf{A}`.
|
|
49
|
-
|
|
50
|
-
support : tuple, default=None
|
|
51
|
-
The support of the density of :math:`\\mathbf{A}`. If `None`, it is
|
|
52
|
-
estimated from the minimum and maximum of the eigenvalues.
|
|
53
|
-
|
|
54
|
-
delta: float, default=1e-6
|
|
55
|
-
Size of perturbations into the upper half plane for Plemelj's
|
|
56
|
-
formula.
|
|
57
|
-
|
|
58
|
-
Parameters for the ``detect_support`` function can also be prescribed here
|
|
59
|
-
when ``support=None``.
|
|
60
|
-
|
|
61
|
-
Notes
|
|
62
|
-
-----
|
|
63
|
-
|
|
64
|
-
Notes.
|
|
65
|
-
|
|
66
|
-
References
|
|
67
|
-
----------
|
|
68
|
-
|
|
69
|
-
.. [1] Reference.
|
|
70
|
-
|
|
71
|
-
Attributes
|
|
72
|
-
----------
|
|
73
|
-
|
|
74
|
-
eig : numpy.array
|
|
75
|
-
Eigenvalues of the matrix
|
|
76
|
-
|
|
77
|
-
support: tuple
|
|
78
|
-
The predicted (or given) support :math:`(\lambda_\min, \lambda_\max)` of the
|
|
79
|
-
eigenvalue density.
|
|
80
|
-
|
|
81
|
-
psi : numpy.array
|
|
82
|
-
Jacobi coefficients.
|
|
83
|
-
|
|
84
|
-
n : int
|
|
85
|
-
Initial array size (assuming a square matrix when :math:`\\mathbf{A}`
|
|
86
|
-
is 2D).
|
|
87
|
-
|
|
88
|
-
Methods
|
|
89
|
-
-------
|
|
90
|
-
|
|
91
|
-
fit
|
|
92
|
-
Fit the Jacobi polynomials to the empirical density.
|
|
93
|
-
|
|
94
|
-
density
|
|
95
|
-
Compute the spectral density of the matrix.
|
|
96
|
-
|
|
97
|
-
hilbert
|
|
98
|
-
Compute Hilbert transform of the spectral density
|
|
99
|
-
|
|
100
|
-
stieltjes
|
|
101
|
-
Compute Stieltjes transform of the spectral density
|
|
102
|
-
|
|
103
|
-
decompress
|
|
104
|
-
Free decompression of spectral density
|
|
105
|
-
|
|
106
|
-
Examples
|
|
107
|
-
--------
|
|
108
|
-
|
|
109
|
-
.. code-block:: python
|
|
110
|
-
|
|
111
|
-
>>> from freealg import FreeForm
|
|
112
|
-
"""
|
|
113
|
-
|
|
114
|
-
# ====
|
|
115
|
-
# init
|
|
116
|
-
# ====
|
|
117
|
-
|
|
118
|
-
def __init__(self, A, support=None, delta=1e-6, **kwargs):
|
|
119
|
-
"""
|
|
120
|
-
Initialization.
|
|
121
|
-
"""
|
|
122
|
-
|
|
123
|
-
self.A = None
|
|
124
|
-
self.eig = None
|
|
125
|
-
self.delta = delta
|
|
126
|
-
|
|
127
|
-
# Eigenvalues
|
|
128
|
-
if A.ndim == 1:
|
|
129
|
-
# When A is a 1D array, it is assumed A is the eigenvalue array.
|
|
130
|
-
self.eig = A
|
|
131
|
-
self.n = len(A)
|
|
132
|
-
elif A.ndim == 2:
|
|
133
|
-
# When A is a 2D array, it is assumed A is the actual array,
|
|
134
|
-
# and its eigenvalues will be computed.
|
|
135
|
-
self.A = A
|
|
136
|
-
self.n = A.shape[0]
|
|
137
|
-
assert A.shape[0] == A.shape[1], \
|
|
138
|
-
'Only square matrices are permitted.'
|
|
139
|
-
self.eig = compute_eig(A)
|
|
140
|
-
|
|
141
|
-
# Support
|
|
142
|
-
if support is None:
|
|
143
|
-
self.lam_m, self.lam_p = detect_support(self.eig, **kwargs)
|
|
144
|
-
else:
|
|
145
|
-
self.lam_m = support[0]
|
|
146
|
-
self.lam_p = support[1]
|
|
147
|
-
self.support = (self.lam_m, self.lam_p)
|
|
148
|
-
|
|
149
|
-
# Initialize
|
|
150
|
-
self.method = None
|
|
151
|
-
self.psi = None
|
|
152
|
-
self.alpha = None
|
|
153
|
-
self.beta = None
|
|
154
|
-
self._pade_sol = None
|
|
155
|
-
|
|
156
|
-
# ===
|
|
157
|
-
# fit
|
|
158
|
-
# ===
|
|
159
|
-
|
|
160
|
-
def fit(self, method='jacobi', K=10, alpha=0.0, beta=0.0, reg=0.0,
|
|
161
|
-
projection='gaussian', kernel_bw=None, damp=None, force=False,
|
|
162
|
-
pade_p=0, pade_q=1, odd_side='left', pade_reg=0.0, optimizer='ls',
|
|
163
|
-
plot=False, latex=False, save=False):
|
|
164
|
-
"""
|
|
165
|
-
Fit model to eigenvalues.
|
|
166
|
-
|
|
167
|
-
Parameters
|
|
168
|
-
----------
|
|
169
|
-
|
|
170
|
-
method : {``'jacobi'``, ``'chebyshev'``}, default= ``'jacobi'``
|
|
171
|
-
Method of approximation, either by Jacobi polynomials or Chebyshev
|
|
172
|
-
polynomials of the second kind.
|
|
173
|
-
|
|
174
|
-
K : int, default=10
|
|
175
|
-
Highest polynomial degree
|
|
176
|
-
|
|
177
|
-
alpha : float, default=0.0
|
|
178
|
-
Jacobi parameter :math:`\\alpha`. Determines the slope of the
|
|
179
|
-
fitting model on the right side of interval. This should be greater
|
|
180
|
-
then -1. This option is only applicable when ``method='jacobi'``.
|
|
181
|
-
|
|
182
|
-
beta : float, default=0.0
|
|
183
|
-
Jacobi parameter :math:`\\beta`. Determines the slope of the
|
|
184
|
-
fitting model on the left side of interval. This should be greater
|
|
185
|
-
then -1. This option is only applicable when ``method='jacobi'``.
|
|
186
|
-
|
|
187
|
-
reg : float, default=0.0
|
|
188
|
-
Tikhonov regularization coefficient.
|
|
189
|
-
|
|
190
|
-
projection : {``'sample'``, ``'gaussian'``, ``'beta'``}, \
|
|
191
|
-
default= ``'beta'``
|
|
192
|
-
The method of Galerkin projection:
|
|
193
|
-
|
|
194
|
-
* ``'sample'``: directly project samples (eigenvalues) to the
|
|
195
|
-
orthogonal polynomials. This method is highly unstable as it
|
|
196
|
-
treats each sample as a delta Dirac function.
|
|
197
|
-
* ``'gaussian'``: computes Gaussian-Kernel KDE from the samples and
|
|
198
|
-
project a smooth KDE to the orthogonal polynomials. This method
|
|
199
|
-
is stable.
|
|
200
|
-
* ``'beta'``: computes Beta-Kernel KDE from the samples and
|
|
201
|
-
project a smooth KDE to the orthogonal polynomials. This method
|
|
202
|
-
is stable.
|
|
203
|
-
|
|
204
|
-
kernel_bw : float, default=0.001
|
|
205
|
-
Kernel band-wdth. See scipy.stats.gaussian_kde. This argument is
|
|
206
|
-
relevant if ``projection='kernel'`` is set.
|
|
207
|
-
|
|
208
|
-
damp : {``'jackson'``, ``'lanczos'``, ``'fejer``, ``'exponential'``,\
|
|
209
|
-
``'parzen'``}, default=None
|
|
210
|
-
Damping method to eliminate Gibbs oscillation.
|
|
211
|
-
|
|
212
|
-
force : bool, default=False
|
|
213
|
-
If `True`, it forces the density to have unit mass and to be
|
|
214
|
-
strictly positive.
|
|
215
|
-
|
|
216
|
-
pade_p : int, default=0
|
|
217
|
-
Degree of polynomial :math:`P(z)` is :math:`q+p` where :math:`p`
|
|
218
|
-
can only be ``-1``, ``0``, or ``1``. See notes below.
|
|
219
|
-
|
|
220
|
-
pade_q : int, default=1
|
|
221
|
-
Degree of polynomial :math:`Q(z)` is :math:`q`. See notes below.
|
|
222
|
-
|
|
223
|
-
odd_side : {``'left'``, ``'right'``}, default= ``'left'``
|
|
224
|
-
In case of odd number of poles (when :math:`q` is odd), the extra
|
|
225
|
-
pole is set to the left or right side of the support interval,
|
|
226
|
-
while all other poles are split in half to the left and right. Note
|
|
227
|
-
that this is only for the initialization of the poles. The
|
|
228
|
-
optimizer will decide best location by moving them to the left or
|
|
229
|
-
right of the support.
|
|
230
|
-
|
|
231
|
-
pade_reg : float, default=0.0
|
|
232
|
-
Regularization for Pade approximation.
|
|
233
|
-
|
|
234
|
-
optimizer : {``'ls'``, ``'de'``}, default= ``'ls'``
|
|
235
|
-
Optimizer for Pade approximation, including:
|
|
236
|
-
|
|
237
|
-
* ``'ls'``: least square (local, fast)
|
|
238
|
-
* ``'de'``: differential evolution (global, slow)
|
|
239
|
-
|
|
240
|
-
plot : bool, default=False
|
|
241
|
-
If `True`, the approximation coefficients and Pade approximation to
|
|
242
|
-
the Hilbert transform are plotted.
|
|
243
|
-
|
|
244
|
-
latex : bool, default=False
|
|
245
|
-
If `True`, the plot is rendered using LaTeX. This option is
|
|
246
|
-
relevant only if ``plot=True``.
|
|
247
|
-
|
|
248
|
-
save : bool, default=False
|
|
249
|
-
If not `False`, the plot is saved. If a string is given, it is
|
|
250
|
-
assumed to the save filename (with the file extension). This option
|
|
251
|
-
is relevant only if ``plot=True``.
|
|
252
|
-
|
|
253
|
-
Returns
|
|
254
|
-
-------
|
|
255
|
-
|
|
256
|
-
psi : (K+1, ) numpy.ndarray
|
|
257
|
-
Coefficients of fitting Jacobi polynomials
|
|
258
|
-
|
|
259
|
-
Notes
|
|
260
|
-
-----
|
|
261
|
-
|
|
262
|
-
The Pade approximation for the glue function :math:`G(z)` is
|
|
263
|
-
|
|
264
|
-
.. math::
|
|
265
|
-
|
|
266
|
-
G(z) = \\frac{P(z)}{Q(z)},
|
|
267
|
-
|
|
268
|
-
where :math:`P(z)` and :math:`Q(z)` are polynomials of order
|
|
269
|
-
:math:`p+q` and :math:`q` respectively. Note that :math:`p` can only
|
|
270
|
-
be -1, 0, or 1, effectively making Pade approximation of order
|
|
271
|
-
:math:`q-1:q`, :math:`q:q`, or :math:`q-1:q`.
|
|
272
|
-
|
|
273
|
-
Examples
|
|
274
|
-
--------
|
|
275
|
-
|
|
276
|
-
.. code-block:: python
|
|
277
|
-
|
|
278
|
-
>>> from freealg import FreeForm
|
|
279
|
-
"""
|
|
280
|
-
|
|
281
|
-
if alpha <= -1:
|
|
282
|
-
raise ValueError('"alpha" should be greater then "-1".')
|
|
283
|
-
|
|
284
|
-
if beta <= -1:
|
|
285
|
-
raise ValueError('"beta" should be greater then "-1".')
|
|
286
|
-
|
|
287
|
-
if not (method in ['jacobi', 'chebyshev']):
|
|
288
|
-
raise ValueError('"method" is invalid.')
|
|
289
|
-
|
|
290
|
-
if not (projection in ['sample', 'gaussian', 'beta']):
|
|
291
|
-
raise ValueError('"projection" is invalid.')
|
|
292
|
-
|
|
293
|
-
# Project eigenvalues to Jacobi polynomials basis
|
|
294
|
-
if method == 'jacobi':
|
|
295
|
-
|
|
296
|
-
if projection == 'sample':
|
|
297
|
-
psi = jacobi_sample_proj(self.eig, support=self.support, K=K,
|
|
298
|
-
alpha=alpha, beta=beta, reg=reg)
|
|
299
|
-
else:
|
|
300
|
-
# smooth KDE on a fixed grid
|
|
301
|
-
xs = numpy.linspace(self.lam_m, self.lam_p, 2000)
|
|
302
|
-
|
|
303
|
-
if projection == 'gaussian':
|
|
304
|
-
pdf = gaussian_kde(self.eig, bw_method=kernel_bw)(xs)
|
|
305
|
-
else:
|
|
306
|
-
pdf = beta_kde(self.eig, xs, self.lam_m, self.lam_p,
|
|
307
|
-
kernel_bw)
|
|
308
|
-
|
|
309
|
-
# Adaptive KDE
|
|
310
|
-
# k = KDEUnivariate(self.eig)
|
|
311
|
-
# k.fit(bw="silverman", fft=False, weights=None, gridsize=1024,
|
|
312
|
-
# adaptive=True)
|
|
313
|
-
# pdf = k.evaluate(xs)
|
|
314
|
-
|
|
315
|
-
# TEST
|
|
316
|
-
# import matplotlib.pyplot as plt
|
|
317
|
-
# plt.plot(xs, pdf)
|
|
318
|
-
# plt.grid(True)
|
|
319
|
-
# plt.show()
|
|
320
|
-
|
|
321
|
-
psi = jacobi_kernel_proj(xs, pdf, support=self.support, K=K,
|
|
322
|
-
alpha=alpha, beta=beta, reg=reg)
|
|
323
|
-
|
|
324
|
-
elif method == 'chebyshev':
|
|
325
|
-
|
|
326
|
-
if projection == 'sample':
|
|
327
|
-
psi = chebyshev_sample_proj(self.eig, support=self.support,
|
|
328
|
-
K=K, reg=reg)
|
|
329
|
-
else:
|
|
330
|
-
# smooth KDE on a fixed grid
|
|
331
|
-
xs = numpy.linspace(self.lam_m, self.lam_p, 2000)
|
|
332
|
-
|
|
333
|
-
if projection == 'gaussian':
|
|
334
|
-
pdf = gaussian_kde(self.eig, bw_method=kernel_bw)(xs)
|
|
335
|
-
else:
|
|
336
|
-
pdf = beta_kde(self.eig, xs, self.lam_m, self.lam_p,
|
|
337
|
-
kernel_bw)
|
|
338
|
-
|
|
339
|
-
# Adaptive KDE
|
|
340
|
-
# k = KDEUnivariate(self.eig)
|
|
341
|
-
# k.fit(bw="silverman", fft=False, weights=None, gridsize=1024,
|
|
342
|
-
# adaptive=True)
|
|
343
|
-
# pdf = k.evaluate(xs)
|
|
344
|
-
|
|
345
|
-
psi = chebyshev_kernel_proj(xs, pdf, support=self.support,
|
|
346
|
-
K=K, reg=reg)
|
|
347
|
-
else:
|
|
348
|
-
raise ValueError('"method" is invalid.')
|
|
349
|
-
|
|
350
|
-
# Damping
|
|
351
|
-
if damp is not None:
|
|
352
|
-
if damp == 'jackson':
|
|
353
|
-
g = jackson_damping(K+1)
|
|
354
|
-
elif damp == 'lanczos':
|
|
355
|
-
g = lanczos_damping(K+1)
|
|
356
|
-
elif damp == 'fejer':
|
|
357
|
-
g = fejer_damping(K+1)
|
|
358
|
-
elif damp == 'exponential':
|
|
359
|
-
g = exponential_damping(K+1)
|
|
360
|
-
elif damp == 'parzen':
|
|
361
|
-
g = parzen_damping(K+1)
|
|
362
|
-
|
|
363
|
-
psi = psi * g
|
|
364
|
-
|
|
365
|
-
if force:
|
|
366
|
-
# A grid to check and enforce positivity and unit mass on it
|
|
367
|
-
grid = numpy.linspace(self.lam_m, self.lam_p, 500)
|
|
368
|
-
|
|
369
|
-
if method == 'jacobi':
|
|
370
|
-
approx = partial(jacobi_approx, support=self.support,
|
|
371
|
-
alpha=alpha, beta=beta)
|
|
372
|
-
elif method == 'chebyshev':
|
|
373
|
-
approx = partial(chebyshev_approx, support=self.support)
|
|
374
|
-
else:
|
|
375
|
-
raise RuntimeError('"method" is invalid.')
|
|
376
|
-
|
|
377
|
-
# Enforce positivity, unit mass, and zero at edges
|
|
378
|
-
psi = force_density(psi, support=self.support, approx=approx,
|
|
379
|
-
grid=grid, alpha=alpha, beta=beta)
|
|
380
|
-
|
|
381
|
-
# Update attributes
|
|
382
|
-
self.method = method
|
|
383
|
-
self.psi = psi
|
|
384
|
-
self.alpha = alpha
|
|
385
|
-
self.beta = beta
|
|
386
|
-
|
|
387
|
-
# Fit a pade approximation
|
|
388
|
-
if method != 'chebyshev' or projection != 'sample':
|
|
389
|
-
# For holomorphic continuation for the lower half-plane
|
|
390
|
-
x_supp = numpy.linspace(self.lam_m, self.lam_p, 1000)
|
|
391
|
-
g_supp = 2.0 * numpy.pi * self.hilbert(x_supp)
|
|
392
|
-
self._pade_sol = fit_pade(x_supp, g_supp, self.lam_m, self.lam_p,
|
|
393
|
-
p=pade_p, q=pade_q, odd_side=odd_side,
|
|
394
|
-
pade_reg=pade_reg, safety=1.0, max_outer=40,
|
|
395
|
-
xtol=1e-12, ftol=1e-12, optimizer=optimizer,
|
|
396
|
-
verbose=0)
|
|
397
|
-
|
|
398
|
-
if plot:
|
|
399
|
-
g_supp_approx = eval_pade(x_supp[None, :], self._pade_sol)[0, :]
|
|
400
|
-
plot_fit(psi, x_supp, g_supp, g_supp_approx, support=self.support,
|
|
401
|
-
latex=latex, save=save)
|
|
402
|
-
|
|
403
|
-
return self.psi
|
|
404
|
-
|
|
405
|
-
# =======
|
|
406
|
-
# density
|
|
407
|
-
# =======
|
|
408
|
-
|
|
409
|
-
def density(self, x=None, plot=False, latex=False, save=False):
|
|
410
|
-
"""
|
|
411
|
-
Evaluate density.
|
|
412
|
-
|
|
413
|
-
Parameters
|
|
414
|
-
----------
|
|
415
|
-
|
|
416
|
-
x : numpy.array, default=None
|
|
417
|
-
Positions where density to be evaluated at. If `None`, an interval
|
|
418
|
-
slightly larger than the support interval will be used.
|
|
419
|
-
|
|
420
|
-
plot : bool, default=False
|
|
421
|
-
If `True`, density is plotted.
|
|
422
|
-
|
|
423
|
-
latex : bool, default=False
|
|
424
|
-
If `True`, the plot is rendered using LaTeX. This option is
|
|
425
|
-
relevant only if ``plot=True``.
|
|
426
|
-
|
|
427
|
-
save : bool, default=False
|
|
428
|
-
If not `False`, the plot is saved. If a string is given, it is
|
|
429
|
-
assumed to the save filename (with the file extension). This option
|
|
430
|
-
is relevant only if ``plot=True``.
|
|
431
|
-
|
|
432
|
-
Returns
|
|
433
|
-
-------
|
|
434
|
-
|
|
435
|
-
rho : numpy.array
|
|
436
|
-
Density at locations x.
|
|
437
|
-
|
|
438
|
-
See Also
|
|
439
|
-
--------
|
|
440
|
-
hilbert
|
|
441
|
-
stieltjes
|
|
442
|
-
|
|
443
|
-
Examples
|
|
444
|
-
--------
|
|
445
|
-
|
|
446
|
-
.. code-block:: python
|
|
447
|
-
|
|
448
|
-
>>> from freealg import FreeForm
|
|
449
|
-
"""
|
|
450
|
-
|
|
451
|
-
if self.psi is None:
|
|
452
|
-
raise RuntimeError('The spectral density needs to be fit using the .fit() function.')
|
|
453
|
-
|
|
454
|
-
# Create x if not given
|
|
455
|
-
if x is None:
|
|
456
|
-
radius = 0.5 * (self.lam_p - self.lam_m)
|
|
457
|
-
center = 0.5 * (self.lam_p + self.lam_m)
|
|
458
|
-
scale = 1.25
|
|
459
|
-
x_min = numpy.floor(center - radius * scale)
|
|
460
|
-
x_max = numpy.ceil(center + radius * scale)
|
|
461
|
-
x = numpy.linspace(x_min, x_max, 500)
|
|
462
|
-
|
|
463
|
-
# Preallocate density to zero
|
|
464
|
-
rho = numpy.zeros_like(x)
|
|
465
|
-
|
|
466
|
-
# Compute density only inside support
|
|
467
|
-
mask = numpy.logical_and(x >= self.lam_m, x <= self.lam_p)
|
|
468
|
-
|
|
469
|
-
if self.method == 'jacobi':
|
|
470
|
-
rho[mask] = jacobi_approx(x[mask], self.psi, self.support,
|
|
471
|
-
self.alpha, self.beta)
|
|
472
|
-
elif self.method == 'chebyshev':
|
|
473
|
-
rho[mask] = chebyshev_approx(x[mask], self.psi, self.support)
|
|
474
|
-
else:
|
|
475
|
-
raise RuntimeError('"method" is invalid.')
|
|
476
|
-
|
|
477
|
-
# Check density is unit mass
|
|
478
|
-
mass = numpy.trapezoid(rho, x)
|
|
479
|
-
if not numpy.isclose(mass, 1.0, atol=1e-2):
|
|
480
|
-
print(f'"rho" is not unit mass. mass: {mass:>0.3f}. Set ' +
|
|
481
|
-
r'"force=True".')
|
|
482
|
-
|
|
483
|
-
# Check density is positive
|
|
484
|
-
min_rho = numpy.min(rho)
|
|
485
|
-
if min_rho < 0.0 - 1e-3:
|
|
486
|
-
print(f'"rho" is not positive. min_rho: {min_rho:>0.3f}. Set ' +
|
|
487
|
-
r'"force=True".')
|
|
488
|
-
|
|
489
|
-
if plot:
|
|
490
|
-
plot_density(x, rho, eig=self.eig, support=self.support,
|
|
491
|
-
label='Estimate', latex=latex, save=save)
|
|
492
|
-
|
|
493
|
-
return rho
|
|
494
|
-
|
|
495
|
-
# =======
|
|
496
|
-
# hilbert
|
|
497
|
-
# =======
|
|
498
|
-
|
|
499
|
-
def hilbert(self, x=None, rho=None, plot=False, latex=False, save=False):
|
|
500
|
-
"""
|
|
501
|
-
Compute Hilbert transform of the spectral density.
|
|
502
|
-
|
|
503
|
-
Parameters
|
|
504
|
-
----------
|
|
505
|
-
|
|
506
|
-
x : numpy.array, default=None
|
|
507
|
-
The locations where Hilbert transform is evaluated at. If `None`,
|
|
508
|
-
an interval slightly larger than the support interval of the
|
|
509
|
-
spectral density is used.
|
|
510
|
-
|
|
511
|
-
rho : numpy.array, default=None
|
|
512
|
-
Density. If `None`, it will be computed.
|
|
513
|
-
|
|
514
|
-
plot : bool, default=False
|
|
515
|
-
If `True`, density is plotted.
|
|
516
|
-
|
|
517
|
-
latex : bool, default=False
|
|
518
|
-
If `True`, the plot is rendered using LaTeX. This option is
|
|
519
|
-
relevant only if ``plot=True``.
|
|
520
|
-
|
|
521
|
-
save : bool, default=False
|
|
522
|
-
If not `False`, the plot is saved. If a string is given, it is
|
|
523
|
-
assumed to the save filename (with the file extension). This option
|
|
524
|
-
is relevant only if ``plot=True``.
|
|
525
|
-
|
|
526
|
-
Returns
|
|
527
|
-
-------
|
|
528
|
-
|
|
529
|
-
hilb : numpy.array
|
|
530
|
-
The Hilbert transform on the locations `x`.
|
|
531
|
-
|
|
532
|
-
See Also
|
|
533
|
-
--------
|
|
534
|
-
density
|
|
535
|
-
stieltjes
|
|
536
|
-
|
|
537
|
-
Examples
|
|
538
|
-
--------
|
|
539
|
-
|
|
540
|
-
.. code-block:: python
|
|
541
|
-
|
|
542
|
-
>>> from freealg import FreeForm
|
|
543
|
-
"""
|
|
544
|
-
|
|
545
|
-
if self.psi is None:
|
|
546
|
-
raise RuntimeError('The spectral density needs to be fit using the .fit() function.')
|
|
547
|
-
|
|
548
|
-
# Create x if not given
|
|
549
|
-
if x is None:
|
|
550
|
-
radius = 0.5 * (self.lam_p - self.lam_m)
|
|
551
|
-
center = 0.5 * (self.lam_p + self.lam_m)
|
|
552
|
-
scale = 1.25
|
|
553
|
-
x_min = numpy.floor(center - radius * scale)
|
|
554
|
-
x_max = numpy.ceil(center + radius * scale)
|
|
555
|
-
x = numpy.linspace(x_min, x_max, 500)
|
|
556
|
-
|
|
557
|
-
# if (numpy.min(x) > self.lam_m) or (numpy.max(x) < self.lam_p):
|
|
558
|
-
# raise ValueError('"x" does not encompass support interval.')
|
|
559
|
-
|
|
560
|
-
# Preallocate density to zero
|
|
561
|
-
if rho is None:
|
|
562
|
-
rho = self.density(x)
|
|
563
|
-
|
|
564
|
-
# mask of support [lam_m, lam_p]
|
|
565
|
-
mask = numpy.logical_and(x >= self.lam_m, x <= self.lam_p)
|
|
566
|
-
x_s = x[mask]
|
|
567
|
-
rho_s = rho[mask]
|
|
568
|
-
|
|
569
|
-
# Form the matrix of integrands: rho_s / (t - x_i)
|
|
570
|
-
# Here, we have diff[i,j] = x[i] - x_s[j]
|
|
571
|
-
diff = x[:, None] - x_s[None, :]
|
|
572
|
-
D = rho_s[None, :] / diff
|
|
573
|
-
|
|
574
|
-
# Principal‐value: wherever t == x_i, then diff == 0, zero that entry
|
|
575
|
-
# (numpy.isclose handles floating‐point exactly)
|
|
576
|
-
D[numpy.isclose(diff, 0.0)] = 0.0
|
|
577
|
-
|
|
578
|
-
# Integrate each row over t using trapezoid rule on x_s
|
|
579
|
-
# Namely, hilb[i] = int rho_s(t)/(t - x[i]) dt
|
|
580
|
-
hilb = numpy.trapezoid(D, x_s, axis=1) / numpy.pi
|
|
581
|
-
|
|
582
|
-
# We use negative sign convention
|
|
583
|
-
hilb = -hilb
|
|
584
|
-
|
|
585
|
-
if plot:
|
|
586
|
-
plot_hilbert(x, hilb, support=self.support, latex=latex,
|
|
587
|
-
save=save)
|
|
588
|
-
|
|
589
|
-
return hilb
|
|
590
|
-
|
|
591
|
-
# ====
|
|
592
|
-
# glue
|
|
593
|
-
# ====
|
|
594
|
-
|
|
595
|
-
def _glue(self, z):
|
|
596
|
-
# Glue function
|
|
597
|
-
if self._pade_sol is None:
|
|
598
|
-
return numpy.zeros_like(z)
|
|
599
|
-
g = eval_pade(z, self._pade_sol)
|
|
600
|
-
return g
|
|
601
|
-
|
|
602
|
-
# =========
|
|
603
|
-
# stieltjes
|
|
604
|
-
# =========
|
|
605
|
-
|
|
606
|
-
def stieltjes(self, x=None, y=None, plot=False, latex=False, save=False):
|
|
607
|
-
"""
|
|
608
|
-
Compute Stieltjes transform of the spectral density, evaluated on an array
|
|
609
|
-
of points, or over a 2D Cartesian grid on the complex plane.
|
|
610
|
-
|
|
611
|
-
Parameters
|
|
612
|
-
----------
|
|
613
|
-
|
|
614
|
-
x : numpy.array, default=None
|
|
615
|
-
The x axis of the grid where the Stieltjes transform is evaluated.
|
|
616
|
-
If `None`, an interval slightly larger than the support interval of
|
|
617
|
-
the spectral density is used.
|
|
618
|
-
|
|
619
|
-
y : numpy.array, default=None
|
|
620
|
-
The y axis of the grid where the Stieltjes transform is evaluated.
|
|
621
|
-
If `None`, a grid on the interval ``[-1, 1]`` is used.
|
|
622
|
-
|
|
623
|
-
plot : bool, default=False
|
|
624
|
-
If `True`, density is plotted.
|
|
625
|
-
|
|
626
|
-
latex : bool, default=False
|
|
627
|
-
If `True`, the plot is rendered using LaTeX. This option is
|
|
628
|
-
relevant only if ``plot=True``.
|
|
629
|
-
|
|
630
|
-
save : bool, default=False
|
|
631
|
-
If not `False`, the plot is saved. If a string is given, it is
|
|
632
|
-
assumed to the save filename (with the file extension). This option
|
|
633
|
-
is relevant only if ``plot=True``.
|
|
634
|
-
|
|
635
|
-
Returns
|
|
636
|
-
-------
|
|
637
|
-
|
|
638
|
-
m_p : numpy.ndarray
|
|
639
|
-
The Stieltjes transform on the principal branch.
|
|
640
|
-
|
|
641
|
-
m_m : numpy.ndarray
|
|
642
|
-
The Stieltjes transform continued to the secondary branch.
|
|
643
|
-
|
|
644
|
-
See Also
|
|
645
|
-
--------
|
|
646
|
-
density
|
|
647
|
-
hilbert
|
|
648
|
-
|
|
649
|
-
Notes
|
|
650
|
-
-----
|
|
651
|
-
|
|
652
|
-
Notes.
|
|
653
|
-
|
|
654
|
-
References
|
|
655
|
-
----------
|
|
656
|
-
|
|
657
|
-
.. [1] tbd
|
|
658
|
-
|
|
659
|
-
Examples
|
|
660
|
-
--------
|
|
661
|
-
|
|
662
|
-
.. code-block:: python
|
|
663
|
-
|
|
664
|
-
>>> from freealg import FreeForm
|
|
665
|
-
"""
|
|
666
|
-
|
|
667
|
-
if self.psi is None:
|
|
668
|
-
raise RuntimeError('The spectral density needs to be fit using the .fit() function.')
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
# Determine whether the Stieltjes transform is to be computed on
|
|
672
|
-
# a Cartesian grid
|
|
673
|
-
cartesian = plot | (y is not None)
|
|
674
|
-
|
|
675
|
-
# Create x if not given
|
|
676
|
-
if x is None:
|
|
677
|
-
radius = 0.5 * (self.lam_p - self.lam_m)
|
|
678
|
-
center = 0.5 * (self.lam_p + self.lam_m)
|
|
679
|
-
scale = 2.0
|
|
680
|
-
x_min = numpy.floor(2.0 * (center - 2.0 * radius * scale)) / 2.0
|
|
681
|
-
x_max = numpy.ceil(2.0 * (center + 2.0 * radius * scale)) / 2.0
|
|
682
|
-
x = numpy.linspace(x_min, x_max, 500)
|
|
683
|
-
if not cartesian:
|
|
684
|
-
# Evaluate slightly above the real line
|
|
685
|
-
x = x.astype(complex)
|
|
686
|
-
x += self.delta * 1j
|
|
687
|
-
|
|
688
|
-
# Create y if not given
|
|
689
|
-
if cartesian:
|
|
690
|
-
if y is None:
|
|
691
|
-
y = numpy.linspace(-1, 1, 400)
|
|
692
|
-
x_grid, y_grid = numpy.meshgrid(x.real, y.real)
|
|
693
|
-
z = x_grid + 1j * y_grid # shape (Ny, Nx)
|
|
694
|
-
else:
|
|
695
|
-
z = x
|
|
696
|
-
|
|
697
|
-
m1, m2 = self._eval_stieltjes(z)
|
|
698
|
-
|
|
699
|
-
if plot:
|
|
700
|
-
plot_stieltjes(x, y, m1, m2, self.support, latex=latex, save=save)
|
|
701
|
-
|
|
702
|
-
return m1, m2
|
|
703
|
-
|
|
704
|
-
# ==============
|
|
705
|
-
# eval stieltjes
|
|
706
|
-
# ==============
|
|
707
|
-
|
|
708
|
-
def _eval_stieltjes(self, z):
|
|
709
|
-
"""
|
|
710
|
-
Compute Stieltjes transform of the spectral density.
|
|
711
|
-
|
|
712
|
-
Parameters
|
|
713
|
-
----------
|
|
714
|
-
|
|
715
|
-
z : numpy.array
|
|
716
|
-
The z values in the complex plan where the Stieltjes transform is
|
|
717
|
-
evaluated.
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
Returns
|
|
721
|
-
-------
|
|
722
|
-
|
|
723
|
-
m_p : numpy.ndarray
|
|
724
|
-
The Stieltjes transform on the principal branch.
|
|
725
|
-
|
|
726
|
-
m_m : numpy.ndarray
|
|
727
|
-
The Stieltjes transform continued to the secondary branch.
|
|
728
|
-
"""
|
|
729
|
-
|
|
730
|
-
assert self.psi is not None, "The fit function has not been called."
|
|
731
|
-
|
|
732
|
-
# Allow for arbitrary input shapes
|
|
733
|
-
z = numpy.asarray(z)
|
|
734
|
-
shape = z.shape
|
|
735
|
-
if len(shape) == 0:
|
|
736
|
-
shape = (1,)
|
|
737
|
-
z = z.reshape(-1, 1)
|
|
738
|
-
|
|
739
|
-
# # Set the number of bases as the number of x points insides support
|
|
740
|
-
# mask_sup = numpy.logical_and(z.real >= self.lam_m, z.real <= self.lam_p)
|
|
741
|
-
# n_base = 2 * numpy.sum(mask_sup)
|
|
742
|
-
|
|
743
|
-
# Stieltjes function
|
|
744
|
-
if self.method == 'jacobi':
|
|
745
|
-
stieltjes = partial(jacobi_stieltjes, psi=self.psi,
|
|
746
|
-
support=self.support, alpha=self.alpha,
|
|
747
|
-
beta=self.beta) # n_base = n_base
|
|
748
|
-
elif self.method == 'chebyshev':
|
|
749
|
-
stieltjes = partial(chebyshev_stieltjes, psi=self.psi,
|
|
750
|
-
support=self.support)
|
|
751
|
-
|
|
752
|
-
mask_p = z.imag >= 0.0
|
|
753
|
-
mask_m = z.imag < 0.0
|
|
754
|
-
|
|
755
|
-
m1 = numpy.zeros_like(z)
|
|
756
|
-
m2 = numpy.zeros_like(z)
|
|
757
|
-
|
|
758
|
-
if self._pade_sol is not None:
|
|
759
|
-
# Upper half-plane
|
|
760
|
-
m1[mask_p] = stieltjes(z[mask_p].reshape(-1, 1)).ravel()
|
|
761
|
-
|
|
762
|
-
# Lower half-plane, use Schwarz reflection
|
|
763
|
-
m1[mask_m] = numpy.conjugate(
|
|
764
|
-
stieltjes(numpy.conjugate(z[mask_m].reshape(-1, 1)))).ravel()
|
|
765
|
-
|
|
766
|
-
# Second Riemann sheet
|
|
767
|
-
m2[mask_p] = m1[mask_p]
|
|
768
|
-
m2[mask_m] = -m1[mask_m] + self._glue(
|
|
769
|
-
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
|
-
|
|
778
|
-
m1, m2 = m1.reshape(*shape), m2.reshape(*shape)
|
|
779
|
-
|
|
780
|
-
return m1, m2
|
|
781
|
-
|
|
782
|
-
# ==========
|
|
783
|
-
# decompress
|
|
784
|
-
# ==========
|
|
785
|
-
|
|
786
|
-
def decompress(self, size, x=None, iterations=500, eigvals=True,
|
|
787
|
-
step_size=0.1, tolerance=1e-6, seed=None, plot=False,
|
|
788
|
-
latex=False, save=False):
|
|
789
|
-
"""
|
|
790
|
-
Free decompression of spectral density.
|
|
791
|
-
|
|
792
|
-
Parameters
|
|
793
|
-
----------
|
|
794
|
-
|
|
795
|
-
size : int
|
|
796
|
-
Size of the decompressed matrix.
|
|
797
|
-
|
|
798
|
-
x : numpy.array, default=None
|
|
799
|
-
Positions where density to be evaluated at. If `None`, an interval
|
|
800
|
-
slightly larger than the support interval will be used.
|
|
801
|
-
|
|
802
|
-
iterations: int, default=500
|
|
803
|
-
Maximum number of Newton iterations.
|
|
804
|
-
|
|
805
|
-
eigvals: bool, default=True
|
|
806
|
-
Return estimated (sampled) eigenvalues as well as the density.
|
|
807
|
-
|
|
808
|
-
step_size: float, default=0.1
|
|
809
|
-
Step size for Newton iterations.
|
|
810
|
-
|
|
811
|
-
tolerance: float, default=1e-6
|
|
812
|
-
Tolerance for the solution obtained by the Newton solver. Also
|
|
813
|
-
used for the finite difference approximation to the derivative.
|
|
814
|
-
|
|
815
|
-
seed : int, default=None
|
|
816
|
-
Seed for random number generator. Used for QMC sampling.
|
|
817
|
-
|
|
818
|
-
plot : bool, default=False
|
|
819
|
-
If `True`, density is plotted.
|
|
820
|
-
|
|
821
|
-
latex : bool, default=False
|
|
822
|
-
If `True`, the plot is rendered using LaTeX. This option is
|
|
823
|
-
relevant only if ``plot=True``.
|
|
824
|
-
|
|
825
|
-
save : bool, default=False
|
|
826
|
-
If not `False`, the plot is saved. If a string is given, it is
|
|
827
|
-
assumed to the save filename (with the file extension). This option
|
|
828
|
-
is relevant only if ``plot=True``.
|
|
829
|
-
|
|
830
|
-
Returns
|
|
831
|
-
-------
|
|
832
|
-
|
|
833
|
-
x : numpy.array
|
|
834
|
-
Locations where the spectral density is estimated
|
|
835
|
-
|
|
836
|
-
rho : numpy.array
|
|
837
|
-
Estimated spectral density at locations x
|
|
838
|
-
|
|
839
|
-
eigs : numpy.array
|
|
840
|
-
Estimated eigenvalues as low-discrepancy samples of the estimated
|
|
841
|
-
spectral density. Only returns if ``eigvals=True``.
|
|
842
|
-
|
|
843
|
-
See Also
|
|
844
|
-
--------
|
|
845
|
-
|
|
846
|
-
density
|
|
847
|
-
stieltjes
|
|
848
|
-
|
|
849
|
-
Notes
|
|
850
|
-
-----
|
|
851
|
-
|
|
852
|
-
Work in progress.
|
|
853
|
-
|
|
854
|
-
References
|
|
855
|
-
----------
|
|
856
|
-
|
|
857
|
-
.. [1] tbd
|
|
858
|
-
|
|
859
|
-
Examples
|
|
860
|
-
--------
|
|
861
|
-
|
|
862
|
-
.. code-block:: python
|
|
863
|
-
|
|
864
|
-
>>> from freealg import FreeForm
|
|
865
|
-
"""
|
|
866
|
-
|
|
867
|
-
size = int(size)
|
|
868
|
-
|
|
869
|
-
rho, x, (lb, ub) = decompress(self, size, x=x, delta=self.delta,
|
|
870
|
-
iterations=iterations,
|
|
871
|
-
step_size=step_size, tolerance=tolerance)
|
|
872
|
-
x, rho = x.ravel(), rho.ravel()
|
|
873
|
-
|
|
874
|
-
if plot:
|
|
875
|
-
plot_density(x, rho, support=(lb, ub),
|
|
876
|
-
label='Decompression', latex=latex, save=save)
|
|
877
|
-
|
|
878
|
-
if eigvals:
|
|
879
|
-
eigs = numpy.sort(qmc_sample(x, rho, size, seed=seed))
|
|
880
|
-
return x, rho, eigs
|
|
881
|
-
else:
|
|
882
|
-
return x, rho
|
|
883
|
-
|
|
884
|
-
def eigfree(A, N = None, psd = None):
|
|
885
|
-
"""
|
|
886
|
-
Estimate the eigenvalues of a matrix :math:`\\mathbf{A}` or a larger matrix
|
|
887
|
-
containing :math:`\\mathbf{A}` using free decompression.
|
|
888
|
-
|
|
889
|
-
This is a convenience function for the FreeForm class with some effective
|
|
890
|
-
defaults that work well for common random matrix ensembles. For improved
|
|
891
|
-
performance and plotting utilites, consider finetuning parameters using
|
|
892
|
-
the FreeForm class.
|
|
893
|
-
|
|
894
|
-
Parameters
|
|
895
|
-
----------
|
|
896
|
-
|
|
897
|
-
A : numpy.ndarray
|
|
898
|
-
The symmetric real-valued matrix :math:`\\mathbf{A}` whose eigenvalues
|
|
899
|
-
(or those of a matrix containing :math:`\\mathbf{A}`) are to be computed.
|
|
900
|
-
|
|
901
|
-
N : int, default=None
|
|
902
|
-
The size of the matrix containing :math:`\\mathbf{A}` to estimate
|
|
903
|
-
eigenvalues of. If None, returns estimates of the eigenvalues of
|
|
904
|
-
:math:`\\mathbf{A}` itself.
|
|
905
|
-
|
|
906
|
-
psd: bool, default=None
|
|
907
|
-
Determines whether the matrix is positive-semidefinite (PSD; all
|
|
908
|
-
eigenvalues are non-negative). If None, the matrix is considered PSD if
|
|
909
|
-
all sampled eigenvalues are positive.
|
|
910
|
-
|
|
911
|
-
Notes
|
|
912
|
-
-----
|
|
913
|
-
|
|
914
|
-
Notes.
|
|
915
|
-
|
|
916
|
-
References
|
|
917
|
-
----------
|
|
918
|
-
|
|
919
|
-
.. [1] Reference.
|
|
920
|
-
|
|
921
|
-
Examples
|
|
922
|
-
--------
|
|
923
|
-
|
|
924
|
-
.. code-block:: python
|
|
925
|
-
|
|
926
|
-
>>> from freealg import FreeForm
|
|
927
|
-
"""
|
|
928
|
-
n = A.shape[0]
|
|
929
|
-
|
|
930
|
-
# Size of sample matrix
|
|
931
|
-
n_s = int(80*(1 + numpy.log(n)))
|
|
932
|
-
|
|
933
|
-
# If matrix is not large enough, return eigenvalues
|
|
934
|
-
if n < n_s:
|
|
935
|
-
return compute_eig(A)
|
|
936
|
-
|
|
937
|
-
if N is None:
|
|
938
|
-
N = n
|
|
939
|
-
|
|
940
|
-
# Number of samples
|
|
941
|
-
num_samples = int(10 * (n / n_s)**0.5)
|
|
942
|
-
|
|
943
|
-
# Collect eigenvalue samples
|
|
944
|
-
samples = []
|
|
945
|
-
for _ in range(num_samples):
|
|
946
|
-
indices = numpy.random.choice(n, n_s, replace=False)
|
|
947
|
-
samples.append(compute_eig(A[numpy.ix_(indices, indices)]))
|
|
948
|
-
samples = numpy.concatenate(samples).ravel()
|
|
949
|
-
|
|
950
|
-
# If all eigenvalues are positive, set PSD flag
|
|
951
|
-
if psd is None:
|
|
952
|
-
psd = samples.min() > 0
|
|
953
|
-
|
|
954
|
-
ff = FreeForm(samples)
|
|
955
|
-
# Since we are resampling, we need to provide the correct matrix size
|
|
956
|
-
ff.n = n_s
|
|
957
|
-
|
|
958
|
-
# Perform fit and estimate eigenvalues
|
|
959
|
-
order = 1 + int(len(samples)**.25)
|
|
960
|
-
ff.fit(method='chebyshev', K=order, projection='sample', damp='jackson',
|
|
961
|
-
force=True, plot=False, latex=False, save=False, reg=0.01)
|
|
962
|
-
_, _, eigs = ff.decompress(N)
|
|
963
|
-
|
|
964
|
-
if psd:
|
|
965
|
-
eigs = numpy.abs(eigs)
|
|
966
|
-
|
|
967
|
-
return eigs
|