freealg 0.7.17__py3-none-any.whl → 0.7.18__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 -6
- freealg/__version__.py +1 -1
- freealg/_algebraic_form/_branch_points.py +18 -18
- freealg/_algebraic_form/_continuation_algebraic.py +13 -13
- freealg/_algebraic_form/_cusp.py +15 -15
- freealg/_algebraic_form/_cusp_wrap.py +6 -6
- freealg/_algebraic_form/_decompress.py +16 -16
- freealg/_algebraic_form/_decompress4.py +31 -31
- freealg/_algebraic_form/_decompress5.py +23 -23
- freealg/_algebraic_form/_decompress6.py +13 -13
- freealg/_algebraic_form/_decompress7.py +15 -15
- freealg/_algebraic_form/_decompress8.py +17 -17
- freealg/_algebraic_form/_decompress9.py +18 -18
- freealg/_algebraic_form/_decompress_new.py +17 -17
- freealg/_algebraic_form/_decompress_new_2.py +57 -57
- freealg/_algebraic_form/_decompress_util.py +10 -10
- freealg/_algebraic_form/_decompressible.py +292 -0
- freealg/_algebraic_form/_edge.py +10 -10
- freealg/_algebraic_form/_homotopy4.py +9 -9
- freealg/_algebraic_form/_homotopy5.py +9 -9
- freealg/_algebraic_form/_support.py +19 -19
- freealg/_algebraic_form/algebraic_form.py +262 -468
- freealg/_base_form.py +401 -0
- freealg/_free_form/__init__.py +1 -4
- freealg/_free_form/_density_util.py +1 -1
- freealg/_free_form/_plot_util.py +3 -511
- freealg/_free_form/free_form.py +8 -367
- freealg/_util.py +59 -11
- freealg/distributions/__init__.py +2 -1
- freealg/distributions/_base_distribution.py +163 -0
- freealg/distributions/_chiral_block.py +137 -11
- freealg/distributions/_compound_poisson.py +141 -47
- freealg/distributions/_deformed_marchenko_pastur.py +138 -33
- freealg/distributions/_deformed_wigner.py +98 -9
- freealg/distributions/_fuss_catalan.py +269 -0
- freealg/distributions/_kesten_mckay.py +4 -130
- freealg/distributions/_marchenko_pastur.py +8 -196
- freealg/distributions/_meixner.py +4 -130
- freealg/distributions/_wachter.py +4 -130
- freealg/distributions/_wigner.py +10 -127
- freealg/visualization/__init__.py +2 -2
- freealg/visualization/{_rgb_hsv.py → _domain_coloring.py} +37 -29
- freealg/visualization/_plot_util.py +513 -0
- {freealg-0.7.17.dist-info → freealg-0.7.18.dist-info}/METADATA +1 -1
- freealg-0.7.18.dist-info/RECORD +74 -0
- freealg-0.7.17.dist-info/RECORD +0 -69
- /freealg/{_free_form/_sample.py → _sample.py} +0 -0
- /freealg/{_free_form/_support.py → _support.py} +0 -0
- {freealg-0.7.17.dist-info → freealg-0.7.18.dist-info}/WHEEL +0 -0
- {freealg-0.7.17.dist-info → freealg-0.7.18.dist-info}/licenses/AUTHORS.txt +0 -0
- {freealg-0.7.17.dist-info → freealg-0.7.18.dist-info}/licenses/LICENSE.txt +0 -0
- {freealg-0.7.17.dist-info → freealg-0.7.18.dist-info}/top_level.txt +0 -0
|
@@ -12,13 +12,14 @@
|
|
|
12
12
|
# =======
|
|
13
13
|
|
|
14
14
|
import numpy
|
|
15
|
-
from .._util import
|
|
15
|
+
from .._util import compute_eig
|
|
16
16
|
# from .._util import compute_eig
|
|
17
17
|
from ._continuation_algebraic import sample_z_joukowski, \
|
|
18
18
|
filter_z_away_from_cuts, fit_polynomial_relation, \
|
|
19
19
|
sanity_check_stieltjes_branch, eval_P
|
|
20
20
|
from ._edge import evolve_edges, merge_edges
|
|
21
21
|
from ._cusp_wrap import cusp_wrap
|
|
22
|
+
from ._decompressible import precheck_laurent
|
|
22
23
|
|
|
23
24
|
# Decompress with Newton
|
|
24
25
|
# from ._decompress import build_time_grid, decompress_newton
|
|
@@ -44,11 +45,13 @@ from ._decompress2 import decompress_coeffs, plot_candidates
|
|
|
44
45
|
# from ._homotopy4 import StieltjesPoly
|
|
45
46
|
from ._homotopy5 import StieltjesPoly
|
|
46
47
|
|
|
47
|
-
from ._branch_points import
|
|
48
|
-
from ._support import
|
|
48
|
+
from ._branch_points import estimate_branch_points
|
|
49
|
+
from ._support import estimate_support
|
|
50
|
+
from .._support import supp as estimate_broad_supp
|
|
49
51
|
from ._moments import Moments, AlgebraicStieltjesMoments
|
|
50
|
-
from ..
|
|
51
|
-
|
|
52
|
+
from ..visualization._plot_util import plot_density, plot_hilbert, \
|
|
53
|
+
plot_stieltjes
|
|
54
|
+
from .._base_form import BaseForm
|
|
52
55
|
|
|
53
56
|
# Fallback to previous numpy API
|
|
54
57
|
if not hasattr(numpy, 'trapezoid'):
|
|
@@ -61,7 +64,7 @@ __all__ = ['AlgebraicForm']
|
|
|
61
64
|
# Algebraic Form
|
|
62
65
|
# ==============
|
|
63
66
|
|
|
64
|
-
class AlgebraicForm(
|
|
67
|
+
class AlgebraicForm(BaseForm):
|
|
65
68
|
"""
|
|
66
69
|
Algebraic surrogate for ensemble models.
|
|
67
70
|
|
|
@@ -104,7 +107,7 @@ class AlgebraicForm(object):
|
|
|
104
107
|
eig : numpy.array
|
|
105
108
|
Eigenvalues of the matrix
|
|
106
109
|
|
|
107
|
-
|
|
110
|
+
supp: tuple
|
|
108
111
|
The predicted (or given) support :math:`(\\lambda_{\\min},
|
|
109
112
|
\\lambda_{\\max})` of the eigenvalue density.
|
|
110
113
|
|
|
@@ -116,10 +119,16 @@ class AlgebraicForm(object):
|
|
|
116
119
|
-------
|
|
117
120
|
|
|
118
121
|
fit
|
|
119
|
-
Fit
|
|
122
|
+
Fit an algebraic structure to the input data
|
|
123
|
+
|
|
124
|
+
support
|
|
125
|
+
Estimate the spectral edges of the density
|
|
126
|
+
|
|
127
|
+
branch_points
|
|
128
|
+
Compute global branch points and zeros of leading coefficient
|
|
120
129
|
|
|
121
130
|
density
|
|
122
|
-
|
|
131
|
+
Evaluate spectral density
|
|
123
132
|
|
|
124
133
|
hilbert
|
|
125
134
|
Compute Hilbert transform of the spectral density
|
|
@@ -130,6 +139,18 @@ class AlgebraicForm(object):
|
|
|
130
139
|
decompress
|
|
131
140
|
Free decompression of spectral density
|
|
132
141
|
|
|
142
|
+
candidate
|
|
143
|
+
Candidate densities of free decompression from all possible roots
|
|
144
|
+
|
|
145
|
+
is_decompressible
|
|
146
|
+
Check if the underlying distribution can be decompressed
|
|
147
|
+
|
|
148
|
+
edge
|
|
149
|
+
Evolves spectral edges
|
|
150
|
+
|
|
151
|
+
cusp
|
|
152
|
+
Find cusp (merge) point of evolving spectral edges
|
|
153
|
+
|
|
133
154
|
eigvalsh
|
|
134
155
|
Estimate the eigenvalues
|
|
135
156
|
|
|
@@ -150,7 +171,7 @@ class AlgebraicForm(object):
|
|
|
150
171
|
|
|
151
172
|
.. code-block:: python
|
|
152
173
|
|
|
153
|
-
>>> from freealg import
|
|
174
|
+
>>> from freealg import AlgebraicForm
|
|
154
175
|
"""
|
|
155
176
|
|
|
156
177
|
# ====
|
|
@@ -163,21 +184,17 @@ class AlgebraicForm(object):
|
|
|
163
184
|
Initialization.
|
|
164
185
|
"""
|
|
165
186
|
|
|
166
|
-
|
|
167
|
-
|
|
187
|
+
super().__init__(delta, dtype)
|
|
188
|
+
|
|
168
189
|
self._stieltjes = None
|
|
169
190
|
self._moments = None
|
|
170
|
-
self.
|
|
171
|
-
self.
|
|
172
|
-
self.delta = delta # Offset above real axis to apply Plemelj formula
|
|
173
|
-
|
|
174
|
-
# Data type for complex arrays
|
|
175
|
-
self.dtype = resolve_complex_dtype(dtype)
|
|
191
|
+
self.supp = support
|
|
192
|
+
self.est_supp = None # Estimated from polynomial after fitting
|
|
176
193
|
|
|
177
194
|
if hasattr(A, 'stieltjes') and callable(getattr(A, 'stieltjes', None)):
|
|
178
195
|
# This is one of the distribution objects, like MarchenkoPastur
|
|
179
196
|
self._stieltjes = A.stieltjes
|
|
180
|
-
self.
|
|
197
|
+
self.supp = A.support()
|
|
181
198
|
self.n = 1
|
|
182
199
|
|
|
183
200
|
elif callable(A):
|
|
@@ -206,22 +223,21 @@ class AlgebraicForm(object):
|
|
|
206
223
|
self._moments = Moments(self.eig) # NOTE (never used)
|
|
207
224
|
|
|
208
225
|
# broad support
|
|
209
|
-
if self.
|
|
226
|
+
if self.supp is None:
|
|
210
227
|
if self.eig is None:
|
|
211
228
|
raise RuntimeError("Support must be provided without data")
|
|
212
229
|
|
|
213
230
|
# Detect support
|
|
214
|
-
self.lam_m, self.lam_p =
|
|
215
|
-
self.
|
|
231
|
+
self.lam_m, self.lam_p = estimate_broad_supp(self.eig, **kwargs)
|
|
232
|
+
self.broad_supp = (float(self.lam_m), float(self.lam_p))
|
|
216
233
|
else:
|
|
217
|
-
self.lam_m = float(min([s[0] for s in self.
|
|
218
|
-
self.lam_p = float(max([s[1] for s in self.
|
|
219
|
-
self.
|
|
234
|
+
self.lam_m = float(min([s[0] for s in self.supp]))
|
|
235
|
+
self.lam_p = float(max([s[1] for s in self.supp]))
|
|
236
|
+
self.broad_supp = (self.lam_m, self.lam_p)
|
|
220
237
|
|
|
221
238
|
# Initialize
|
|
222
|
-
self.
|
|
239
|
+
self.coeffs = None # Polynomial coefficients
|
|
223
240
|
self.status = None # Fitting status
|
|
224
|
-
self.cache = {} # Cache inner-computations
|
|
225
241
|
|
|
226
242
|
# ===
|
|
227
243
|
# fit
|
|
@@ -239,7 +255,7 @@ class AlgebraicForm(object):
|
|
|
239
255
|
normalize=False,
|
|
240
256
|
verbose=False):
|
|
241
257
|
"""
|
|
242
|
-
Fit
|
|
258
|
+
Fit an algebraic structure to the input data.
|
|
243
259
|
|
|
244
260
|
Parameters
|
|
245
261
|
----------
|
|
@@ -275,14 +291,14 @@ class AlgebraicForm(object):
|
|
|
275
291
|
z_fits = []
|
|
276
292
|
|
|
277
293
|
# Sampling around support, or broad_support. This is only needed to
|
|
278
|
-
# ensure sampled points are not
|
|
294
|
+
# ensure sampled points are not hitting the support itself is not used
|
|
279
295
|
# in any computation. If support is not known, use broad support.
|
|
280
|
-
if self.
|
|
281
|
-
|
|
296
|
+
if self.supp is not None:
|
|
297
|
+
possible_supp = self.supp
|
|
282
298
|
else:
|
|
283
|
-
|
|
299
|
+
possible_supp = [self.broad_supp]
|
|
284
300
|
|
|
285
|
-
for sup in
|
|
301
|
+
for sup in possible_supp:
|
|
286
302
|
a, b = sup
|
|
287
303
|
|
|
288
304
|
for i in range(len(r)):
|
|
@@ -292,30 +308,28 @@ class AlgebraicForm(object):
|
|
|
292
308
|
z_fit = numpy.concatenate(z_fits)
|
|
293
309
|
|
|
294
310
|
# Remove points too close to any cut
|
|
295
|
-
z_fit = filter_z_away_from_cuts(z_fit,
|
|
311
|
+
z_fit = filter_z_away_from_cuts(z_fit, possible_supp, y_eps=y_eps,
|
|
296
312
|
x_pad=x_pad)
|
|
297
313
|
|
|
298
314
|
# Fitting (w_inf = None means adaptive weight selection)
|
|
299
315
|
m1_fit = self._stieltjes(z_fit)
|
|
300
|
-
|
|
316
|
+
self.coeffs, fit_metrics = fit_polynomial_relation(
|
|
301
317
|
z_fit, m1_fit, s=deg_m, deg_z=deg_z, ridge_lambda=reg,
|
|
302
318
|
triangular=triangular, normalize=normalize, mu=mu,
|
|
303
319
|
mu_reg=mu_reg)
|
|
304
320
|
|
|
305
|
-
self.a_coeffs = a_coeffs
|
|
306
|
-
|
|
307
321
|
# Estimate support from the fitted polynomial
|
|
308
|
-
self.
|
|
322
|
+
self.est_supp, _ = self.support(self.coeffs)
|
|
309
323
|
|
|
310
324
|
# Reporting error
|
|
311
|
-
P_res = numpy.abs(eval_P(z_fit, m1_fit,
|
|
325
|
+
P_res = numpy.abs(eval_P(z_fit, m1_fit, self.coeffs))
|
|
312
326
|
res_max = numpy.max(P_res[numpy.isfinite(P_res)])
|
|
313
327
|
res_99_9 = numpy.quantile(P_res[numpy.isfinite(P_res)], 0.999)
|
|
314
328
|
|
|
315
329
|
# Check polynomial has Stieltjes root
|
|
316
330
|
x_min = self.lam_m - 1.0
|
|
317
331
|
x_max = self.lam_p + 1.0
|
|
318
|
-
status = sanity_check_stieltjes_branch(
|
|
332
|
+
status = sanity_check_stieltjes_branch(self.coeffs, x_min, x_max,
|
|
319
333
|
eta=max(y_eps, 1e-2), n_x=128,
|
|
320
334
|
max_bad_frac=0.05)
|
|
321
335
|
|
|
@@ -327,7 +341,7 @@ class AlgebraicForm(object):
|
|
|
327
341
|
# -----------------
|
|
328
342
|
|
|
329
343
|
# Inflate a bit to make sure all points are searched
|
|
330
|
-
# x_min, x_max = self.
|
|
344
|
+
# x_min, x_max = self._inflate_broad_supp(inflate=0.2)
|
|
331
345
|
# scale = float(max(1.0, abs(x_max - x_min), abs(x_min), abs(x_max)))
|
|
332
346
|
# eta = 1e-6 * scale
|
|
333
347
|
#
|
|
@@ -340,10 +354,10 @@ class AlgebraicForm(object):
|
|
|
340
354
|
# }
|
|
341
355
|
|
|
342
356
|
# NOTE overwrite init
|
|
343
|
-
self._stieltjes = StieltjesPoly(self.
|
|
344
|
-
# self._stieltjes = StieltjesPoly(self.
|
|
357
|
+
self._stieltjes = StieltjesPoly(self.coeffs)
|
|
358
|
+
# self._stieltjes = StieltjesPoly(self.coeffs, viterbi_opt=vopt)
|
|
345
359
|
|
|
346
|
-
self._moments_base = AlgebraicStieltjesMoments(
|
|
360
|
+
self._moments_base = AlgebraicStieltjesMoments(self.coeffs)
|
|
347
361
|
self.moments = Moments(self._moments_base)
|
|
348
362
|
|
|
349
363
|
if verbose:
|
|
@@ -352,14 +366,14 @@ class AlgebraicForm(object):
|
|
|
352
366
|
|
|
353
367
|
print('\nCoefficients (real)')
|
|
354
368
|
with numpy.printoptions(precision=8, suppress=True):
|
|
355
|
-
for i in range(
|
|
356
|
-
for j in range(
|
|
357
|
-
v =
|
|
369
|
+
for i in range(self.coeffs.shape[0]):
|
|
370
|
+
for j in range(self.coeffs.shape[1]):
|
|
371
|
+
v = self.coeffs[i, j]
|
|
358
372
|
print(f'{v.real:>+0.8f}', end=' ')
|
|
359
373
|
print('')
|
|
360
374
|
|
|
361
|
-
|
|
362
|
-
print(f'\nCoefficients (imag) norm: {
|
|
375
|
+
coeffs_img_norm = numpy.linalg.norm(self.coeffs.imag, ord='fro')
|
|
376
|
+
print(f'\nCoefficients (imag) norm: {coeffs_img_norm:>0.4e}')
|
|
363
377
|
|
|
364
378
|
if not status['ok']:
|
|
365
379
|
print("\nWARNING: sanity check failed:\n" +
|
|
@@ -369,17 +383,22 @@ class AlgebraicForm(object):
|
|
|
369
383
|
else:
|
|
370
384
|
print('\nStieltjes sanity check: OK')
|
|
371
385
|
|
|
372
|
-
return
|
|
386
|
+
return self.coeffs, self.est_supp, status
|
|
373
387
|
|
|
374
|
-
#
|
|
375
|
-
# inflate broad
|
|
376
|
-
#
|
|
388
|
+
# ==================
|
|
389
|
+
# inflate broad supp
|
|
390
|
+
# ==================
|
|
377
391
|
|
|
378
|
-
def
|
|
392
|
+
def _inflate_broad_supp(self, inflate=0.0):
|
|
379
393
|
"""
|
|
394
|
+
Inflate the broad support for better post-processing, such as detecting
|
|
395
|
+
branch points, spectral edges, etc.
|
|
380
396
|
"""
|
|
381
397
|
|
|
382
|
-
|
|
398
|
+
if inflate < 0:
|
|
399
|
+
raise ValueError('"inflate" should be non-negative.')
|
|
400
|
+
|
|
401
|
+
min_supp, max_supp = self.broad_supp
|
|
383
402
|
|
|
384
403
|
c_supp = 0.5 * (max_supp + min_supp)
|
|
385
404
|
r_supp = 0.5 * (max_supp - min_supp)
|
|
@@ -389,68 +408,48 @@ class AlgebraicForm(object):
|
|
|
389
408
|
|
|
390
409
|
return x_min, x_max
|
|
391
410
|
|
|
392
|
-
#
|
|
393
|
-
#
|
|
394
|
-
#
|
|
411
|
+
# =======
|
|
412
|
+
# support
|
|
413
|
+
# =======
|
|
395
414
|
|
|
396
|
-
def
|
|
415
|
+
def support(self, coeffs=None, scan_range=None, n_scan=4000):
|
|
397
416
|
"""
|
|
417
|
+
Estimate the spectral edges of the density.
|
|
398
418
|
"""
|
|
399
419
|
|
|
400
|
-
if
|
|
401
|
-
if self.
|
|
420
|
+
if coeffs is None:
|
|
421
|
+
if self.coeffs is None:
|
|
402
422
|
raise RuntimeError('Call "fit" first.')
|
|
403
423
|
else:
|
|
404
|
-
|
|
424
|
+
coeffs = self.coeffs
|
|
405
425
|
|
|
406
426
|
# Inflate a bit to make sure all points are searched
|
|
407
427
|
if scan_range is not None:
|
|
408
428
|
x_min, x_max = scan_range
|
|
409
429
|
else:
|
|
410
|
-
x_min, x_max = self.
|
|
411
|
-
|
|
412
|
-
est_support, info = compute_support(a_coeffs, x_min=x_min, x_max=x_max,
|
|
413
|
-
n_scan=n_scan)
|
|
414
|
-
|
|
415
|
-
return est_support, info
|
|
416
|
-
|
|
417
|
-
# ======================
|
|
418
|
-
# estimate branch points
|
|
419
|
-
# ======================
|
|
430
|
+
x_min, x_max = self._inflate_broad_supp(inflate=0.2)
|
|
420
431
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
Compute global branch points and zeros of leading a_j
|
|
424
|
-
"""
|
|
425
|
-
|
|
426
|
-
if self.a_coeffs is None:
|
|
427
|
-
raise RuntimeError('Call "fit" first.')
|
|
432
|
+
est_supp, info = estimate_support(coeffs, x_min=x_min, x_max=x_max,
|
|
433
|
+
n_scan=n_scan)
|
|
428
434
|
|
|
429
|
-
|
|
430
|
-
self.a_coeffs, tol=tol, real_tol=real_tol)
|
|
431
|
-
|
|
432
|
-
return bp, leading_zeros, info
|
|
435
|
+
return est_supp, info
|
|
433
436
|
|
|
434
437
|
# =============
|
|
435
|
-
#
|
|
438
|
+
# branch points
|
|
436
439
|
# =============
|
|
437
440
|
|
|
438
|
-
def
|
|
441
|
+
def branch_points(self, tol=1e-15, real_tol=None):
|
|
439
442
|
"""
|
|
440
|
-
|
|
441
|
-
transforms.
|
|
443
|
+
Compute global branch points and zeros of leading coefficient.
|
|
442
444
|
"""
|
|
443
445
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
x_min = numpy.floor(extend * (center - extend * radius * scale))
|
|
448
|
-
x_max = numpy.ceil(extend * (center + extend * radius * scale))
|
|
446
|
+
if self.coeffs is None:
|
|
447
|
+
raise RuntimeError('Call "fit" first.')
|
|
449
448
|
|
|
450
|
-
|
|
451
|
-
|
|
449
|
+
bp, leading_zeros, info = estimate_branch_points(
|
|
450
|
+
self.coeffs, tol=tol, real_tol=real_tol)
|
|
452
451
|
|
|
453
|
-
return
|
|
452
|
+
return bp, leading_zeros, info
|
|
454
453
|
|
|
455
454
|
# =======
|
|
456
455
|
# density
|
|
@@ -498,7 +497,7 @@ class AlgebraicForm(object):
|
|
|
498
497
|
>>> from freealg import FreeForm
|
|
499
498
|
"""
|
|
500
499
|
|
|
501
|
-
if self.
|
|
500
|
+
if self.coeffs is None:
|
|
502
501
|
raise RuntimeError('The model needs to be fit using the .fit() ' +
|
|
503
502
|
'function.')
|
|
504
503
|
|
|
@@ -511,7 +510,7 @@ class AlgebraicForm(object):
|
|
|
511
510
|
rho = self._stieltjes(z).imag / numpy.pi
|
|
512
511
|
|
|
513
512
|
if plot:
|
|
514
|
-
plot_density(x, rho, eig=self.eig, support=self.
|
|
513
|
+
plot_density(x, rho, eig=self.eig, support=self.broad_supp,
|
|
515
514
|
label='Estimate', latex=latex, save=save)
|
|
516
515
|
|
|
517
516
|
return rho
|
|
@@ -563,7 +562,7 @@ class AlgebraicForm(object):
|
|
|
563
562
|
>>> from freealg import FreeForm
|
|
564
563
|
"""
|
|
565
564
|
|
|
566
|
-
if self.
|
|
565
|
+
if self.coeffs is None:
|
|
567
566
|
raise RuntimeError('The model needs to be fit using the .fit() ' +
|
|
568
567
|
'function.')
|
|
569
568
|
|
|
@@ -575,7 +574,7 @@ class AlgebraicForm(object):
|
|
|
575
574
|
hilb = -self._stieltjes(x).real / numpy.pi
|
|
576
575
|
|
|
577
576
|
if plot:
|
|
578
|
-
plot_hilbert(x, hilb, support=self.
|
|
577
|
+
plot_hilbert(x, hilb, support=self.broad_supp, latex=latex,
|
|
579
578
|
save=save)
|
|
580
579
|
|
|
581
580
|
return hilb
|
|
@@ -586,7 +585,7 @@ class AlgebraicForm(object):
|
|
|
586
585
|
|
|
587
586
|
def stieltjes(self, x=None, y=None, plot=False, latex=False, save=False):
|
|
588
587
|
"""
|
|
589
|
-
Compute Stieltjes transform of the spectral density
|
|
588
|
+
Compute Stieltjes transform of the spectral density
|
|
590
589
|
|
|
591
590
|
This function evaluates Stieltjes transform on an array of points, or
|
|
592
591
|
over a 2D Cartesian grid on the complex plane.
|
|
@@ -635,7 +634,7 @@ class AlgebraicForm(object):
|
|
|
635
634
|
>>> from freealg import FreeForm
|
|
636
635
|
"""
|
|
637
636
|
|
|
638
|
-
if self.
|
|
637
|
+
if self.coeffs is None:
|
|
639
638
|
raise RuntimeError('The model needs to be fit using the .fit() ' +
|
|
640
639
|
'function.')
|
|
641
640
|
|
|
@@ -659,44 +658,11 @@ class AlgebraicForm(object):
|
|
|
659
658
|
m = self._stieltjes(z, progress=True)
|
|
660
659
|
|
|
661
660
|
if plot:
|
|
662
|
-
plot_stieltjes(x, y, m, m, self.
|
|
661
|
+
plot_stieltjes(x, y, m, m, self.broad_supp, latex=latex,
|
|
663
662
|
save=save)
|
|
664
663
|
|
|
665
664
|
return m
|
|
666
665
|
|
|
667
|
-
# ==============
|
|
668
|
-
# eval stieltjes
|
|
669
|
-
# ==============
|
|
670
|
-
|
|
671
|
-
def _eval_stieltjes(self, z, branches=False):
|
|
672
|
-
"""
|
|
673
|
-
Compute Stieltjes transform of the spectral density.
|
|
674
|
-
|
|
675
|
-
Parameters
|
|
676
|
-
----------
|
|
677
|
-
|
|
678
|
-
z : numpy.array
|
|
679
|
-
The z values in the complex plan where the Stieltjes transform is
|
|
680
|
-
evaluated.
|
|
681
|
-
|
|
682
|
-
branches : bool, default = False
|
|
683
|
-
Return both the principal and secondary branches of the Stieltjes
|
|
684
|
-
transform. The default ``branches=False`` will return only
|
|
685
|
-
the secondary branch.
|
|
686
|
-
|
|
687
|
-
Returns
|
|
688
|
-
-------
|
|
689
|
-
|
|
690
|
-
m_p : numpy.ndarray
|
|
691
|
-
The Stieltjes transform on the principal branch if
|
|
692
|
-
``branches=True``.
|
|
693
|
-
|
|
694
|
-
m_m : numpy.ndarray
|
|
695
|
-
The Stieltjes transform continued to the secondary branch.
|
|
696
|
-
"""
|
|
697
|
-
|
|
698
|
-
pass
|
|
699
|
-
|
|
700
666
|
# ==========
|
|
701
667
|
# decompress
|
|
702
668
|
# ==========
|
|
@@ -756,7 +722,7 @@ class AlgebraicForm(object):
|
|
|
756
722
|
|
|
757
723
|
# Evolve
|
|
758
724
|
W, ok = decompress_newton(
|
|
759
|
-
z_query, t_all, self.
|
|
725
|
+
z_query, t_all, self.coeffs,
|
|
760
726
|
w0_list=w0_list, **newton_opt)
|
|
761
727
|
|
|
762
728
|
rho_all = W.imag / numpy.pi
|
|
@@ -775,7 +741,7 @@ class AlgebraicForm(object):
|
|
|
775
741
|
# Decompress to each alpha
|
|
776
742
|
for i in range(alpha.size):
|
|
777
743
|
t_i = numpy.log(alpha[i])
|
|
778
|
-
coeffs_i = decompress_coeffs(self.
|
|
744
|
+
coeffs_i = decompress_coeffs(self.coeffs, t_i)
|
|
779
745
|
|
|
780
746
|
def mom(k):
|
|
781
747
|
return self.moments(k, t_i)
|
|
@@ -808,6 +774,9 @@ class AlgebraicForm(object):
|
|
|
808
774
|
# ==========
|
|
809
775
|
|
|
810
776
|
def candidates(self, size, x=None, verbose=False):
|
|
777
|
+
"""
|
|
778
|
+
Candidate densities of free decompression from all possible roots
|
|
779
|
+
"""
|
|
811
780
|
|
|
812
781
|
# Check size argument
|
|
813
782
|
if numpy.isscalar(size):
|
|
@@ -842,410 +811,235 @@ class AlgebraicForm(object):
|
|
|
842
811
|
|
|
843
812
|
for i in range(alpha.size):
|
|
844
813
|
t_i = numpy.log(alpha[i])
|
|
845
|
-
coeffs_i = decompress_coeffs(self.
|
|
814
|
+
coeffs_i = decompress_coeffs(self.coeffs, t_i)
|
|
846
815
|
plot_candidates(coeffs_i, x, size=int(alpha[i]*self.n),
|
|
847
816
|
verbose=verbose)
|
|
848
817
|
|
|
849
|
-
#
|
|
850
|
-
#
|
|
851
|
-
#
|
|
852
|
-
|
|
853
|
-
def edge(self, t, eta=1e-3, dt_max=0.1, max_iter=30, tol=1e-12,
|
|
854
|
-
verbose=False):
|
|
855
|
-
"""
|
|
856
|
-
Evolves spectral edges.
|
|
818
|
+
# =================
|
|
819
|
+
# is decompressible
|
|
820
|
+
# =================
|
|
857
821
|
|
|
858
|
-
|
|
859
|
-
|
|
822
|
+
def is_decompressible(self, ratio=2, n_ratios=5, K=(6, 8, 10), L=3,
|
|
823
|
+
tol=1e-8, verbose=False):
|
|
860
824
|
"""
|
|
825
|
+
Check if the given distribution can be decompressed.
|
|
861
826
|
|
|
862
|
-
if
|
|
863
|
-
|
|
864
|
-
elif self.est_support is not None:
|
|
865
|
-
known_support = self.est_support
|
|
866
|
-
else:
|
|
867
|
-
raise RuntimeError('Call "fit" first.')
|
|
868
|
-
|
|
869
|
-
t = numpy.asarray(t, dtype=float).ravel()
|
|
827
|
+
To this end, this function checks if the evolved polynomial under the
|
|
828
|
+
free decompression admits a valid Stieltjes root.
|
|
870
829
|
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
if t1 == 0.0:
|
|
874
|
-
t_grid = numpy.array([0.0], dtype=float)
|
|
875
|
-
complex_edges, ok_edges = evolve_edges(
|
|
876
|
-
t_grid, self.a_coeffs, support=known_support, eta=eta,
|
|
877
|
-
dt_max=dt_max, max_iter=max_iter, tol=tol
|
|
878
|
-
)
|
|
879
|
-
else:
|
|
880
|
-
# prepend 0 and drop it after evolution
|
|
881
|
-
t_grid = numpy.array([0.0, t1], dtype=float)
|
|
882
|
-
complex_edges2, ok_edges2 = evolve_edges(
|
|
883
|
-
t_grid, self.a_coeffs, support=known_support, eta=eta,
|
|
884
|
-
dt_max=dt_max, max_iter=max_iter, tol=tol
|
|
885
|
-
)
|
|
886
|
-
complex_edges = complex_edges2[-1:, :]
|
|
887
|
-
ok_edges = ok_edges2[-1:, :]
|
|
888
|
-
else:
|
|
889
|
-
# For vector t, require it starts at 0 for correct initialization
|
|
890
|
-
# (you can relax this if you want by prepending 0 similarly).
|
|
891
|
-
complex_edges, ok_edges = evolve_edges(
|
|
892
|
-
t, self.a_coeffs, support=known_support, eta=eta,
|
|
893
|
-
dt_max=dt_max, max_iter=max_iter, tol=tol
|
|
894
|
-
)
|
|
830
|
+
Parameters
|
|
831
|
+
----------
|
|
895
832
|
|
|
896
|
-
|
|
833
|
+
ratio : float, default=2
|
|
834
|
+
The maximum ratio of decompressed matrix size to the original size.
|
|
897
835
|
|
|
898
|
-
|
|
899
|
-
|
|
836
|
+
n_ratios : int, default=5
|
|
837
|
+
Number of ratios to test from 1 (no decompression) to the given
|
|
838
|
+
maximum ``ratio``.
|
|
900
839
|
|
|
901
|
-
|
|
902
|
-
|
|
840
|
+
K : sequence of int, default=(6, 8, 10)
|
|
841
|
+
Truncation orders ``K`` used to build the Laurent cancellation
|
|
842
|
+
system. Each ``K`` enforces powers ``p`` in ``[-L, ..., K]``.
|
|
903
843
|
|
|
904
|
-
|
|
844
|
+
L : int, default=3
|
|
845
|
+
Number of negative powers to enforce. The enforced power range is
|
|
846
|
+
``p in [-L, ..., K]`` for each ``K``.
|
|
905
847
|
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
848
|
+
tol : float, default=1e-10
|
|
849
|
+
Pass threshold for the best residual over ``K``. The residual
|
|
850
|
+
is ``max_p |c_p|`` over enforced powers, where ``c_p`` are the
|
|
851
|
+
Laurent coefficients of :math:`P_t(1/w, m(w))`.
|
|
909
852
|
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
"""
|
|
853
|
+
verbose : bool, default=True
|
|
854
|
+
If True, print a per-``t`` summary and the per-``K`` diagnostics.
|
|
913
855
|
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
# ========
|
|
918
|
-
# eigvalsh
|
|
919
|
-
# ========
|
|
920
|
-
|
|
921
|
-
def eigvalsh(self, size=None, seed=None, **kwargs):
|
|
922
|
-
"""
|
|
923
|
-
Estimate the eigenvalues.
|
|
924
|
-
|
|
925
|
-
This function estimates the eigenvalues of the freeform matrix
|
|
926
|
-
or a larger matrix containing it using free decompression.
|
|
927
|
-
|
|
928
|
-
Parameters
|
|
929
|
-
----------
|
|
856
|
+
Returns
|
|
857
|
+
-------
|
|
930
858
|
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
:math:`\\mathbf{A}` itself.
|
|
859
|
+
status : array
|
|
860
|
+
Boolean array of `True` or `False for each time in ``t``.
|
|
861
|
+
`True` means decompressible, and `False` means not decompressible.
|
|
935
862
|
|
|
936
|
-
|
|
937
|
-
|
|
863
|
+
info : dict
|
|
864
|
+
Dictionary with the following keys
|
|
938
865
|
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
866
|
+
* ``'ratios'``: List of decompression ratios that is checked.
|
|
867
|
+
* ``'ok'``: status of the decompressiblity at the tested ratio.
|
|
868
|
+
* ``'res'``: details of test for each ratio.
|
|
942
869
|
|
|
943
|
-
|
|
944
|
-
|
|
870
|
+
Raises
|
|
871
|
+
------
|
|
945
872
|
|
|
946
|
-
|
|
947
|
-
|
|
873
|
+
ValueError
|
|
874
|
+
If ``K`` is empty, or if any ``K`` or ``L`` is not positive.
|
|
948
875
|
|
|
949
876
|
See Also
|
|
950
877
|
--------
|
|
951
878
|
|
|
952
|
-
|
|
953
|
-
|
|
879
|
+
branch_points :
|
|
880
|
+
Geometric branch-point estimation; complementary to this asymptotic
|
|
881
|
+
check.
|
|
954
882
|
|
|
955
883
|
Notes
|
|
956
884
|
-----
|
|
957
885
|
|
|
958
|
-
|
|
886
|
+
This is a ""no-FD-run" diagnostic: it only uses the base algebraic
|
|
887
|
+
relation :math:`P(z,m)=0` (stored in ``self.coeffs``) and tests the
|
|
888
|
+
pushed relation under free decompression (FD) at selected expansion
|
|
889
|
+
factors ``t``.
|
|
959
890
|
|
|
960
|
-
|
|
961
|
-
--------
|
|
891
|
+
The FD pushforward used here is the characteristic change-of-variables
|
|
962
892
|
|
|
963
|
-
|
|
964
|
-
|
|
893
|
+
* :math:`\\tau = e^t`
|
|
894
|
+
* :math:`y = \\tau * m`
|
|
895
|
+
* :math:`\\zeta = z + (1 - 1/\\tau) / m`
|
|
896
|
+
* :math:`P_t(z, m) = P(\\zeta, y)`
|
|
965
897
|
|
|
966
|
-
|
|
967
|
-
"
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
# size = self.n
|
|
971
|
-
#
|
|
972
|
-
# rho, x = self.decompress(size, **kwargs)
|
|
973
|
-
# eigs = numpy.sort(sample(x, rho, size, method='qmc', seed=seed))
|
|
974
|
-
#
|
|
975
|
-
# return eigs
|
|
976
|
-
pass
|
|
977
|
-
|
|
978
|
-
# ====
|
|
979
|
-
# cond
|
|
980
|
-
# ====
|
|
898
|
+
A necessary condition for FD tracking to remain well-posed is that, for
|
|
899
|
+
each :math:`\\tau > 1`), the pushed relation admits a "Stieltjes branch
|
|
900
|
+
at infinity", i.e., a solution branch with the large
|
|
901
|
+
:math:`\\vert z \\vert` behavior
|
|
981
902
|
|
|
982
|
-
|
|
983
|
-
"""
|
|
984
|
-
Estimate the condition number.
|
|
903
|
+
.. math::
|
|
985
904
|
|
|
986
|
-
|
|
987
|
-
:math:`\\mathbf{A}` or a larger matrix containing :math:`\\mathbf{A}`
|
|
988
|
-
using free decompression.
|
|
905
|
+
m(z) = -1/z + O(1/z^2)
|
|
989
906
|
|
|
990
|
-
|
|
991
|
-
----------
|
|
992
|
-
|
|
993
|
-
size : int, default=None
|
|
994
|
-
The size of the matrix containing :math:`\\mathbf{A}` to estimate
|
|
995
|
-
eigenvalues of. If None, returns estimates of the eigenvalues of
|
|
996
|
-
:math:`\\mathbf{A}` itself.
|
|
907
|
+
as :math:`\\vert z \\vert \\to \\infty` in :math:`\\Im(z) > 0`.
|
|
997
908
|
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
909
|
+
This routine enforces that asymptotic structure by constructing a
|
|
910
|
+
truncated Laurent expansion in :math:`w = 1/z`. It solves for
|
|
911
|
+
coefficients in
|
|
1001
912
|
|
|
1002
|
-
|
|
1003
|
-
-------
|
|
913
|
+
.. math::
|
|
1004
914
|
|
|
1005
|
-
|
|
1006
|
-
Condition number
|
|
915
|
+
m(z) = -(alpha/z + mu_1/z^2 + mu_2/z^3 + ...)
|
|
1007
916
|
|
|
1008
|
-
|
|
1009
|
-
|
|
917
|
+
so that the Laurent series of :math:`P_t(1/w, m(w))` cancels on a
|
|
918
|
+
prescribed range of powers. Concretely, for each truncation order
|
|
919
|
+
``K``, we enforce the cancellation of powers ``p`` in ``[-L, ..., K]``
|
|
920
|
+
and measure the maximum absolute residual among those enforced
|
|
921
|
+
coefficients. The best (smallest) residual across ``K`` is used for the
|
|
922
|
+
main pass/fail decision.
|
|
1010
923
|
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
924
|
+
* ``K`` controls how many asymptotic constraints are enforced. Larger
|
|
925
|
+
``K`` is usually stricter but can become sensitive to coefficient
|
|
926
|
+
noise (e.g. from a fitted polynomial).
|
|
927
|
+
* ``L`` controls how many negative powers are enforced. A safe default
|
|
928
|
+
is ``deg_z + 2``; here we expose it directly so you can tune it per
|
|
929
|
+
model.
|
|
1015
930
|
|
|
1016
931
|
Examples
|
|
1017
932
|
--------
|
|
1018
933
|
|
|
1019
934
|
.. code-block:: python
|
|
1020
|
-
:emphasize-lines:
|
|
935
|
+
:emphasize-lines: 9,10
|
|
1021
936
|
|
|
1022
|
-
>>>
|
|
1023
|
-
|
|
937
|
+
>>> import freealg AlgebraicForm
|
|
938
|
+
>>> from freealg.distributions import CompoundPoisson
|
|
1024
939
|
|
|
1025
|
-
|
|
1026
|
-
|
|
940
|
+
>>> # Create compound free Poisson law
|
|
941
|
+
>>> cp = CompoundPoisson(t1=2.0, t2=5.5, w1=0.75, c=0.1)
|
|
942
|
+
>>> af = AlgebraicForm(cp)
|
|
1027
943
|
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
944
|
+
>>> # Check the decompressibility of compound free Poisson
|
|
945
|
+
>>> status, info = af.is_decompressible(ratio=2, n_ratios=5,
|
|
946
|
+
... verbose=True)
|
|
1031
947
|
|
|
1032
|
-
|
|
948
|
+
>>> status
|
|
949
|
+
True
|
|
1033
950
|
"""
|
|
1034
|
-
Estimate the trace of a power.
|
|
1035
951
|
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
containing it.
|
|
952
|
+
if self.coeffs is None:
|
|
953
|
+
raise RuntimeError('"fit" model first.')
|
|
1039
954
|
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
size : int, default=None
|
|
1044
|
-
The size of the matrix containing :math:`\\mathbf{A}` to estimate
|
|
1045
|
-
eigenvalues of. If None, returns estimates of the eigenvalues of
|
|
1046
|
-
:math:`\\mathbf{A}` itself.
|
|
1047
|
-
|
|
1048
|
-
p : float, default=1.0
|
|
1049
|
-
The exponent :math:`p` in :math:`\\mathbf{A}^p`.
|
|
1050
|
-
|
|
1051
|
-
seed : int, default=None
|
|
1052
|
-
The seed for the Quasi-Monte Carlo sampler.
|
|
1053
|
-
|
|
1054
|
-
**kwargs : dict, optional
|
|
1055
|
-
Pass additional options to the underlying
|
|
1056
|
-
:func:`FreeForm.decompress` function.
|
|
1057
|
-
|
|
1058
|
-
Returns
|
|
1059
|
-
-------
|
|
955
|
+
if ratio < 1:
|
|
956
|
+
raise ValueError('"ratio" cannot be smaller than 1.')
|
|
1060
957
|
|
|
1061
|
-
|
|
1062
|
-
|
|
958
|
+
tau = numpy.linspace(0.0, ratio, n_ratios)
|
|
959
|
+
ok = numpy.zeros_like(tau, dtype=bool)
|
|
960
|
+
res = [] * tau.size
|
|
1063
961
|
|
|
1064
|
-
|
|
1065
|
-
|
|
962
|
+
for i in range(tau.size):
|
|
963
|
+
ok[i], res[i] = precheck_laurent(self.coeffs, tau[i], K=K, L=L,
|
|
964
|
+
tol=tol, verbose=verbose)
|
|
1066
965
|
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
FreeForm.slogdet
|
|
1070
|
-
FreeForm.norm
|
|
966
|
+
if verbose:
|
|
967
|
+
print("")
|
|
1071
968
|
|
|
1072
|
-
|
|
1073
|
-
-----
|
|
969
|
+
status = numpy.any(numpy.logical_not(ok))
|
|
1074
970
|
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
971
|
+
info = {
|
|
972
|
+
'ratios': tau,
|
|
973
|
+
'ok': ok,
|
|
974
|
+
'res': res,
|
|
975
|
+
}
|
|
1079
976
|
|
|
1080
|
-
|
|
1081
|
-
--------
|
|
977
|
+
return status, info
|
|
1082
978
|
|
|
1083
|
-
|
|
1084
|
-
|
|
979
|
+
# ====
|
|
980
|
+
# edge
|
|
981
|
+
# ====
|
|
1085
982
|
|
|
1086
|
-
|
|
983
|
+
def edge(self, t, eta=1e-3, dt_max=0.1, max_iter=30, tol=1e-12,
|
|
984
|
+
verbose=False):
|
|
1087
985
|
"""
|
|
986
|
+
Evolves spectral edges.
|
|
1088
987
|
|
|
1089
|
-
if
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
eig = self.eigvalsh(size=size, seed=seed, **kwargs)
|
|
1093
|
-
return numpy.sum(eig ** p)
|
|
1094
|
-
|
|
1095
|
-
# =======
|
|
1096
|
-
# slogdet
|
|
1097
|
-
# =======
|
|
1098
|
-
|
|
1099
|
-
def slogdet(self, size=None, seed=None, **kwargs):
|
|
988
|
+
Fix: if t is a scalar or length-1 array, we prepend t=0 internally so
|
|
989
|
+
evolve_edges actually advances from the initialization at t=0.
|
|
1100
990
|
"""
|
|
1101
|
-
Estimate the sign and logarithm of the determinant.
|
|
1102
|
-
|
|
1103
|
-
This function estimates the *slogdet* of the freeform or that of
|
|
1104
|
-
a larger matrix containing it using free decompression.
|
|
1105
991
|
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
:math:`\\mathbf{A}` itself.
|
|
1113
|
-
|
|
1114
|
-
seed : int, default=None
|
|
1115
|
-
The seed for the Quasi-Monte Carlo sampler.
|
|
1116
|
-
|
|
1117
|
-
Returns
|
|
1118
|
-
-------
|
|
1119
|
-
|
|
1120
|
-
sign : float
|
|
1121
|
-
Sign of determinant
|
|
1122
|
-
|
|
1123
|
-
ld : float
|
|
1124
|
-
natural logarithm of the absolute value of the determinant
|
|
1125
|
-
|
|
1126
|
-
See Also
|
|
1127
|
-
--------
|
|
992
|
+
if self.supp is not None:
|
|
993
|
+
known_supp = self.supp
|
|
994
|
+
elif self.est_supp is not None:
|
|
995
|
+
known_supp = self.est_supp
|
|
996
|
+
else:
|
|
997
|
+
raise RuntimeError('Call "fit" first.')
|
|
1128
998
|
|
|
1129
|
-
|
|
1130
|
-
FreeForm.cond
|
|
1131
|
-
FreeForm.trace
|
|
1132
|
-
FreeForm.norm
|
|
999
|
+
t = numpy.asarray(t, dtype=float).ravel()
|
|
1133
1000
|
|
|
1134
|
-
|
|
1135
|
-
|
|
1001
|
+
if t.size == 1:
|
|
1002
|
+
t1 = float(t[0])
|
|
1003
|
+
if t1 == 0.0:
|
|
1004
|
+
t_grid = numpy.array([0.0], dtype=float)
|
|
1005
|
+
complex_edges, ok_edges = evolve_edges(
|
|
1006
|
+
t_grid, self.coeffs, support=known_supp, eta=eta,
|
|
1007
|
+
dt_max=dt_max, max_iter=max_iter, tol=tol
|
|
1008
|
+
)
|
|
1009
|
+
else:
|
|
1010
|
+
# prepend 0 and drop it after evolution
|
|
1011
|
+
t_grid = numpy.array([0.0, t1], dtype=float)
|
|
1012
|
+
complex_edges2, ok_edges2 = evolve_edges(
|
|
1013
|
+
t_grid, self.coeffs, support=known_supp, eta=eta,
|
|
1014
|
+
dt_max=dt_max, max_iter=max_iter, tol=tol)
|
|
1136
1015
|
|
|
1137
|
-
|
|
1016
|
+
complex_edges = complex_edges2[-1:, :]
|
|
1017
|
+
ok_edges = ok_edges2[-1:, :]
|
|
1018
|
+
else:
|
|
1019
|
+
# For vector t, require it starts at 0 for correct initialization
|
|
1020
|
+
# (you can relax this if you want by prepending 0 similarly).
|
|
1021
|
+
complex_edges, ok_edges = evolve_edges(
|
|
1022
|
+
t, self.coeffs, support=known_supp, eta=eta,
|
|
1023
|
+
dt_max=dt_max, max_iter=max_iter, tol=tol)
|
|
1138
1024
|
|
|
1139
|
-
|
|
1140
|
-
--------
|
|
1025
|
+
real_edges = complex_edges.real
|
|
1141
1026
|
|
|
1142
|
-
|
|
1143
|
-
|
|
1027
|
+
# Remove spurious edges / merges for plotting
|
|
1028
|
+
real_merged_edges, active_k = merge_edges(real_edges, tol=1e-4)
|
|
1144
1029
|
|
|
1145
|
-
|
|
1146
|
-
|
|
1030
|
+
if verbose:
|
|
1031
|
+
print("edge success rate:", ok_edges.mean())
|
|
1147
1032
|
|
|
1148
|
-
|
|
1149
|
-
sign = numpy.prod(numpy.sign(eigs))
|
|
1150
|
-
ld = numpy.sum(numpy.log(numpy.abs(eigs)))
|
|
1151
|
-
return sign, ld
|
|
1033
|
+
return complex_edges, real_merged_edges, active_k
|
|
1152
1034
|
|
|
1153
1035
|
# ====
|
|
1154
|
-
#
|
|
1036
|
+
# cusp
|
|
1155
1037
|
# ====
|
|
1156
1038
|
|
|
1157
|
-
def
|
|
1039
|
+
def cusp(self, t_grid):
|
|
1158
1040
|
"""
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
This function estimates the norm of the freeform or a larger
|
|
1162
|
-
matrix containing it using free decompression.
|
|
1163
|
-
|
|
1164
|
-
Parameters
|
|
1165
|
-
----------
|
|
1166
|
-
|
|
1167
|
-
size : int, default=None
|
|
1168
|
-
The size of the matrix containing :math:`\\mathbf{A}` to estimate
|
|
1169
|
-
eigenvalues of. If None, returns estimates of the eigenvalues of
|
|
1170
|
-
:math:`\\mathbf{A}` itself.
|
|
1171
|
-
|
|
1172
|
-
order : {float, ``''inf``, ``'-inf'``, ``'fro'``, ``'nuc'``}, default=2
|
|
1173
|
-
Order of the norm.
|
|
1174
|
-
|
|
1175
|
-
* float :math:`p`: Schatten p-norm.
|
|
1176
|
-
* ``'inf'``: Largest absolute eigenvalue
|
|
1177
|
-
:math:`\\max \\vert \\lambda_i \\vert)`
|
|
1178
|
-
* ``'-inf'``: Smallest absolute eigenvalue
|
|
1179
|
-
:math:`\\min \\vert \\lambda_i \\vert)`
|
|
1180
|
-
* ``'fro'``: Frobenius norm corresponding to :math:`p=2`
|
|
1181
|
-
* ``'nuc'``: Nuclear (or trace) norm corresponding to :math:`p=1`
|
|
1182
|
-
|
|
1183
|
-
seed : int, default=None
|
|
1184
|
-
The seed for the Quasi-Monte Carlo sampler.
|
|
1185
|
-
|
|
1186
|
-
**kwargs : dict, optional
|
|
1187
|
-
Pass additional options to the underlying
|
|
1188
|
-
:func:`FreeForm.decompress` function.
|
|
1189
|
-
|
|
1190
|
-
Returns
|
|
1191
|
-
-------
|
|
1192
|
-
|
|
1193
|
-
norm : float
|
|
1194
|
-
matrix norm
|
|
1195
|
-
|
|
1196
|
-
See Also
|
|
1197
|
-
--------
|
|
1198
|
-
|
|
1199
|
-
FreeForm.eigvalsh
|
|
1200
|
-
FreeForm.cond
|
|
1201
|
-
FreeForm.slogdet
|
|
1202
|
-
FreeForm.trace
|
|
1203
|
-
|
|
1204
|
-
Notes
|
|
1205
|
-
-----
|
|
1206
|
-
|
|
1207
|
-
Thes Schatten :math:`p`-norm is defined by
|
|
1208
|
-
|
|
1209
|
-
.. math::
|
|
1210
|
-
|
|
1211
|
-
\\Vert \\mathbf{A} \\Vert_p = \\left(
|
|
1212
|
-
\\sum_{i=1}^N \\vert \\lambda_i \\vert^p \\right)^{1/p}.
|
|
1213
|
-
|
|
1214
|
-
Examples
|
|
1215
|
-
--------
|
|
1216
|
-
|
|
1217
|
-
.. code-block:: python
|
|
1218
|
-
:emphasize-lines: 1
|
|
1219
|
-
|
|
1220
|
-
>>> from freealg import FreeForm
|
|
1041
|
+
Find cusp (merge) point of evolving spectral edges
|
|
1221
1042
|
"""
|
|
1222
1043
|
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
# Check order type and convert to float
|
|
1226
|
-
if order == 'nuc':
|
|
1227
|
-
order = 1
|
|
1228
|
-
elif order == 'fro':
|
|
1229
|
-
order = 2
|
|
1230
|
-
elif order == 'inf':
|
|
1231
|
-
order = float('inf')
|
|
1232
|
-
elif order == '-inf':
|
|
1233
|
-
order = -float('inf')
|
|
1234
|
-
elif not isinstance(order,
|
|
1235
|
-
(int, float, numpy.integer, numpy.floating)) \
|
|
1236
|
-
and not isinstance(order, (bool, numpy.bool_)):
|
|
1237
|
-
raise ValueError('"order" is invalid.')
|
|
1238
|
-
|
|
1239
|
-
# Compute norm
|
|
1240
|
-
if numpy.isinf(order) and not numpy.isneginf(order):
|
|
1241
|
-
norm_ = max(numpy.abs(eigs))
|
|
1242
|
-
|
|
1243
|
-
elif numpy.isneginf(order):
|
|
1244
|
-
norm_ = min(numpy.abs(eigs))
|
|
1245
|
-
|
|
1246
|
-
elif isinstance(order, (int, float, numpy.integer, numpy.floating)) \
|
|
1247
|
-
and not isinstance(order, (bool, numpy.bool_)):
|
|
1248
|
-
norm_q = numpy.sum(numpy.abs(eigs)**order)
|
|
1249
|
-
norm_ = norm_q**(1.0 / order)
|
|
1250
|
-
|
|
1251
|
-
return norm_
|
|
1044
|
+
return cusp_wrap(self, t_grid, edge_kwargs=None, max_iter=50,
|
|
1045
|
+
tol=1.0e-12)
|