freealg 0.0.1__tar.gz → 0.0.3__tar.gz

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 (28) hide show
  1. {freealg-0.0.1/freealg.egg-info → freealg-0.0.3}/PKG-INFO +1 -1
  2. freealg-0.0.3/freealg/__version__.py +1 -0
  3. freealg-0.0.3/freealg/_decompress.py +136 -0
  4. {freealg-0.0.1 → freealg-0.0.3}/freealg/_jacobi.py +0 -2
  5. {freealg-0.0.1 → freealg-0.0.3}/freealg/_plot_util.py +86 -33
  6. {freealg-0.0.1 → freealg-0.0.3}/freealg/distributions/marchenko_pastur.py +71 -15
  7. {freealg-0.0.1 → freealg-0.0.3}/freealg/freeform.py +219 -50
  8. {freealg-0.0.1 → freealg-0.0.3/freealg.egg-info}/PKG-INFO +1 -1
  9. {freealg-0.0.1 → freealg-0.0.3}/freealg.egg-info/SOURCES.txt +1 -0
  10. freealg-0.0.1/freealg/__version__.py +0 -1
  11. {freealg-0.0.1 → freealg-0.0.3}/CHANGELOG.rst +0 -0
  12. {freealg-0.0.1 → freealg-0.0.3}/LICENSE.txt +0 -0
  13. {freealg-0.0.1 → freealg-0.0.3}/MANIFEST.in +0 -0
  14. {freealg-0.0.1 → freealg-0.0.3}/README.rst +0 -0
  15. {freealg-0.0.1 → freealg-0.0.3}/freealg/__init__.py +0 -0
  16. {freealg-0.0.1 → freealg-0.0.3}/freealg/_chebyshev.py +0 -0
  17. {freealg-0.0.1 → freealg-0.0.3}/freealg/_damp.py +0 -0
  18. {freealg-0.0.1 → freealg-0.0.3}/freealg/_pade.py +0 -0
  19. {freealg-0.0.1 → freealg-0.0.3}/freealg/_util.py +0 -0
  20. {freealg-0.0.1 → freealg-0.0.3}/freealg/distributions/__init__.py +0 -0
  21. {freealg-0.0.1 → freealg-0.0.3}/freealg.egg-info/dependency_links.txt +0 -0
  22. {freealg-0.0.1 → freealg-0.0.3}/freealg.egg-info/not-zip-safe +0 -0
  23. {freealg-0.0.1 → freealg-0.0.3}/freealg.egg-info/requires.txt +0 -0
  24. {freealg-0.0.1 → freealg-0.0.3}/freealg.egg-info/top_level.txt +0 -0
  25. {freealg-0.0.1 → freealg-0.0.3}/pyproject.toml +0 -0
  26. {freealg-0.0.1 → freealg-0.0.3}/requirements.txt +0 -0
  27. {freealg-0.0.1 → freealg-0.0.3}/setup.cfg +0 -0
  28. {freealg-0.0.1 → freealg-0.0.3}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: freealg
3
- Version: 0.0.1
3
+ Version: 0.0.3
4
4
  Summary: Free probability for large matrices
5
5
  Keywords: leaderboard bot chat
6
6
  Platform: Linux
