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.
Files changed (59) hide show
  1. freealg/__init__.py +8 -2
  2. freealg/__version__.py +1 -1
  3. freealg/_algebraic_form/__init__.py +12 -0
  4. freealg/_algebraic_form/_branch_points.py +288 -0
  5. freealg/_algebraic_form/_constraints.py +139 -0
  6. freealg/_algebraic_form/_continuation_algebraic.py +706 -0
  7. freealg/_algebraic_form/_decompress.py +641 -0
  8. freealg/_algebraic_form/_decompress2.py +204 -0
  9. freealg/_algebraic_form/_edge.py +330 -0
  10. freealg/_algebraic_form/_homotopy.py +323 -0
  11. freealg/_algebraic_form/_moments.py +448 -0
  12. freealg/_algebraic_form/_sheets_util.py +145 -0
  13. freealg/_algebraic_form/_support.py +309 -0
  14. freealg/_algebraic_form/algebraic_form.py +1232 -0
  15. freealg/_free_form/__init__.py +16 -0
  16. freealg/{_chebyshev.py → _free_form/_chebyshev.py} +75 -43
  17. freealg/_free_form/_decompress.py +993 -0
  18. freealg/_free_form/_density_util.py +243 -0
  19. freealg/_free_form/_jacobi.py +359 -0
  20. freealg/_free_form/_linalg.py +508 -0
  21. freealg/{_pade.py → _free_form/_pade.py} +42 -208
  22. freealg/{_plot_util.py → _free_form/_plot_util.py} +37 -22
  23. freealg/{_sample.py → _free_form/_sample.py} +58 -22
  24. freealg/_free_form/_series.py +454 -0
  25. freealg/_free_form/_support.py +214 -0
  26. freealg/_free_form/free_form.py +1362 -0
  27. freealg/_geometric_form/__init__.py +13 -0
  28. freealg/_geometric_form/_continuation_genus0.py +175 -0
  29. freealg/_geometric_form/_continuation_genus1.py +275 -0
  30. freealg/_geometric_form/_elliptic_functions.py +174 -0
  31. freealg/_geometric_form/_sphere_maps.py +63 -0
  32. freealg/_geometric_form/_torus_maps.py +118 -0
  33. freealg/_geometric_form/geometric_form.py +1094 -0
  34. freealg/_util.py +56 -110
  35. freealg/distributions/__init__.py +7 -1
  36. freealg/distributions/_chiral_block.py +494 -0
  37. freealg/distributions/_deformed_marchenko_pastur.py +726 -0
  38. freealg/distributions/_deformed_wigner.py +386 -0
  39. freealg/distributions/_kesten_mckay.py +29 -15
  40. freealg/distributions/_marchenko_pastur.py +224 -95
  41. freealg/distributions/_meixner.py +47 -37
  42. freealg/distributions/_wachter.py +29 -17
  43. freealg/distributions/_wigner.py +27 -14
  44. freealg/visualization/__init__.py +12 -0
  45. freealg/visualization/_glue_util.py +32 -0
  46. freealg/visualization/_rgb_hsv.py +125 -0
  47. freealg-0.7.12.dist-info/METADATA +172 -0
  48. freealg-0.7.12.dist-info/RECORD +53 -0
  49. {freealg-0.1.11.dist-info → freealg-0.7.12.dist-info}/WHEEL +1 -1
  50. freealg/_decompress.py +0 -180
  51. freealg/_jacobi.py +0 -218
  52. freealg/_support.py +0 -85
  53. freealg/freeform.py +0 -967
  54. freealg-0.1.11.dist-info/METADATA +0 -140
  55. freealg-0.1.11.dist-info/RECORD +0 -24
  56. /freealg/{_damp.py → _free_form/_damp.py} +0 -0
  57. {freealg-0.1.11.dist-info → freealg-0.7.12.dist-info}/licenses/AUTHORS.txt +0 -0
  58. {freealg-0.1.11.dist-info → freealg-0.7.12.dist-info}/licenses/LICENSE.txt +0 -0
  59. {freealg-0.1.11.dist-info → freealg-0.7.12.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,1094 @@
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 functools import partial
16
+ from .._util import resolve_complex_dtype, compute_eig
17
+ # from ._plot_util import plot_fit, plot_density, plot_hilbert, plot_stieltjes
18
+ # from ._decompress import decompress
19
+ # from ._support import supp
20
+
21
+ # Fallback to previous numpy API
22
+ if not hasattr(numpy, 'trapezoid'):
23
+ numpy.trapezoid = numpy.trapz
24
+
25
+ __all__ = ['GeometricForm']
26
+
27
+
28
+ # ==============
29
+ # Geometric Form
30
+ # ==============
31
+
32
+ class GeometricForm(object):
33
+ """
34
+ Geometric object representing Riemann surface for ensemble models.
35
+
36
+ Parameters
37
+ ----------
38
+
39
+ A : numpy.ndarray
40
+ The 2D symmetric :math:`\\mathbf{A}`. The eigenvalues of this will be
41
+ computed upon calling this class. If a 1D array provided, it is
42
+ assumed to be the eigenvalues of :math:`\\mathbf{A}`.
43
+
44
+ support : tuple, default=None
45
+ The support of the density of :math:`\\mathbf{A}`. If `None`, it is
46
+ estimated from the minimum and maximum of the eigenvalues.
47
+
48
+ delta: float, default=1e-6
49
+ Size of perturbations into the upper half plane for Plemelj's
50
+ formula.
51
+
52
+ dtype : {``'complex128'``, ``'complex256'``}, default = ``'complex128'``
53
+ Data type for inner computations of complex variables:
54
+
55
+ * ``'complex128'``: 128-bit complex numbers, equivalent of two double
56
+ precision floating point.
57
+ * ``'complex256'``: 256-bit complex numbers, equivalent of two long
58
+ double precision floating point. This optino is only available on
59
+ Linux machines.
60
+
61
+ When using series acceleration methods (such as setting
62
+ ``continuation`` in :func:`fit` function to ``wynn-eps``), setting a
63
+ higher precision floating point arithmetics might improve conference.
64
+
65
+ **kwargs : dict, optional
66
+ Parameters for the :func:`supp` function can also be prescribed
67
+ here when ``support=None``.
68
+
69
+ Attributes
70
+ ----------
71
+
72
+ eig : numpy.array
73
+ Eigenvalues of the matrix
74
+
75
+ support: tuple
76
+ The predicted (or given) support :math:`(\\lambda_{\\min},
77
+ \\lambda_{\\max})` of the eigenvalue density.
78
+
79
+ n : int
80
+ Initial array size (assuming a square matrix when :math:`\\mathbf{A}` is
81
+ 2D).
82
+
83
+ Methods
84
+ -------
85
+
86
+ fit
87
+ Fit the Jacobi polynomials to the empirical density.
88
+
89
+ density
90
+ Compute the spectral density of the matrix.
91
+
92
+ hilbert
93
+ Compute Hilbert transform of the spectral density
94
+
95
+ stieltjes
96
+ Compute Stieltjes transform of the spectral density
97
+
98
+ decompress
99
+ Free decompression of spectral density
100
+
101
+ eigvalsh
102
+ Estimate the eigenvalues
103
+
104
+ cond
105
+ Estimate the condition number
106
+
107
+ trace
108
+ Estimate the trace of a matrix power
109
+
110
+ slogdet
111
+ Estimate the sign and logarithm of the determinant
112
+
113
+ norm
114
+ Estimate the Schatten norm
115
+
116
+ Examples
117
+ --------
118
+
119
+ .. code-block:: python
120
+
121
+ >>> from freealg import FreeForm
122
+ """
123
+
124
+ # ====
125
+ # init
126
+ # ====
127
+
128
+ def __init__(self, A, support=None, delta=1e-6, dtype='complex128',
129
+ **kwargs):
130
+ """
131
+ Initialization.
132
+ """
133
+
134
+ self.A = None
135
+ self.eig = None
136
+ self.delta = delta # Offset above real axis to apply Plemelj formula
137
+
138
+ # Data type for complex arrays
139
+ self.dtype = resolve_complex_dtype(dtype)
140
+
141
+ # Eigenvalues
142
+ if A.ndim == 1:
143
+ # When A is a 1D array, it is assumed A is the eigenvalue array.
144
+ self.eig = A
145
+ self.n = len(A)
146
+ elif A.ndim == 2:
147
+ # When A is a 2D array, it is assumed A is the actual array,
148
+ # and its eigenvalues will be computed.
149
+ self.A = A
150
+ self.n = A.shape[0]
151
+ assert A.shape[0] == A.shape[1], \
152
+ 'Only square matrices are permitted.'
153
+ self.eig = compute_eig(A)
154
+
155
+ # Support
156
+ # if support is None:
157
+ # # Detect support
158
+ # self.lam_m, self.lam_p = supp(self.eig, **kwargs)
159
+ # else:
160
+ # self.lam_m = float(support[0])
161
+ # self.lam_p = float(support[1])
162
+ # self.support = (self.lam_m, self.lam_p)
163
+
164
+ # Initialize
165
+ self.method = None # fitting rho: jacobi, chebyshev
166
+ self.a_coeffs = None # Polynomial coefficients
167
+ self.cache = {} # Cache inner-computations
168
+
169
+ # ===
170
+ # fit
171
+ # ===
172
+
173
+ def fit(self, deg_m, deg_z, reg=0.0, optimizer='ls', plot=False,
174
+ latex=False, save=False):
175
+ """
176
+ Fit model to eigenvalues.
177
+
178
+ Parameters
179
+ ----------
180
+
181
+ method : {``'jacobi'``, ``'chebyshev'``}, default= ``'jacobi'``
182
+ Method of approximation, either by Jacobi polynomials or Chebyshev
183
+ polynomials of the second kind.
184
+
185
+ K : int, default=10
186
+ Highest polynomial degree
187
+
188
+ alpha : float, default=0.0
189
+ Jacobi parameter :math:`\\alpha`. Determines the slope of the
190
+ fitting model on the right side of interval. This should be greater
191
+ then -1. This option is only applicable when ``method='jacobi'``.
192
+
193
+ beta : float, default=0.0
194
+ Jacobi parameter :math:`\\beta`. Determines the slope of the
195
+ fitting model on the left side of interval. This should be greater
196
+ then -1. This option is only applicable when ``method='jacobi'``.
197
+
198
+ n_quad : int, default=60
199
+ Number of quadrature points to evaluate Stieltjes transform later
200
+ on (when :func:`decompress` is called) using Gauss-Jacob
201
+ quadrature. This option is relevant only if ``method='jacobi'``.
202
+
203
+ reg : float, default=0.0
204
+ Tikhonov regularization coefficient.
205
+
206
+ projection : {``'sample'``, ``'gaussian'``, ``'beta'``}, \
207
+ default= ``'beta'``
208
+ The method of Galerkin projection:
209
+
210
+ * ``'sample'``: directly project samples (eigenvalues) to the
211
+ orthogonal polynomials. This method is highly unstable as it
212
+ treats each sample as a delta Dirac function.
213
+ * ``'gaussian'``: computes Gaussian-Kernel KDE from the samples and
214
+ project a smooth KDE to the orthogonal polynomials. This method
215
+ is stable.
216
+ * ``'beta'``: computes Beta-Kernel KDE from the samples and
217
+ project a smooth KDE to the orthogonal polynomials. This method
218
+ is stable.
219
+
220
+ kernel_bw : float, default=0.001
221
+ Kernel band-wdth. See scipy.stats.gaussian_kde. This argument is
222
+ relevant if ``projection='kernel'`` is set.
223
+
224
+ damp : {``'jackson'``, ``'lanczos'``, ``'fejer``, ``'exponential'``,\
225
+ ``'parzen'``}, default=None
226
+ Damping method to eliminate Gibbs oscillation.
227
+
228
+ force : bool, default=False
229
+ If `True`, it forces the density to have unit mass and to be
230
+ strictly positive.
231
+
232
+ continuation : {``'pade'``, ``'wynn-eps'``, ``'wynn-rho'``, \
233
+ ``'levin'``, ``'weniger'``, ``'brezinski'``}, default= ``'pade'``
234
+ Method of analytic continuation to construct the second branch of
235
+ Steltjes transform in the lower-half complex plane:
236
+
237
+ * ``'pade'``: using Riemann-Hilbert problem with Pade
238
+ approximation.
239
+ * ``'wynn-eps'``: Wynn's :math:`\\epsilon` algorithm.
240
+ * ``'wynn-rho'``: Wynn's :math:`\\rho` algorithm (`experimental`).
241
+ * ``'levin'``: Levin's :math:`u` transform (`experimental`).
242
+ * ``'weniger'``: Weniger's :math:`\\delta^2` algorithm
243
+ (`experimental`).
244
+ * ``'brezinski'``: Brezinski's :math:`\\theta` algorithm
245
+ (`experimental`).
246
+
247
+ pade_p : int, default=1
248
+ Degree of polynomial :math:`P(z)` is :math:`p` where :math:`p` can
249
+ only be ``q-1``, ``q``, or ``q+1``. See notes below. This option
250
+ is applicable if ``continuation='pade'``.
251
+
252
+ pade_q : int, default=1
253
+ Degree of polynomial :math:`Q(z)` is :math:`q` where :math:`q` can
254
+ only be ``p-1``, ``p``, or ``p+1``. See notes below. This option
255
+ is applicable if ``continuation='pade'``.
256
+
257
+ odd_side : {``'left'``, ``'right'``}, default= ``'left'``
258
+ In case of odd number of poles (when :math:`q` is odd), the extra
259
+ pole is set to the left or right side of the support interval,
260
+ while all other poles are split in half to the left and right. Note
261
+ that this is only for the initialization of the poles. The
262
+ optimizer will decide best location by moving them to the left or
263
+ right of the support. This option is applicable if
264
+ ``continuation='pade'``.
265
+
266
+ pade_reg : float, default=0.0
267
+ Regularization for Pade approximation. This option is applicable if
268
+ ``continuation='pade'``.
269
+
270
+ optimizer : {``'ls'``, ``'de'``}, default= ``'ls'``
271
+ Optimizer for Pade approximation, including:
272
+
273
+ * ``'ls'``: least square (local, fast)
274
+ * ``'de'``: differential evolution (global, slow)
275
+
276
+ This option is applicable if ``continuation='pade'``.
277
+
278
+ plot : bool, default=False
279
+ If `True`, the approximation coefficients and Pade approximation to
280
+ the Hilbert transform (if applicable) are plotted.
281
+
282
+ latex : bool, default=False
283
+ If `True`, the plot is rendered using LaTeX. This option is
284
+ relevant only if ``plot=True``.
285
+
286
+ save : bool, default=False
287
+ If not `False`, the plot is saved. If a string is given, it is
288
+ assumed to the save filename (with the file extension). This option
289
+ is relevant only if ``plot=True``.
290
+
291
+ Returns
292
+ -------
293
+
294
+ psi : (K+1, ) numpy.ndarray
295
+ Coefficients of fitting Jacobi polynomials
296
+
297
+ Notes
298
+ -----
299
+
300
+ The Pade approximation for the glue function :math:`G(z)` is
301
+
302
+ .. math::
303
+
304
+ G(z) = \\frac{P(z)}{Q(z)},
305
+
306
+ where :math:`P(z)` and :math:`Q(z)` are polynomials of order
307
+ :math:`p+q` and :math:`q` respectively. Note that :math:`p` can only
308
+ be -1, 0, or 1, effectively making Pade approximation of order
309
+ :math:`q-1:q`, :math:`q:q`, or :math:`q-1:q`.
310
+
311
+ Examples
312
+ --------
313
+
314
+ .. code-block:: python
315
+
316
+ >>> from freealg import FreeForm
317
+ """
318
+
319
+ # Very important: reset cache whenever this function is called. This
320
+ # also empties all references holdign a cache copy.
321
+ # self.cache.clear()
322
+
323
+ # return self.a_coeffs
324
+
325
+ pass
326
+
327
+ # =============
328
+ # generate grid
329
+ # =============
330
+
331
+ # def _generate_grid(self, scale, extend=1.0, N=500):
332
+ # """
333
+ # Generate a grid of points to evaluate density / Hilbert / Stieltjes
334
+ # transforms.
335
+ # """
336
+ #
337
+ # radius = 0.5 * (self.lam_p - self.lam_m)
338
+ # center = 0.5 * (self.lam_p + self.lam_m)
339
+ #
340
+ # x_min = numpy.floor(extend * (center - extend * radius * scale))
341
+ # x_max = numpy.ceil(extend * (center + extend * radius * scale))
342
+ #
343
+ # x_min /= extend
344
+ # x_max /= extend
345
+ #
346
+ # return numpy.linspace(x_min, x_max, N)
347
+
348
+ # =======
349
+ # density
350
+ # =======
351
+
352
+ def density(self, x=None, plot=False, latex=False, save=False):
353
+ """
354
+ Evaluate spectral density.
355
+
356
+ Parameters
357
+ ----------
358
+
359
+ x : numpy.array, default=None
360
+ Positions where density to be evaluated at. If `None`, an interval
361
+ slightly larger than the support interval will be used.
362
+
363
+ plot : bool, default=False
364
+ If `True`, density is plotted.
365
+
366
+ latex : bool, default=False
367
+ If `True`, the plot is rendered using LaTeX. This option is
368
+ relevant only if ``plot=True``.
369
+
370
+ save : bool, default=False
371
+ If not `False`, the plot is saved. If a string is given, it is
372
+ assumed to the save filename (with the file extension). This option
373
+ is relevant only if ``plot=True``.
374
+
375
+ Returns
376
+ -------
377
+
378
+ rho : numpy.array
379
+ Density at locations x.
380
+
381
+ See Also
382
+ --------
383
+ hilbert
384
+ stieltjes
385
+
386
+ Examples
387
+ --------
388
+
389
+ .. code-block:: python
390
+
391
+ >>> from freealg import FreeForm
392
+ """
393
+
394
+ pass
395
+
396
+ if self.a_coeffs is None:
397
+ raise RuntimeError('The model needs to be fit using the .fit() ' +
398
+ 'function.')
399
+
400
+ # # Create x if not given
401
+ # if x is None:
402
+ # x = self._generate_grid(1.25)
403
+ #
404
+ # # Preallocate density to zero
405
+ # rho = numpy.zeros_like(x)
406
+ #
407
+ # # Compute density only inside support
408
+ # mask = numpy.logical_and(x >= self.lam_m, x <= self.lam_p)
409
+ #
410
+ # if self.method == 'jacobi':
411
+ # rho[mask] = jacobi_density(x[mask], self.psi, self.support,
412
+ # self.alpha, self.beta)
413
+ # elif self.method == 'chebyshev':
414
+ # rho[mask] = chebyshev_density(x[mask], self.psi, self.support)
415
+ # else:
416
+ # raise RuntimeError('"method" is invalid.')
417
+ #
418
+ # # Check density is unit mass
419
+ # mass = numpy.trapezoid(rho, x)
420
+ # if not numpy.isclose(mass, 1.0, atol=1e-2):
421
+ # print(f'"rho" is not unit mass. mass: {mass:>0.3f}. Set ' +
422
+ # r'"force=True".')
423
+ #
424
+ # # Check density is positive
425
+ # min_rho = numpy.min(rho)
426
+ # if min_rho < 0.0 - 1e-3:
427
+ # print(f'"rho" is not positive. min_rho: {min_rho:>0.3f}. Set ' +
428
+ # r'"force=True".')
429
+ #
430
+ # if plot:
431
+ # plot_density(x, rho, eig=self.eig, support=self.support,
432
+ # label='Estimate', latex=latex, save=save)
433
+ #
434
+ # return rho
435
+
436
+ # =======
437
+ # hilbert
438
+ # =======
439
+
440
+ def hilbert(self, x=None, rho=None, plot=False, latex=False, save=False):
441
+ """
442
+ Compute Hilbert transform of the spectral density.
443
+
444
+ Parameters
445
+ ----------
446
+
447
+ x : numpy.array, default=None
448
+ The locations where Hilbert transform is evaluated at. If `None`,
449
+ an interval slightly larger than the support interval of the
450
+ spectral density is used.
451
+
452
+ rho : numpy.array, default=None
453
+ Density. If `None`, it will be computed.
454
+
455
+ plot : bool, default=False
456
+ If `True`, density is plotted.
457
+
458
+ latex : bool, default=False
459
+ If `True`, the plot is rendered using LaTeX. This option is
460
+ relevant only if ``plot=True``.
461
+
462
+ save : bool, default=False
463
+ If not `False`, the plot is saved. If a string is given, it is
464
+ assumed to the save filename (with the file extension). This option
465
+ is relevant only if ``plot=True``.
466
+
467
+ Returns
468
+ -------
469
+
470
+ hilb : numpy.array
471
+ The Hilbert transform on the locations `x`.
472
+
473
+ See Also
474
+ --------
475
+ density
476
+ stieltjes
477
+
478
+ Examples
479
+ --------
480
+
481
+ .. code-block:: python
482
+
483
+ >>> from freealg import FreeForm
484
+ """
485
+
486
+ pass
487
+
488
+ if self.a_coeffs is None:
489
+ raise RuntimeError('The model needs to be fit using the .fit() ' +
490
+ 'function.')
491
+
492
+ # # Create x if not given
493
+ # if x is None:
494
+ # x = self._generate_grid(1.25)
495
+ #
496
+ # # if (numpy.min(x) > self.lam_m) or (numpy.max(x) < self.lam_p):
497
+ # # raise ValueError('"x" does not encompass support interval.')
498
+ #
499
+ # # Preallocate density to zero
500
+ # if rho is None:
501
+ # rho = self.density(x)
502
+ #
503
+ # # mask of support [lam_m, lam_p]
504
+ # mask = numpy.logical_and(x >= self.lam_m, x <= self.lam_p)
505
+ # x_s = x[mask]
506
+ # rho_s = rho[mask]
507
+ #
508
+ # # Form the matrix of integrands: rho_s / (t - x_i)
509
+ # # Here, we have diff[i,j] = x[i] - x_s[j]
510
+ # diff = x[:, None] - x_s[None, :]
511
+ # D = rho_s[None, :] / diff
512
+ #
513
+ # # Principal-value: wherever t == x_i, then diff == 0, zero that entry
514
+ # # (numpy.isclose handles floating-point exactly)
515
+ # D[numpy.isclose(diff, 0.0)] = 0.0
516
+ #
517
+ # # Integrate each row over t using trapezoid rule on x_s
518
+ # # Namely, hilb[i] = int rho_s(t)/(t - x[i]) dt
519
+ # hilb = numpy.trapezoid(D, x_s, axis=1) / numpy.pi
520
+ #
521
+ # # We use negative sign convention
522
+ # hilb = -hilb
523
+ #
524
+ # if plot:
525
+ # plot_hilbert(x, hilb, support=self.support, latex=latex,
526
+ # save=save)
527
+ #
528
+ # return hilb
529
+
530
+ # =========
531
+ # stieltjes
532
+ # =========
533
+
534
+ def stieltjes(self, x=None, y=None, plot=False, latex=False, save=False):
535
+ """
536
+ Compute Stieltjes transform of the spectral density on a grid.
537
+
538
+ This function evaluates Stieltjes transform on an array of points, or
539
+ over a 2D Cartesian grid on the complex plane.
540
+
541
+ Parameters
542
+ ----------
543
+
544
+ x : numpy.array, default=None
545
+ The x axis of the grid where the Stieltjes transform is evaluated.
546
+ If `None`, an interval slightly larger than the support interval of
547
+ the spectral density is used.
548
+
549
+ y : numpy.array, default=None
550
+ The y axis of the grid where the Stieltjes transform is evaluated.
551
+ If `None`, a grid on the interval ``[-1, 1]`` is used.
552
+
553
+ plot : bool, default=False
554
+ If `True`, density is plotted.
555
+
556
+ latex : bool, default=False
557
+ If `True`, the plot is rendered using LaTeX. This option is
558
+ relevant only if ``plot=True``.
559
+
560
+ save : bool, default=False
561
+ If not `False`, the plot is saved. If a string is given, it is
562
+ assumed to the save filename (with the file extension). This option
563
+ is relevant only if ``plot=True``.
564
+
565
+ Returns
566
+ -------
567
+
568
+ m_p : numpy.ndarray
569
+ The Stieltjes transform on the principal branch.
570
+
571
+ m_m : numpy.ndarray
572
+ The Stieltjes transform continued to the secondary branch.
573
+
574
+ See Also
575
+ --------
576
+
577
+ density
578
+ hilbert
579
+
580
+ Examples
581
+ --------
582
+
583
+ .. code-block:: python
584
+
585
+ >>> from freealg import FreeForm
586
+ """
587
+
588
+ pass
589
+
590
+ if self.a_coeffs is None:
591
+ raise RuntimeError('The model needs to be fit using the .fit() ' +
592
+ 'function.')
593
+
594
+ # # Create x if not given
595
+ # if x is None:
596
+ # x = self._generate_grid(2.0, extend=2.0)
597
+ #
598
+ # # Create y if not given
599
+ # if (plot is False) and (y is None):
600
+ # # Do not use a Cartesian grid. Create a 1D array z slightly above
601
+ # # the real line.
602
+ # y = self.delta * 1j
603
+ # z = x.astype(complex) + y # shape (Nx,)
604
+ # else:
605
+ # # Use a Cartesian grid
606
+ # if y is None:
607
+ # y = numpy.linspace(-1, 1, 400)
608
+ # x_grid, y_grid = numpy.meshgrid(x.real, y.real)
609
+ # z = x_grid + 1j * y_grid # shape (Ny, Nx)
610
+ #
611
+ # m1, m2 = self._eval_stieltjes(z, branches=True)
612
+ #
613
+ # if plot:
614
+ # plot_stieltjes(x, y, m1, m2, self.support, latex=latex,
615
+ # save=save)
616
+ #
617
+ # return m1, m2
618
+
619
+ # ==============
620
+ # eval stieltjes
621
+ # ==============
622
+
623
+ def _eval_stieltjes(self, z, branches=False):
624
+ """
625
+ Compute Stieltjes transform of the spectral density.
626
+
627
+ Parameters
628
+ ----------
629
+
630
+ z : numpy.array
631
+ The z values in the complex plan where the Stieltjes transform is
632
+ evaluated.
633
+
634
+ branches : bool, default = False
635
+ Return both the principal and secondary branches of the Stieltjes
636
+ transform. The default ``branches=False`` will return only
637
+ the secondary branch.
638
+
639
+ Returns
640
+ -------
641
+
642
+ m_p : numpy.ndarray
643
+ The Stieltjes transform on the principal branch if
644
+ ``branches=True``.
645
+
646
+ m_m : numpy.ndarray
647
+ The Stieltjes transform continued to the secondary branch.
648
+ """
649
+
650
+ pass
651
+
652
+ # ==========
653
+ # decompress
654
+ # ==========
655
+
656
+ def decompress(self, size, x=None, method='newton', max_iter=500,
657
+ step_size=0.1, tolerance=1e-4, plot=False, latex=False,
658
+ save=False, plot_diagnostics=False):
659
+ """
660
+ Free decompression of spectral density.
661
+
662
+ Parameters
663
+ ----------
664
+
665
+ size : int or array_like
666
+ Size(s) of the decompressed matrix. This can be a scalar or an
667
+ array of sizes. For each matrix size in ``size`` array, a density
668
+ is produced.
669
+
670
+ x : numpy.array, default=None
671
+ Positions where density to be evaluated at. If `None`, an interval
672
+ slightly larger than the support interval will be used.
673
+
674
+ method : {``'newton'``, ``'secant'``}, default= ``'newton'``
675
+ Root-finding method.
676
+
677
+ max_iter: int, default=500
678
+ Maximum number of root-finding method iterations.
679
+
680
+ step_size: float, default=0.1
681
+ Step size for Newton iterations.
682
+
683
+ tolerance: float, default=1e-4
684
+ Tolerance for the solution obtained by the Newton solver. Also
685
+ used for the finite difference approximation to the derivative.
686
+
687
+ plot : bool, default=False
688
+ If `True`, density is plotted.
689
+
690
+ latex : bool, default=False
691
+ If `True`, the plot is rendered using LaTeX. This option is
692
+ relevant only if ``plot=True``.
693
+
694
+ save : bool, default=False
695
+ If not `False`, the plot is saved. If a string is given, it is
696
+ assumed to the save filename (with the file extension). This option
697
+ is relevant only if ``plot=True``.
698
+
699
+ plot_diagnostics : bool, default=False
700
+ Plots diagnostics including convergence and number of iterations
701
+ of root finding method.
702
+
703
+ Returns
704
+ -------
705
+
706
+ rho : numpy.array or numpy.ndarray
707
+ Estimated spectral density at locations x. ``rho`` can be a 1D or
708
+ 2D array output:
709
+
710
+ * If ``size`` is a scalar, ``rho`` is a 1D array of the same size
711
+ as ``x``.
712
+ * If ``size`` is an array of size `n`, ``rho`` is a 2D array with
713
+ `n` rows, where each row corresponds to decompression to a size.
714
+ Number of columns of ``rho`` is the same as the size of ``x``.
715
+
716
+ x : numpy.array
717
+ Locations where the spectral density is estimated
718
+
719
+ See Also
720
+ --------
721
+
722
+ density
723
+ stieltjes
724
+
725
+ Examples
726
+ --------
727
+
728
+ .. code-block:: python
729
+
730
+ >>> from freealg import FreeForm
731
+ """
732
+
733
+ # Check size argument
734
+ if numpy.isscalar(size):
735
+ size = int(size)
736
+ else:
737
+ # Check monotonic increment (either all increasing or decreasing)
738
+ diff = numpy.diff(size)
739
+ if not (numpy.all(diff >= 0) or numpy.all(diff <= 0)):
740
+ raise ValueError('"size" increment should be monotonic.')
741
+
742
+ # Decompression ratio equal to e^{t}.
743
+ # alpha = numpy.atleast_1d(size) / self.n
744
+
745
+ # # If the input size was only a scalar, return a 1D rho, otherwise 2D.
746
+ # if numpy.isscalar(size):
747
+ # rho = numpy.squeeze(rho)
748
+ #
749
+ # # Plot only the last size
750
+ # if plot:
751
+ # if numpy.isscalar(size):
752
+ # rho_last = rho
753
+ # else:
754
+ # rho_last = rho[-1, :]
755
+ # plot_density(x, rho_last, support=(lb, ub),
756
+ # label='Decompression', latex=latex, save=save)
757
+ #
758
+ # return rho, x
759
+
760
+ # ========
761
+ # eigvalsh
762
+ # ========
763
+
764
+ def eigvalsh(self, size=None, seed=None, **kwargs):
765
+ """
766
+ Estimate the eigenvalues.
767
+
768
+ This function estimates the eigenvalues of the freeform matrix
769
+ or a larger matrix containing it using free decompression.
770
+
771
+ Parameters
772
+ ----------
773
+
774
+ size : int, default=None
775
+ The size of the matrix containing :math:`\\mathbf{A}` to estimate
776
+ eigenvalues of. If None, returns estimates of the eigenvalues of
777
+ :math:`\\mathbf{A}` itself.
778
+
779
+ seed : int, default=None
780
+ The seed for the Quasi-Monte Carlo sampler.
781
+
782
+ **kwargs : dict, optional
783
+ Pass additional options to the underlying
784
+ :func:`FreeForm.decompress` function.
785
+
786
+ Returns
787
+ -------
788
+
789
+ eigs : numpy.array
790
+ Eigenvalues of decompressed matrix
791
+
792
+ See Also
793
+ --------
794
+
795
+ FreeForm.decompress
796
+ FreeForm.cond
797
+
798
+ Notes
799
+ -----
800
+
801
+ All arguments to the `.decompress()` procedure can be provided.
802
+
803
+ Examples
804
+ --------
805
+
806
+ .. code-block:: python
807
+ :emphasize-lines: 1
808
+
809
+ >>> from freealg import FreeForm
810
+ """
811
+
812
+ # if size is None:
813
+ # size = self.n
814
+ #
815
+ # rho, x = self.decompress(size, **kwargs)
816
+ # eigs = numpy.sort(sample(x, rho, size, method='qmc', seed=seed))
817
+ #
818
+ # return eigs
819
+ pass
820
+
821
+ # ====
822
+ # cond
823
+ # ====
824
+
825
+ def cond(self, size=None, seed=None, **kwargs):
826
+ """
827
+ Estimate the condition number.
828
+
829
+ This function estimates the condition number of the matrix
830
+ :math:`\\mathbf{A}` or a larger matrix containing :math:`\\mathbf{A}`
831
+ using free decompression.
832
+
833
+ Parameters
834
+ ----------
835
+
836
+ size : int, default=None
837
+ The size of the matrix containing :math:`\\mathbf{A}` to estimate
838
+ eigenvalues of. If None, returns estimates of the eigenvalues of
839
+ :math:`\\mathbf{A}` itself.
840
+
841
+ **kwargs : dict, optional
842
+ Pass additional options to the underlying
843
+ :func:`FreeForm.decompress` function.
844
+
845
+ Returns
846
+ -------
847
+
848
+ c : float
849
+ Condition number
850
+
851
+ See Also
852
+ --------
853
+
854
+ FreeForm.eigvalsh
855
+ FreeForm.norm
856
+ FreeForm.slogdet
857
+ FreeForm.trace
858
+
859
+ Examples
860
+ --------
861
+
862
+ .. code-block:: python
863
+ :emphasize-lines: 1
864
+
865
+ >>> from freealg import FreeForm
866
+ """
867
+
868
+ eigs = self.eigvalsh(size=size, **kwargs)
869
+ return eigs.max() / eigs.min()
870
+
871
+ # =====
872
+ # trace
873
+ # =====
874
+
875
+ def trace(self, size=None, p=1.0, seed=None, **kwargs):
876
+ """
877
+ Estimate the trace of a power.
878
+
879
+ This function estimates the trace of the matrix power
880
+ :math:`\\mathbf{A}^p` of the freeform or that of a larger matrix
881
+ containing it.
882
+
883
+ Parameters
884
+ ----------
885
+
886
+ size : int, default=None
887
+ The size of the matrix containing :math:`\\mathbf{A}` to estimate
888
+ eigenvalues of. If None, returns estimates of the eigenvalues of
889
+ :math:`\\mathbf{A}` itself.
890
+
891
+ p : float, default=1.0
892
+ The exponent :math:`p` in :math:`\\mathbf{A}^p`.
893
+
894
+ seed : int, default=None
895
+ The seed for the Quasi-Monte Carlo sampler.
896
+
897
+ **kwargs : dict, optional
898
+ Pass additional options to the underlying
899
+ :func:`FreeForm.decompress` function.
900
+
901
+ Returns
902
+ -------
903
+
904
+ trace : float
905
+ matrix trace
906
+
907
+ See Also
908
+ --------
909
+
910
+ FreeForm.eigvalsh
911
+ FreeForm.cond
912
+ FreeForm.slogdet
913
+ FreeForm.norm
914
+
915
+ Notes
916
+ -----
917
+
918
+ The trace is highly amenable to subsampling: under free decompression
919
+ the average eigenvalue is assumed constant, so the trace increases
920
+ linearly. Traces of powers fall back to :func:`eigvalsh`.
921
+ All arguments to the `.decompress()` procedure can be provided.
922
+
923
+ Examples
924
+ --------
925
+
926
+ .. code-block:: python
927
+ :emphasize-lines: 1
928
+
929
+ >>> from freealg import FreeForm
930
+ """
931
+
932
+ if numpy.isclose(p, 1.0):
933
+ return numpy.mean(self.eig) * (size / self.n)
934
+
935
+ eig = self.eigvalsh(size=size, seed=seed, **kwargs)
936
+ return numpy.sum(eig ** p)
937
+
938
+ # =======
939
+ # slogdet
940
+ # =======
941
+
942
+ def slogdet(self, size=None, seed=None, **kwargs):
943
+ """
944
+ Estimate the sign and logarithm of the determinant.
945
+
946
+ This function estimates the *slogdet* of the freeform or that of
947
+ a larger matrix containing it using free decompression.
948
+
949
+ Parameters
950
+ ----------
951
+
952
+ size : int, default=None
953
+ The size of the matrix containing :math:`\\mathbf{A}` to estimate
954
+ eigenvalues of. If None, returns estimates of the eigenvalues of
955
+ :math:`\\mathbf{A}` itself.
956
+
957
+ seed : int, default=None
958
+ The seed for the Quasi-Monte Carlo sampler.
959
+
960
+ Returns
961
+ -------
962
+
963
+ sign : float
964
+ Sign of determinant
965
+
966
+ ld : float
967
+ natural logarithm of the absolute value of the determinant
968
+
969
+ See Also
970
+ --------
971
+
972
+ FreeForm.eigvalsh
973
+ FreeForm.cond
974
+ FreeForm.trace
975
+ FreeForm.norm
976
+
977
+ Notes
978
+ -----
979
+
980
+ All arguments to the `.decompress()` procedure can be provided.
981
+
982
+ Examples
983
+ --------
984
+
985
+ .. code-block:: python
986
+ :emphasize-lines: 1
987
+
988
+ >>> from freealg import FreeForm
989
+ """
990
+
991
+ eigs = self.eigvalsh(size=size, seed=seed, **kwargs)
992
+ sign = numpy.prod(numpy.sign(eigs))
993
+ ld = numpy.sum(numpy.log(numpy.abs(eigs)))
994
+ return sign, ld
995
+
996
+ # ====
997
+ # norm
998
+ # ====
999
+
1000
+ def norm(self, size=None, order=2, seed=None, **kwargs):
1001
+ """
1002
+ Estimate the Schatten norm.
1003
+
1004
+ This function estimates the norm of the freeform or a larger
1005
+ matrix containing it using free decompression.
1006
+
1007
+ Parameters
1008
+ ----------
1009
+
1010
+ size : int, default=None
1011
+ The size of the matrix containing :math:`\\mathbf{A}` to estimate
1012
+ eigenvalues of. If None, returns estimates of the eigenvalues of
1013
+ :math:`\\mathbf{A}` itself.
1014
+
1015
+ order : {float, ``''inf``, ``'-inf'``, ``'fro'``, ``'nuc'``}, default=2
1016
+ Order of the norm.
1017
+
1018
+ * float :math:`p`: Schatten p-norm.
1019
+ * ``'inf'``: Largest absolute eigenvalue
1020
+ :math:`\\max \\vert \\lambda_i \\vert)`
1021
+ * ``'-inf'``: Smallest absolute eigenvalue
1022
+ :math:`\\min \\vert \\lambda_i \\vert)`
1023
+ * ``'fro'``: Frobenius norm corresponding to :math:`p=2`
1024
+ * ``'nuc'``: Nuclear (or trace) norm corresponding to :math:`p=1`
1025
+
1026
+ seed : int, default=None
1027
+ The seed for the Quasi-Monte Carlo sampler.
1028
+
1029
+ **kwargs : dict, optional
1030
+ Pass additional options to the underlying
1031
+ :func:`FreeForm.decompress` function.
1032
+
1033
+ Returns
1034
+ -------
1035
+
1036
+ norm : float
1037
+ matrix norm
1038
+
1039
+ See Also
1040
+ --------
1041
+
1042
+ FreeForm.eigvalsh
1043
+ FreeForm.cond
1044
+ FreeForm.slogdet
1045
+ FreeForm.trace
1046
+
1047
+ Notes
1048
+ -----
1049
+
1050
+ Thes Schatten :math:`p`-norm is defined by
1051
+
1052
+ .. math::
1053
+
1054
+ \\Vert \\mathbf{A} \\Vert_p = \\left(
1055
+ \\sum_{i=1}^N \\vert \\lambda_i \\vert^p \\right)^{1/p}.
1056
+
1057
+ Examples
1058
+ --------
1059
+
1060
+ .. code-block:: python
1061
+ :emphasize-lines: 1
1062
+
1063
+ >>> from freealg import FreeForm
1064
+ """
1065
+
1066
+ eigs = self.eigvalsh(size, seed=seed, **kwargs)
1067
+
1068
+ # Check order type and convert to float
1069
+ if order == 'nuc':
1070
+ order = 1
1071
+ elif order == 'fro':
1072
+ order = 2
1073
+ elif order == 'inf':
1074
+ order = float('inf')
1075
+ elif order == '-inf':
1076
+ order = -float('inf')
1077
+ elif not isinstance(order,
1078
+ (int, float, numpy.integer, numpy.floating)) \
1079
+ and not isinstance(order, (bool, numpy.bool_)):
1080
+ raise ValueError('"order" is invalid.')
1081
+
1082
+ # Compute norm
1083
+ if numpy.isinf(order) and not numpy.isneginf(order):
1084
+ norm_ = max(numpy.abs(eigs))
1085
+
1086
+ elif numpy.isneginf(order):
1087
+ norm_ = min(numpy.abs(eigs))
1088
+
1089
+ elif isinstance(order, (int, float, numpy.integer, numpy.floating)) \
1090
+ and not isinstance(order, (bool, numpy.bool_)):
1091
+ norm_q = numpy.sum(numpy.abs(eigs)**order)
1092
+ norm_ = norm_q**(1.0 / order)
1093
+
1094
+ return norm_