@@ -0,0 +1 @@
1
+ __version__ = "0.0.3"
@@ -0,0 +1,136 @@
1
+ # SPDX-License-Identifier: BSD-3-Clause
2
+ # SPDX-FileType: SOURCE
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify it under
5
+ # the terms of the license found in the LICENSE.txt file in the root directory
6
+ # of this source tree.
7
+
8
+
9
+ # =======
10
+ # Imports
11
+ # =======
12
+
13
+ import numpy
14
+
15
+ __all__ = ['decompress']
16
+
17
+
18
+ # ==========
19
+ # decompress
20
+ # ==========
21
+
22
+ def decompress(matrix, size, x=None, delta=1e-4, iterations=500, step_size=0.1,
23
+ tolerance=1e-4):
24
+ """
25
+ Free decompression of spectral density.
26
+
27
+ Parameters
28
+ ----------
29
+
30
+ matrix : FreeForm
31
+ The initial matrix to be decompressed
32
+
33
+ size : int
34
+ Size of the decompressed matrix.
35
+
36
+ x : numpy.array, default=None
37
+ Positions where density to be evaluated at. If `None`, an interval
38
+ slightly larger than the support interval will be used.
39
+
40
+ delta: float, default=1e-4
41
+ Size of the perturbation into the upper half plane for Plemelj's
42
+ formula.
43
+
44
+ iterations: int, default=500
45
+ Maximum number of Newton iterations.
46
+
47
+ step_size: float, default=0.1
48
+ Step size for Newton iterations.
49
+
50
+ tolerance: float, default=1e-4
51
+ Tolerance for the solution obtained by the Newton solver. Also
52
+ used for the finite difference approximation to the derivative.
53
+
54
+ Returns
55
+ -------
56
+
57
+ rho : numpy.array
58
+ Spectral density
59
+
60
+ See Also
61
+ --------
62
+
63
+ density
64
+ stieltjes
65
+
66
+ Notes
67
+ -----
68
+
69
+ Work in progress.
70
+
71
+ References
72
+ ----------
73
+
74
+ .. [1] tbd
75
+
76
+ Examples
77
+ --------
78
+
79
+ .. code-block:: python
80
+
81
+ >>> from freealg import FreeForm
82
+ """
83
+
84
+ alpha = size / matrix.n
85
+ m = matrix._eval_stieltjes
86
+ # Lower and upper bound on new support
87
+ hilb_lb = (1 / m(matrix.lam_m + delta * 1j)[1]).real
88
+ hilb_ub = (1 / m(matrix.lam_p + delta * 1j)[1]).real
89
+ lb = matrix.lam_m - (alpha - 1) * hilb_lb
90
+ ub = matrix.lam_p - (alpha - 1) * hilb_ub
91
+
92
+ # Create x if not given
93
+ if x is None:
94
+ radius = 0.5 * (ub - lb)
95
+ center = 0.5 * (ub + lb)
96
+ scale = 1.25
97
+ x_min = numpy.floor(center - radius * scale)
98
+ x_max = numpy.ceil(center + radius * scale)
99
+ x = numpy.linspace(x_min, x_max, 500)
100
+
101
+ def _char_z(z):
102
+ return z + (1 / m(z)[1]) * (1 - alpha)
103
+
104
+ # Ensure that input is an array
105
+ x = numpy.asarray(x)
106
+
107
+ target = x + delta * 1j
108
+
109
+ z = numpy.full(target.shape, numpy.mean(matrix.support) - .1j,
110
+ dtype=numpy.complex128)
111
+
112
+ # Broken Newton steps can produce a lot of warnings. Removing them
113
+ # for now.
114
+ with numpy.errstate(all='ignore'):
115
+ for _ in range(iterations):
116
+ objective = _char_z(z) - target
117
+ mask = numpy.abs(objective) >= tolerance
118
+ if not numpy.any(mask):
119
+ break
120
+ z_m = z[mask]
121
+
122
+ # Perform finite difference approximation
123
+ dfdz = _char_z(z_m+tolerance) - _char_z(z_m-tolerance)
124
+ dfdz /= 2*tolerance
125
+ dfdz[dfdz == 0] = 1.0
126
+
127
+ # Perform Newton step
128
+ z[mask] = z_m - step_size * objective[mask] / dfdz
129
+
130
+ # Plemelj's formula
131
+ char_s = m(z)[1] / alpha
132
+ rho = numpy.maximum(0, char_s.imag / numpy.pi)
133
+ rho[numpy.isnan(rho) | numpy.isinf(rho)] = 0
134
+ rho = rho.reshape(*x.shape)
135
+
136
+ return rho, x, (lb, ub)
@@ -174,9 +174,7 @@ def jacobi_stieltjes(z, psi, support, alpha=0.0, beta=0.0, n_base=40):
174
174
  integrand = w_nodes * P_k_nodes # (n_quad,)
175
175
 
176
176
  # Broadcast over z: shape (n_quad, ...) / ...
177
- # diff = u_z[None, ...] - t_nodes[:, None] # (n_quad, ...)
178
177
  diff = u_z[None, ...] - t_nodes[:, None, None] # (n_quad, Ny, Nx)
179
- # m_k = (integrand[:, None] / diff).sum(axis=0) # shape like z
180
178
  m_k = (integrand[:, None, None] / diff).sum(axis=0)
181
179
 
182
180
  # Accumulate with factor 2/span
@@ -19,28 +19,21 @@ import colorsys
19
19
  import matplotlib.ticker as ticker
20
20
  import matplotlib.gridspec as gridspec
21
21
 
22
- __all__ = ['plot_coeff', 'plot_density', 'plot_hilbert', 'plot_stieltjes',
23
- 'plot_stieltjes_on_disk', 'plot_glue_fit']
22
+ __all__ = ['plot_fit', 'plot_density', 'plot_hilbert', 'plot_stieltjes',
23
+ 'plot_stieltjes_on_disk']
24
24
 
25
25
 
26
- # ==========
27
- # plot coeff
28
- # ==========
26
+ # ==============
27
+ # plot coeff fit
28
+ # ==============
29
29
 
30
- def plot_coeff(psi, latex=False, save=False):
30
+ def plot_coeff_fit(psi, latex=False, save=False):
31
31
  """
32
32
  """
33
33
 
34
34
  with texplot.theme(use_latex=latex):
35
35
 
36
36
  fig, ax = plt.subplots(figsize=(6, 2.7))
37
- n = numpy.arange(1, 1+psi.size)
38
- ax.plot(n, psi**2, '-o', markersize=3, color='black')
39
- ax.set_xlim([n[0]-1e-3, n[-1]+1e-3])
40
- ax.set_xlabel(r'$k$')
41
- ax.set_ylabel(r'$\vert \psi_k \vert^2$')
42
- ax.set_title('Spectral Energy per Mode')
43
- ax.set_yscale('log')
44
37
 
45
38
  # Save
46
39
  if save is False:
@@ -54,7 +47,65 @@ def plot_coeff(psi, latex=False, save=False):
54
47
  save_filename = 'energy.pdf'
55
48
 
56
49
  texplot.show_or_save_plot(plt, default_filename=save_filename,
57
- transparent_background=True, dpi=200,
50
+ transparent_background=True, dpi=400,
51
+ show_and_save=save_status, verbose=True)
52
+
53
+
54
+ # ========
55
+ # plot fit
56
+ # ========
57
+
58
+ def plot_fit(psi, x_supp, g_supp, g_supp_approx, support, latex=False,
59
+ save=False):
60
+ """
61
+ """
62
+
63
+ with texplot.theme(use_latex=latex):
64
+
65
+ fig, ax = plt.subplots(figsize=(9, 3), ncols=2)
66
+
67
+ # Plot psi
68
+ n = numpy.arange(1, 1+psi.size)
69
+ ax[0].plot(n, psi**2, '-o', markersize=3, color='black')
70
+ ax[0].set_xlim([n[0]-1e-3, n[-1]+1e-3])
71
+ ax[0].set_xlabel(r'$k$')
72
+ ax[0].set_ylabel(r'$\vert \psi_k \vert^2$')
73
+ ax[0].set_title('Spectral Energy per Mode')
74
+ ax[0].set_yscale('log')
75
+
76
+ # Plot pade
77
+ lam_m, lam_p = support
78
+ g_supp_min = numpy.min(g_supp)
79
+ g_supp_max = numpy.max(g_supp)
80
+ g_supp_dif = g_supp_max - g_supp_min
81
+ g_min = g_supp_min - g_supp_dif * 1.1
82
+ g_max = g_supp_max + g_supp_dif * 1.1
83
+
84
+ ax[1].plot(x_supp, g_supp, color='firebrick',
85
+ label=r'$2 \pi \times $ Hilbert Transform')
86
+ ax[1].plot(x_supp, g_supp_approx, color='black', label='Pade estimate')
87
+ ax[1].legend(fontsize='small')
88
+ ax[1].set_xlim([lam_m, lam_p])
89
+ ax[1].set_ylim([g_min, g_max])
90
+ ax[1].set_title('Approximation of Glue Function')
91
+ ax[1].set_xlabel(r'$x$')
92
+ ax[1].set_ylabel(r'$G(x)$')
93
+
94
+ plt.tight_layout()
95
+
96
+ # Save
97
+ if save is False:
98
+ save_status = False
99
+ save_filename = ''
100
+ else:
101
+ save_status = True
102
+ if isinstance(save, str):
103
+ save_filename = save
104
+ else:
105
+ save_filename = 'fit.pdf'
106
+
107
+ texplot.show_or_save_plot(plt, default_filename=save_filename,
108
+ transparent_background=True, dpi=400,
58
109
  show_and_save=save_status, verbose=True)
59
110
 
60
111
 
@@ -76,6 +127,8 @@ def plot_density(x, rho, eig=None, support=None, label='',
76
127
  bins = numpy.linspace(lam_m, lam_p, 250)
77
128
  _ = ax.hist(eig, bins, density=True, color='silver',
78
129
  edgecolor='none', label='Histogram')
130
+ else:
131
+ plt.fill_between(x, y1=rho, y2=0, color='silver', zorder=-1)
79
132
 
80
133
  ax.plot(x, rho, color='black', label=label, zorder=3)
81
134
  ax.set_xlabel(r'$\lambda$')
@@ -99,7 +152,7 @@ def plot_density(x, rho, eig=None, support=None, label='',
99
152
  save_filename = 'density.pdf'
100
153
 
101
154
  texplot.show_or_save_plot(plt, default_filename=save_filename,
102
- transparent_background=True, dpi=200,
155
+ transparent_background=True, dpi=400,
103
156
  show_and_save=save_status, verbose=True)
104
157
 
105
158
 
@@ -149,7 +202,7 @@ def plot_hilbert(x, hilb, support=None, latex=False, save=False):
149
202
  save_filename = 'hilbert.pdf'
150
203
 
151
204
  texplot.show_or_save_plot(plt, default_filename=save_filename,
152
- transparent_background=True, dpi=200,
205
+ transparent_background=True, dpi=400,
153
206
  show_and_save=save_status, verbose=True)
154
207
 
155
208
 
@@ -327,7 +380,7 @@ def plot_stieltjes(x, y, m1, m2, support, latex=False, save=False):
327
380
  save_filename = 'stieltjes.pdf'
328
381
 
329
382
  texplot.show_or_save_plot(plt, default_filename=save_filename,
330
- transparent_background=True, dpi=200,
383
+ transparent_background=True, dpi=400,
331
384
  show_and_save=save_status, verbose=True)
332
385
 
333
386
 
@@ -456,16 +509,15 @@ def plot_stieltjes_on_disk(r, t, m1_D, m2_D, support, latex=False, save=False):
456
509
  save_filename = 'stieltjes_disk.pdf'
457
510
 
458
511
  texplot.show_or_save_plot(plt, default_filename=save_filename,
459
- transparent_background=True, dpi=200,
512
+ transparent_background=True, dpi=400,
460
513
  show_and_save=save_status, verbose=True)
461
514
 
462
515
 
463
- # =============
464
- # plot glue fit
465
- # =============
516
+ # ============
517
+ # plot samples
518
+ # ============
466
519
 
467
- def plot_glue_fit(x_supp, g_supp, g_supp_approx, support, latex=False,
468
- save=False):
520
+ def plot_samples(x, rho, x_min, x_max, samples, latex=False, save=False):
469
521
  """
470
522
  """
471
523
 
@@ -473,15 +525,16 @@ def plot_glue_fit(x_supp, g_supp, g_supp_approx, support, latex=False,
473
525
 
474
526
  fig, ax = plt.subplots(figsize=(6, 3))
475
527
 
476
- lam_m, lam_p = support
477
- ax.plot(x_supp, g_supp, color='black', label='Glue target')
478
- ax.plot(x_supp, g_supp_approx, color='firebrick',
479
- label='Glue estimate')
528
+ bins = numpy.linspace(x_min, x_max, samples.size // 10)
529
+ _ = ax.hist(samples, bins, density=True, color='silver',
530
+ label='Samples histogram')
531
+ ax.plot(x, rho, color='black', label='Exact density')
480
532
  ax.legend(fontsize='small')
481
- ax.set_xlim([lam_m, lam_p])
482
- ax.set_title('Approximation of Glue function on real axis')
483
- ax.set_xlabel(r'$x$')
484
- ax.set_ylabel(r'$G(x)$')
533
+ ax.set_ylim(bottom=0)
534
+ ax.set_xlim([x[0], x[-1]])
535
+ ax.set_xlabel(r'$\lambda$')
536
+ ax.set_ylabel(r'$\rho(\lambda)$''')
537
+ ax.set_title('Histogram of Samples from Distribution')
485
538
 
486
539
  # Save
487
540
  if save is False:
@@ -492,8 +545,8 @@ def plot_glue_fit(x_supp, g_supp, g_supp_approx, support, latex=False,
492
545
  if isinstance(save, str):
493
546
  save_filename = save
494
547
  else:
495
- save_filename = 'glue_fit.pdf'
548
+ save_filename = 'samples.pdf'
496
549
 
497
550
  texplot.show_or_save_plot(plt, default_filename=save_filename,
498
- transparent_background=True, dpi=200,
551
+ transparent_background=True, dpi=400,
499
552
  show_and_save=save_status, verbose=True)
@@ -14,7 +14,7 @@
14
14
  import numpy
15
15
  from scipy.interpolate import interp1d
16
16
  from .._plot_util import plot_density, plot_hilbert, plot_stieltjes, \
17
- plot_stieltjes_on_disk
17
+ plot_stieltjes_on_disk, plot_samples
18
18
 
19
19
  try:
20
20
  from scipy.integrate import cumtrapz
@@ -77,9 +77,7 @@ class MarchenkoPastur(object):
77
77
 
78
78
  .. [1] Marcenko, V. A., Pastur, L. A. (1967). Distribution of eigenvalues
79
79
  for some sets of random matrices. Mathematics of the USSR-Sbornik,
80
- 1(4), 457 (`DOI<
81
- https://iopscience.iop.org/article/10.1070/SM1967v001n04ABEH001994
82
- >`__)
80
+ 1(4), 457
83
81
 
84
82
  Examples
85
83
  --------
@@ -149,6 +147,10 @@ class MarchenkoPastur(object):
149
147
  >>> from freealg.distributions import MarchenkoPastur
150
148
  >>> mp = MarchenkoPastur(1/50)
151
149
  >>> rho = mp.density(plot=True)
150
+
151
+ .. image:: ../_static/images/plots/mp_density.png
152
+ :align: center
153
+ :class: custom-dark
152
154
  """
153
155
 
154
156
  # Create x if not given
@@ -167,8 +169,7 @@ class MarchenkoPastur(object):
167
169
  numpy.sqrt((self.lam_p - x[mask]) * (x[mask] - self.lam_m))
168
170
 
169
171
  if plot:
170
- plot_density(x, rho, support=self.support, label='', latex=latex,
171
- save=save)
172
+ plot_density(x, rho, label='', latex=latex, save=save)
172
173
 
173
174
  return rho
174
175
 
@@ -189,7 +190,7 @@ class MarchenkoPastur(object):
189
190
  spectral density is used.
190
191
 
191
192
  plot : bool, default=False
192
- If `True`, density is plotted.
193
+ If `True`, Hilbert transform is plotted.
193
194
 
194
195
  latex : bool, default=False
195
196
  If `True`, the plot is rendered using LaTeX. This option is
@@ -214,6 +215,10 @@ class MarchenkoPastur(object):
214
215
  >>> from freealg.distributions import MarchenkoPastur
215
216
  >>> mp = MarchenkoPastur(1/50)
216
217
  >>> hilb = mp.hilbert(plot=True)
218
+
219
+ .. image:: ../_static/images/plots/mp_hilbert.png
220
+ :align: center
221
+ :class: custom-dark
217
222
  """
218
223
 
219
224
  # Create x if not given
@@ -225,9 +230,18 @@ class MarchenkoPastur(object):
225
230
  x_max = numpy.ceil(center + radius * scale)
226
231
  x = numpy.linspace(x_min, x_max, 500)
227
232
 
228
- hilb = numpy.zeros_like(x)
229
- mask = numpy.logical_and(x >= self.lam_m, x <= self.lam_p)
230
- hilb[mask] = (x[mask] - 1 - self.lam) / (self.lam * x[mask])
233
+ def _P(x):
234
+ return x - 1 + self.lam
235
+
236
+ def _Q(x):
237
+ return self.lam * x
238
+
239
+ P = _P(x)
240
+ Q = _Q(x)
241
+ Delta2 = P**2 - 4.0 * Q
242
+ Delta = numpy.sqrt(numpy.maximum(Delta2, 0))
243
+ sign = numpy.sign(P)
244
+ hilb = (P - sign * Delta) / (2.0 * Q)
231
245
 
232
246
  # using negative sign convention
233
247
  hilb = -hilb
@@ -319,7 +333,7 @@ class MarchenkoPastur(object):
319
333
  If `None`, a grid on the interval ``[-1, 1]`` is used.
320
334
 
321
335
  plot : bool, default=False
322
- If `True`, density is plotted.
336
+ If `True`, Stieltjes transform is plotted.
323
337
 
324
338
  on_disk : bool, default=False
325
339
  If `True`, the Stieltjes transform is mapped on unit disk. This
@@ -346,12 +360,25 @@ class MarchenkoPastur(object):
346
360
  Examples
347
361
  --------
348
362
 
349
- .. code-block::python
363
+ .. code-block:: python
350
364
 
351
365
  >>> from freealg.distributions import MarchenkoPastur
352
366
  >>> mp = MarchenkoPastur(1/50)
353
367
  >>> m1, m2 = mp.stieltjes(plot=True)
354
368
 
369
+ .. image:: ../_static/images/plots/mp_stieltjes.png
370
+ :align: center
371
+ :class: custom-dark
372
+
373
+ Plot on unit disk using Cayley transform:
374
+
375
+ .. code-block:: python
376
+
377
+ >>> m1, m2 = mp.stieltjes(plot=True, on_disk=True)
378
+
379
+ .. image:: ../_static/images/plots/mp_stieltjes_disk.png
380
+ :align: center
381
+ :class: custom-dark
355
382
  """
356
383
 
357
384
  if (plot is True) and (on_disk is True):
@@ -407,7 +434,8 @@ class MarchenkoPastur(object):
407
434
  # sample
408
435
  # ======
409
436
 
410
- def sample(self, size, x_min=None, x_max=None):
437
+ def sample(self, size, x_min=None, x_max=None, plot=False, latex=False,
438
+ save=False):
411
439
  """
412
440
  Sample from distribution.
413
441
 
@@ -425,6 +453,18 @@ class MarchenkoPastur(object):
425
453
  Maximum of sample values. If `None`, the right edge of the support
426
454
  is used.
427
455
 
456
+ plot : bool, default=False
457
+ If `True`, samples histogram is plotted.
458
+
459
+ latex : bool, default=False
460
+ If `True`, the plot is rendered using LaTeX. This option is
461
+ relevant only if ``plot=True``.
462
+
463
+ save : bool, default=False
464
+ If not `False`, the plot is saved. If a string is given, it is
465
+ assumed to the save filename (with the file extension). This option
466
+ is relevant only if ``plot=True``.
467
+
428
468
  Returns
429
469
  -------
430
470
 
@@ -443,7 +483,11 @@ class MarchenkoPastur(object):
443
483
 
444
484
  >>> from freealg.distributions import MarchenkoPastur
445
485
  >>> mp = MarchenkoPastur(1/50)
446
- >>> s = mp.sample(2000)
486
+
487
+ >>> s = mp.sample(2000)
488
+ .. image:: ../_static/images/plots/mp_samples.png
489
+ :align: center
490
+ :class: custom-dark
447
491
  """
448
492
 
449
493
  if x_min is None:
@@ -466,7 +510,19 @@ class MarchenkoPastur(object):
466
510
 
467
511
  # Sample and map
468
512
  u = numpy.random.rand(size)
469
- return inv_cdf(u)
513
+ samples = inv_cdf(u)
514
+
515
+ if plot:
516
+ radius = 0.5 * (self.lam_p - self.lam_m)
517
+ center = 0.5 * (self.lam_p + self.lam_m)
518
+ scale = 1.25
519
+ x_min = numpy.floor(center - radius * scale)
520
+ x_max = numpy.ceil(center + radius * scale)
521
+ x = numpy.linspace(x_min, x_max, 500)
522
+ rho = self.density(x)
523
+ plot_samples(x, rho, x_min, x_max, samples, latex=latex, save=save)
524
+
525
+ return samples
470
526
 
471
527
  # ======
472
528
  # matrix
@@ -12,15 +12,16 @@
12
12
  # =======
13
13
 
14
14
  import numpy
15
+ from scipy.stats import gaussian_kde
15
16
  from functools import partial
16
17
  from ._util import compute_eig, force_density
17
18
  from ._jacobi import jacobi_proj, jacobi_approx, jacobi_stieltjes
18
19
  from ._chebyshev import chebyshev_proj, chebyshev_approx, chebyshev_stieltjes
19
20
  from ._damp import jackson_damping, lanczos_damping, fejer_damping, \
20
21
  exponential_damping, parzen_damping
21
- from ._plot_util import plot_coeff, plot_density, plot_hilbert, \
22
- plot_stieltjes, plot_glue_fit
22
+ from ._plot_util import plot_fit, plot_density, plot_hilbert, plot_stieltjes
23
23
  from ._pade import fit_pade, eval_pade
24
+ from ._decompress import decompress
24
25
 
25
26
  __all__ = ['FreeForm']
26
27
 
@@ -45,6 +46,13 @@ class FreeForm(object):
45
46
  The support of the density of :math:`\\mathbf{A}`. If `None`, it is
46
47
  estimated from the minimum and maximum of the eigenvalues.
47
48
 
49
+ p : float, default=0.001
50
+ The edges of the support of the distribution is detected by the
51
+ :math:`p`-quantile on the left and :math:`(1-p)`-quantile on the right.
52
+ If the argument ``support`` is directly provided, this option is
53
+ ignored. This value should be between 0 and 1, ideally a small
54
+ number close to zero.
55
+
48
56
  Notes
49
57
  -----
50
58
 
@@ -64,6 +72,10 @@ class FreeForm(object):
64
72
  psi : numpy.array
65
73
  Jacobi coefficients.
66
74
 
75
+ n : int
76
+ Initial array size (assuming a square matrix when :math:`\\mathbf{A}`
77
+ is 2D)
78
+
67
79
  Methods
68
80
  -------
69
81
 
@@ -94,7 +106,7 @@ class FreeForm(object):
94
106
  # init
95
107
  # ====
96
108
 
97
- def __init__(self, A, support=None):
109
+ def __init__(self, A, support=None, p=0.001):
98
110
  """
99
111
  Initialization.
100
112
  """
@@ -106,16 +118,20 @@ class FreeForm(object):
106
118
  if A.ndim == 1:
107
119
  # When A is a 1D array, it is assumed A is the eigenvalue array.
108
120
  self.eig = A
121
+ self.n = len(A)
109
122
  elif A.ndim == 2:
110
123
  # When A is a 2D array, it is assumed A is the actual array,
111
124
  # and its eigenvalues will be computed.
112
125
  self.A = A
126
+ self.n = A.shape[0]
127
+ assert A.shape[0] == A.shape[1], \
128
+ 'Only square matrices are permitted.'
113
129
  self.eig = compute_eig(A)
114
130
 
115
131
  # Support
116
132
  if support is None:
117
- self.lam_m = self.eig.min()
118
- self.lam_p = self.eig.max()
133
+ self.lam_m, self.lam_p = self._detect_support(self.eig, p,
134
+ smoothen=True)
119
135
  else:
120
136
  self.lam_m = support[0]
121
137
  self.lam_p = support[1]
@@ -126,20 +142,46 @@ class FreeForm(object):
126
142
  self.psi = None
127
143
  self.alpha = None
128
144
  self.beta = None
145
+ self._pade_sol = None
146
+
147
+ # ==============
148
+ # detect support
149
+ # ==============
150
+
151
+ def _detect_support(self, eig, p, smoothen=True):
152
+ """
153
+ """
154
+
155
+ # Using quantile directly.
156
+ if smoothen:
157
+ kde = gaussian_kde(eig)
158
+ xs = numpy.linspace(eig.min(), eig.max(), 1000)
159
+ fs = kde(xs)
160
+
161
+ cdf = numpy.cumsum(fs)
162
+ cdf /= cdf[-1]
163
+
164
+ lam_m = numpy.interp(p, cdf, xs)
165
+ lam_p = numpy.interp(1-p, cdf, xs)
166
+ else:
167
+ lam_m, lam_p = numpy.quantile(eig, [p, 1-p])
168
+
169
+ return lam_m, lam_p
129
170
 
130
171
  # ===
131
172
  # fit
132
173
  # ===
133
174
 
134
175
  def fit(self, method='jacobi', K=10, alpha=0.0, beta=0.0, reg=0.0,
135
- damp=None, force=False, plot=False, latex=False, save=False):
176
+ damp=None, force=False, pade_p=1, pade_q=1, plot=False,
177
+ latex=False, save=False):
136
178
  """
137
179
  Fit model to eigenvalues.
138
180
 
139
181
  Parameters
140
182
  ----------
141
183
 
142
- method : {``'jacobi'``, ``'chebyshev'``}, default=``'jacobi'``
184
+ method : {``'jacobi'``, ``'chebyshev'``}, default= ``'jacobi'``
143
185
  Method of approximation, either by Jacobi polynomials or Chebyshev
144
186
  polynomials of the second kind.
145
187
 
@@ -167,8 +209,15 @@ class FreeForm(object):
167
209
  If `True`, it forces the density to have unit mass and to be
168
210
  strictly positive.
169
211
 
212
+ pade_p : int, default=1
213
+ Degree of polynomial :math:`P(z)`. See notes below.
214
+
215
+ pade_q : int, default=1
216
+ Degree of polynomial :math:`Q(z)`. See notes below.
217
+
170
218
  plot : bool, default=False
171
- If `True`, density is plotted.
219
+ If `True`, the approximation coefficients and pade approximaton to
220
+ the Hilbert traosnform are plotted.
172
221
 
173
222
  latex : bool, default=False
174
223
  If `True`, the plot is rendered using LaTeX. This option is
@@ -245,8 +294,24 @@ class FreeForm(object):
245
294
  self.alpha = alpha
246
295
  self.beta = beta
247
296
 
297
+ # For holomorphic continuation for the lower half-plane
298
+ x_supp = numpy.linspace(self.lam_m, self.lam_p, 1000)
299
+ g_supp = 2.0 * numpy.pi * self.hilbert(x_supp)
300
+
301
+ # Fit a pade approximation
302
+ self._pade_sol = fit_pade(x_supp, g_supp, self.lam_m,
303
+ self.lam_p, pade_p, pade_q, delta=1e-8,
304
+ B=numpy.inf, S=numpy.inf)
305
+
248
306
  if plot:
249
- plot_coeff(psi, latex=latex, save=save)
307
+ # Unpack optimized parameters
308
+ s = self._pade_sol['s']
309
+ a = self._pade_sol['a']
310
+ b = self._pade_sol['b']
311
+
312
+ g_supp_approx = eval_pade(x_supp[None, :], s, a, b)[0, :]
313
+ plot_fit(psi, x_supp, g_supp, g_supp_approx, support=self.support,
314
+ latex=latex, save=save)
250
315
 
251
316
  return self.psi
252
317
 
@@ -444,41 +509,28 @@ class FreeForm(object):
444
509
  # glue
445
510
  # ====
446
511
 
447
- def _glue(self, z, p, q, plot_glue=False, latex=False, save=False):
512
+ def _glue(self, z):
448
513
  """
449
514
  """
450
515
 
451
- # Holomorphic continuation for the lower half-plane
452
- x_supp = numpy.linspace(self.lam_m, self.lam_p, 1000)
453
- g_supp = 2.0 * numpy.pi * self.hilbert(x_supp)
454
-
455
- # Fit a pade approximation
456
- sol = fit_pade(x_supp, g_supp, self.lam_m, self.lam_p, p, q,
457
- delta=1e-8, B=numpy.inf, S=numpy.inf)
458
-
459
516
  # Unpack optimized parameters
460
- s = sol['s']
461
- a = sol['a']
462
- b = sol['b']
517
+ s = self._pade_sol['s']
518
+ a = self._pade_sol['a']
519
+ b = self._pade_sol['b']
463
520
 
464
521
  # Glue function
465
522
  g = eval_pade(z, s, a, b)
466
523
 
467
- if plot_glue:
468
- g_supp_approx = eval_pade(x_supp[None, :], s, a, b)[0, :]
469
- plot_glue_fit(x_supp, g_supp, g_supp_approx, support=self.support,
470
- latex=latex, save=save)
471
-
472
524
  return g
473
525
 
474
526
  # =========
475
527
  # stieltjes
476
528
  # =========
477
529
 
478
- def stieltjes(self, x, y, p=1, q=2, plot=False, plot_glue=False,
479
- latex=False, save=False):
530
+ def stieltjes(self, x, y, plot=False, latex=False, save=False):
480
531
  """
481
- Compute Stieltjes transform of the spectral density.
532
+ Compute Stieltjes transform of the spectral density over a 2D Cartesian
533
+ grid on the complex plane.
482
534
 
483
535
  Parameters
484
536
  ----------
@@ -492,19 +544,9 @@ class FreeForm(object):
492
544
  The y axis of the grid where the Stieltjes transform is evaluated.
493
545
  If `None`, a grid on the interval ``[-1, 1]`` is used.
494
546
 
495
- p : int, default=1
496
- Degree of polynomial :math:`P(z)`. See notes below.
497
-
498
- q : int, default=1
499
- Degree of polynomial :math:`Q(z)`. See notes below.
500
-
501
547
  plot : bool, default=False
502
548
  If `True`, density is plotted.
503
549
 
504
- plot_glue : bool, default=False
505
- If `True`, the fit of glue function to Hilbert transform is
506
- plotted.
507
-
508
550
  latex : bool, default=False
509
551
  If `True`, the plot is rendered using LaTeX. This option is
510
552
  relevant only if ``plot=True``.
@@ -536,7 +578,7 @@ class FreeForm(object):
536
578
  References
537
579
  ----------
538
580
 
539
- .. [1] rbd
581
+ .. [1] tbd
540
582
 
541
583
  Examples
542
584
  --------
@@ -591,46 +633,165 @@ class FreeForm(object):
591
633
  m1[mask_m, :] = numpy.conjugate(
592
634
  stieltjes(numpy.conjugate(z[mask_m, :])))
593
635
 
636
+ # Second Reimann sheet
594
637
  m2[mask_p, :] = m1[mask_p, :]
595
- m2[mask_m, :] = -m1[mask_m, :] + self._glue(
596
- z[mask_m, :], p, q, plot_glue=plot_glue, latex=latex,
597
- save=save)
638
+ m2[mask_m, :] = -m1[mask_m, :] + self._glue(z[mask_m, :])
598
639
 
599
640
  if plot:
600
641
  plot_stieltjes(x, y, m1, m2, self.support, latex=latex, save=save)
601
642
 
602
643
  return m1, m2
603
644
 
645
+ # ==============
646
+ # eval stieltjes
647
+ # ==============
648
+
649
+ def _eval_stieltjes(self, z):
650
+ """
651
+ Compute Stieltjes transform of the spectral density.
652
+
653
+ Parameters
654
+ ----------
655
+
656
+ z : numpy.array
657
+ The z values in the complex plan where the Stieltjes transform is
658
+ evaluated.
659
+
660
+
661
+ Returns
662
+ -------
663
+
664
+ m_p : numpy.ndarray
665
+ The Stieltjes transform on the principal branch.
666
+
667
+ m_m : numpy.ndarray
668
+ The Stieltjes transform continued to the secondary branch.
669
+
670
+ See Also
671
+ --------
672
+ density
673
+ hilbert
674
+
675
+ Notes
676
+ -----
677
+
678
+ Notes.
679
+
680
+ References
681
+ ----------
682
+
683
+ .. [1] tbd
684
+
685
+ Examples
686
+ --------
687
+
688
+ .. code-block:: python
689
+
690
+ >>> from freealg import FreeForm
691
+ """
692
+
693
+ if self.psi is None:
694
+ raise RuntimeError('"fit" the model first.')
695
+
696
+ z = numpy.asarray(z)
697
+ shape = z.shape
698
+ if len(shape) == 0:
699
+ shape = (1,)
700
+ z = z.reshape(-1, 1)
701
+
702
+ # Stieltjes function
703
+ if self.method == 'jacobi':
704
+ stieltjes = partial(jacobi_stieltjes, psi=self.psi,
705
+ support=self.support, alpha=self.alpha,
706
+ beta=self.beta)
707
+ elif self.method == 'chebyshev':
708
+ stieltjes = partial(chebyshev_stieltjes, psi=self.psi,
709
+ support=self.support)
710
+
711
+ mask_p = z.imag >= 0.0
712
+ mask_m = z.imag < 0.0
713
+
714
+ m1 = numpy.zeros_like(z)
715
+ m2 = numpy.zeros_like(z)
716
+
717
+ # Upper half-plane
718
+ m1[mask_p] = stieltjes(z[mask_p].reshape(-1, 1)).reshape(-1)
719
+
720
+ # Lower half-plane, use Schwarz reflection
721
+ m1[mask_m] = numpy.conjugate(
722
+ stieltjes(numpy.conjugate(z[mask_m].reshape(-1, 1)))).reshape(-1)
723
+
724
+ # Second Reimann sheet
725
+ m2[mask_p] = m1[mask_p]
726
+ m2[mask_m] = -m1[mask_m] + self._glue(
727
+ z[mask_m].reshape(-1, 1)).reshape(-1)
728
+
729
+ m1, m2 = m1.reshape(*shape), m2.reshape(*shape)
730
+
731
+ return m1, m2
732
+
604
733
  # ==========
605
734
  # decompress
606
735
  # ==========
607
736
 
608
- def decompress(self, n):
737
+ def decompress(self, size, x=None, delta=1e-4, iterations=500,
738
+ step_size=0.1, tolerance=1e-4, plot=False, latex=False,
739
+ save=False):
609
740
  """
610
741
  Free decompression of spectral density.
611
742
 
612
743
  Parameters
613
744
  ----------
614
745
 
615
- n : int
616
- Size of the matrix.
746
+ size : int
747
+ Size of the decompressed matrix.
748
+
749
+ x : numpy.array, default=None
750
+ Positions where density to be evaluated at. If `None`, an interval
751
+ slightly larger than the support interval will be used.
752
+
753
+ delta: float, default=1e-4
754
+ Size of the perturbation into the upper half plane for Plemelj's
755
+ formula.
756
+
757
+ iterations: int, default=500
758
+ Maximum number of Newton iterations.
759
+
760
+ step_size: float, default=0.1
761
+ Step size for Newton iterations.
762
+
763
+ tolerance: float, default=1e-4
764
+ Tolerance for the solution obtained by the Newton solver. Also
765
+ used for the finite difference approximation to the derivative.
766
+
767
+ plot : bool, default=False
768
+ If `True`, density is plotted.
769
+
770
+ latex : bool, default=False
771
+ If `True`, the plot is rendered using LaTeX. This option is
772
+ relevant only if ``plot=True``.
773
+
774
+ save : bool, default=False
775
+ If not `False`, the plot is saved. If a string is given, it is
776
+ assumed to the save filename (with the file extension). This option
777
+ is relevant only if ``plot=True``.
617
778
 
618
779
  Returns
619
780
  -------
620
781
 
621
782
  rho : numpy.array
622
- Spenctral density
783
+ Spectral density
623
784
 
624
785
  See Also
625
786
  --------
626
787
 
627
788
  density
628
- stiletjes
789
+ stieltjes
629
790
 
630
791
  Notes
631
792
  -----
632
793
 
633
- Not implemented.
794
+ Work in progress.
634
795
 
635
796
  References
636
797
  ----------
@@ -645,4 +806,12 @@ class FreeForm(object):
645
806
  >>> from freealg import FreeForm
646
807
  """
647
808
 
648
- pass
809
+ rho, x, (lb, ub) = decompress(self, size, x=x, delta=delta,
810
+ iterations=iterations,
811
+ step_size=step_size, tolerance=tolerance)
812
+
813
+ if plot:
814
+ plot_density(x.reshape(-1), rho.reshape(-1), support=(lb, ub),
815
+ label='Decompression', latex=latex, save=save)
816
+
817
+ return rho
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: freealg
3
- Version: 0.0.1
3
+ Version: 0.0.3
4
4
  Summary: Free probability for large matrices
5
5
  Keywords: leaderboard bot chat
6
6
  Platform: Linux
@@ -10,6 +10,7 @@ freealg/__init__.py
10
10
  freealg/__version__.py
11
11
  freealg/_chebyshev.py
12
12
  freealg/_damp.py
13
+ freealg/_decompress.py
13
14
  freealg/_jacobi.py
14
15
  freealg/_pade.py
15
16
  freealg/_plot_util.py
@@ -1 +0,0 @@
1
- __version__ = "0.0.1"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes