freealg 0.0.2__py3-none-any.whl → 0.1.0__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/freeform.py CHANGED
@@ -12,15 +12,17 @@
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
25
+ from ._sample import qmc_sample
24
26
 
25
27
  __all__ = ['FreeForm']
26
28
 
@@ -45,6 +47,13 @@ class FreeForm(object):
45
47
  The support of the density of :math:`\\mathbf{A}`. If `None`, it is
46
48
  estimated from the minimum and maximum of the eigenvalues.
47
49
 
50
+ p : float, default=0.001
51
+ The edges of the support of the distribution is detected by the
52
+ :math:`p`-quantile on the left and :math:`(1-p)`-quantile on the right.
53
+ If the argument ``support`` is directly provided, this option is
54
+ ignored. This value should be between 0 and 1, ideally a small
55
+ number close to zero.
56
+
48
57
  Notes
49
58
  -----
50
59
 
@@ -64,6 +73,10 @@ class FreeForm(object):
64
73
  psi : numpy.array
65
74
  Jacobi coefficients.
66
75
 
76
+ n : int
77
+ Initial array size (assuming a square matrix when :math:`\\mathbf{A}`
78
+ is 2D)
79
+
67
80
  Methods
68
81
  -------
69
82
 
@@ -94,7 +107,7 @@ class FreeForm(object):
94
107
  # init
95
108
  # ====
96
109
 
97
- def __init__(self, A, support=None):
110
+ def __init__(self, A, support=None, p=0.001):
98
111
  """
99
112
  Initialization.
100
113
  """
@@ -106,16 +119,20 @@ class FreeForm(object):
106
119
  if A.ndim == 1:
107
120
  # When A is a 1D array, it is assumed A is the eigenvalue array.
108
121
  self.eig = A
122
+ self.n = len(A)
109
123
  elif A.ndim == 2:
110
124
  # When A is a 2D array, it is assumed A is the actual array,
111
125
  # and its eigenvalues will be computed.
112
126
  self.A = A
127
+ self.n = A.shape[0]
128
+ assert A.shape[0] == A.shape[1], \
129
+ 'Only square matrices are permitted.'
113
130
  self.eig = compute_eig(A)
114
131
 
115
132
  # Support
116
133
  if support is None:
117
- self.lam_m = self.eig.min()
118
- self.lam_p = self.eig.max()
134
+ self.lam_m, self.lam_p = self._detect_support(self.eig, p,
135
+ smoothen=True)
119
136
  else:
120
137
  self.lam_m = support[0]
121
138
  self.lam_p = support[1]
@@ -126,20 +143,46 @@ class FreeForm(object):
126
143
  self.psi = None
127
144
  self.alpha = None
128
145
  self.beta = None
146
+ self._pade_sol = None
147
+
148
+ # ==============
149
+ # detect support
150
+ # ==============
151
+
152
+ def _detect_support(self, eig, p, smoothen=True):
153
+ """
154
+ """
155
+
156
+ # Using quantile directly.
157
+ if smoothen:
158
+ kde = gaussian_kde(eig)
159
+ xs = numpy.linspace(eig.min(), eig.max(), 1000)
160
+ fs = kde(xs)
161
+
162
+ cdf = numpy.cumsum(fs)
163
+ cdf /= cdf[-1]
164
+
165
+ lam_m = numpy.interp(p, cdf, xs)
166
+ lam_p = numpy.interp(1-p, cdf, xs)
167
+ else:
168
+ lam_m, lam_p = numpy.quantile(eig, [p, 1-p])
169
+
170
+ return lam_m, lam_p
129
171
 
130
172
  # ===
131
173
  # fit
132
174
  # ===
133
175
 
134
176
  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):
177
+ damp=None, force=False, pade_p=0, pade_q=1, odd_side='left',
178
+ optimizer='ls', plot=False, latex=False, save=False):
136
179
  """
137
180
  Fit model to eigenvalues.
138
181
 
139
182
  Parameters
140
183
  ----------
141
184
 
142
- method : {``'jacobi'``, ``'chebyshev'``}, default=``'jacobi'``
185
+ method : {``'jacobi'``, ``'chebyshev'``}, default= ``'jacobi'``
143
186
  Method of approximation, either by Jacobi polynomials or Chebyshev
144
187
  polynomials of the second kind.
145
188
 
@@ -167,8 +210,30 @@ class FreeForm(object):
167
210
  If `True`, it forces the density to have unit mass and to be
168
211
  strictly positive.
169
212
 
213
+ pade_p : int, default=0
214
+ Degree of polynomial :math:`P(z)` is :math:`q+p` where :math:`p`
215
+ can only be ``-1``, ``0``, or ``1``. See notes below.
216
+
217
+ pade_q : int, default=1
218
+ Degree of polynomial :math:`Q(z)` is :math:`q`. See notes below.
219
+
220
+ odd_side : {``'left'``, ``'right'``}, default= ``'left'``
221
+ In case of odd number of poles (when :math:`q` is odd), the extra
222
+ pole is set to the left or right side of the support interval,
223
+ while all other poles are split in half to the left and right. Note
224
+ that this is only for the initialization of the poles. The
225
+ optimizer will decide best location by moving them to the left or
226
+ right of the support.
227
+
228
+ optimizer : {``'ls'``, ``'de'``}, default= ``'ls'``
229
+ Optimizer for Pade approximation, including:
230
+
231
+ * ``'ls'``: least square (local, fast)
232
+ * ``'de'``: differential evolution (global, slow)
233
+
170
234
  plot : bool, default=False
171
- If `True`, density is plotted.
235
+ If `True`, the approximation coefficients and Pade approximation to
236
+ the Hilbert transform are plotted.
172
237
 
173
238
  latex : bool, default=False
174
239
  If `True`, the plot is rendered using LaTeX. This option is
@@ -185,6 +250,20 @@ class FreeForm(object):
185
250
  psi : (K+1, ) numpy.ndarray
186
251
  Coefficients of fitting Jacobi polynomials
187
252
 
253
+ Notes
254
+ -----
255
+
256
+ The Pade approximation for the glue function :math:`G(z)` is
257
+
258
+ .. math::
259
+
260
+ G(z) = \\frac{P(z)}{Q(z)},
261
+
262
+ where :math:`P(z)` and :math:`Q(z)` are polynomials of order
263
+ :math:`p+q` and :math:`q` respectively. Note that :math:`p` can only
264
+ be -1, 0, or 1, effectively making Pade approximation of order
265
+ :math:`q-1:q`, :math:`q:q`, or :math:`q-1:q`.
266
+
188
267
  Examples
189
268
  --------
190
269
 
@@ -245,8 +324,23 @@ class FreeForm(object):
245
324
  self.alpha = alpha
246
325
  self.beta = beta
247
326
 
327
+ # For holomorphic continuation for the lower half-plane
328
+ x_supp = numpy.linspace(self.lam_m, self.lam_p, 1000)
329
+ g_supp = 2.0 * numpy.pi * self.hilbert(x_supp)
330
+
331
+ # Fit a pade approximation
332
+ # self._pade_sol = fit_pade(x_supp, g_supp, self.lam_m,
333
+ # self.lam_p, pade_p, pade_q, delta=1e-8,
334
+ # B=numpy.inf, S=numpy.inf)
335
+ self._pade_sol = fit_pade(x_supp, g_supp, self.lam_m, self.lam_p,
336
+ p=pade_p, q=pade_q, odd_side=odd_side,
337
+ safety=1.0, max_outer=40, xtol=1e-12,
338
+ ftol=1e-12, optimizer=optimizer, verbose=0)
339
+
248
340
  if plot:
249
- plot_coeff(psi, latex=latex, save=save)
341
+ g_supp_approx = eval_pade(x_supp[None, :], self._pade_sol)[0, :]
342
+ plot_fit(psi, x_supp, g_supp, g_supp_approx, support=self.support,
343
+ latex=latex, save=save)
250
344
 
251
345
  return self.psi
252
346
 
@@ -444,30 +538,12 @@ class FreeForm(object):
444
538
  # glue
445
539
  # ====
446
540
 
447
- def _glue(self, z, p, q, plot_glue=False, latex=False, save=False):
541
+ def _glue(self, z):
448
542
  """
449
543
  """
450
544
 
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
- # Unpack optimized parameters
460
- s = sol['s']
461
- a = sol['a']
462
- b = sol['b']
463
-
464
545
  # Glue function
465
- g = eval_pade(z, s, a, b)
466
-
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)
546
+ g = eval_pade(z, self._pade_sol)
471
547
 
472
548
  return g
473
549
 
@@ -475,10 +551,10 @@ class FreeForm(object):
475
551
  # stieltjes
476
552
  # =========
477
553
 
478
- def stieltjes(self, x, y, p=1, q=2, plot=False, plot_glue=False,
479
- latex=False, save=False):
554
+ def stieltjes(self, x, y, plot=False, latex=False, save=False):
480
555
  """
481
- Compute Stieltjes transform of the spectral density.
556
+ Compute Stieltjes transform of the spectral density over a 2D Cartesian
557
+ grid on the complex plane.
482
558
 
483
559
  Parameters
484
560
  ----------
@@ -492,19 +568,9 @@ class FreeForm(object):
492
568
  The y axis of the grid where the Stieltjes transform is evaluated.
493
569
  If `None`, a grid on the interval ``[-1, 1]`` is used.
494
570
 
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
571
  plot : bool, default=False
502
572
  If `True`, density is plotted.
503
573
 
504
- plot_glue : bool, default=False
505
- If `True`, the fit of glue function to Hilbert transform is
506
- plotted.
507
-
508
574
  latex : bool, default=False
509
575
  If `True`, the plot is rendered using LaTeX. This option is
510
576
  relevant only if ``plot=True``.
@@ -536,7 +602,7 @@ class FreeForm(object):
536
602
  References
537
603
  ----------
538
604
 
539
- .. [1] rbd
605
+ .. [1] tbd
540
606
 
541
607
  Examples
542
608
  --------
@@ -591,35 +657,158 @@ class FreeForm(object):
591
657
  m1[mask_m, :] = numpy.conjugate(
592
658
  stieltjes(numpy.conjugate(z[mask_m, :])))
593
659
 
660
+ # Second Riemann sheet
594
661
  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)
662
+ m2[mask_m, :] = -m1[mask_m, :] + self._glue(z[mask_m, :])
598
663
 
599
664
  if plot:
600
665
  plot_stieltjes(x, y, m1, m2, self.support, latex=latex, save=save)
601
666
 
602
667
  return m1, m2
603
668
 
669
+ # ==============
670
+ # eval stieltjes
671
+ # ==============
672
+
673
+ def _eval_stieltjes(self, z):
674
+ """
675
+ Compute Stieltjes transform of the spectral density.
676
+
677
+ Parameters
678
+ ----------
679
+
680
+ z : numpy.array
681
+ The z values in the complex plan where the Stieltjes transform is
682
+ evaluated.
683
+
684
+
685
+ Returns
686
+ -------
687
+
688
+ m_p : numpy.ndarray
689
+ The Stieltjes transform on the principal branch.
690
+
691
+ m_m : numpy.ndarray
692
+ The Stieltjes transform continued to the secondary branch.
693
+
694
+ See Also
695
+ --------
696
+ density
697
+ hilbert
698
+
699
+ Notes
700
+ -----
701
+
702
+ Notes.
703
+
704
+ References
705
+ ----------
706
+
707
+ .. [1] tbd
708
+
709
+ Examples
710
+ --------
711
+
712
+ .. code-block:: python
713
+
714
+ >>> from freealg import FreeForm
715
+ """
716
+
717
+ if self.psi is None:
718
+ raise RuntimeError('"fit" the model first.')
719
+
720
+ z = numpy.asarray(z)
721
+ shape = z.shape
722
+ if len(shape) == 0:
723
+ shape = (1,)
724
+ z = z.reshape(-1, 1)
725
+
726
+ # Stieltjes function
727
+ if self.method == 'jacobi':
728
+ stieltjes = partial(jacobi_stieltjes, psi=self.psi,
729
+ support=self.support, alpha=self.alpha,
730
+ beta=self.beta)
731
+ elif self.method == 'chebyshev':
732
+ stieltjes = partial(chebyshev_stieltjes, psi=self.psi,
733
+ support=self.support)
734
+
735
+ mask_p = z.imag >= 0.0
736
+ mask_m = z.imag < 0.0
737
+
738
+ m1 = numpy.zeros_like(z)
739
+ m2 = numpy.zeros_like(z)
740
+
741
+ # Upper half-plane
742
+ m1[mask_p] = stieltjes(z[mask_p].reshape(-1, 1)).reshape(-1)
743
+
744
+ # Lower half-plane, use Schwarz reflection
745
+ m1[mask_m] = numpy.conjugate(
746
+ stieltjes(numpy.conjugate(z[mask_m].reshape(-1, 1)))).reshape(-1)
747
+
748
+ # Second Riemann sheet
749
+ m2[mask_p] = m1[mask_p]
750
+ m2[mask_m] = -m1[mask_m] + self._glue(
751
+ z[mask_m].reshape(-1, 1)).reshape(-1)
752
+
753
+ m1, m2 = m1.reshape(*shape), m2.reshape(*shape)
754
+
755
+ return m1, m2
756
+
604
757
  # ==========
605
758
  # decompress
606
759
  # ==========
607
760
 
608
- def decompress(self, n):
761
+ def decompress(self, size, x=None, delta=1e-6, iterations=500,
762
+ step_size=0.1, tolerance=1e-4, plot=False, latex=False,
763
+ save=False):
609
764
  """
610
765
  Free decompression of spectral density.
611
766
 
612
767
  Parameters
613
768
  ----------
614
769
 
615
- n : int
616
- Size of the matrix.
770
+ size : int
771
+ Size of the decompressed matrix.
772
+
773
+ x : numpy.array, default=None
774
+ Positions where density to be evaluated at. If `None`, an interval
775
+ slightly larger than the support interval will be used.
776
+
777
+ delta: float, default=1e-4
778
+ Size of the perturbation into the upper half plane for Plemelj's
779
+ formula.
780
+
781
+ iterations: int, default=500
782
+ Maximum number of Newton iterations.
783
+
784
+ step_size: float, default=0.1
785
+ Step size for Newton iterations.
786
+
787
+ tolerance: float, default=1e-4
788
+ Tolerance for the solution obtained by the Newton solver. Also
789
+ used for the finite difference approximation to the derivative.
790
+
791
+ plot : bool, default=False
792
+ If `True`, density is plotted.
793
+
794
+ latex : bool, default=False
795
+ If `True`, the plot is rendered using LaTeX. This option is
796
+ relevant only if ``plot=True``.
797
+
798
+ save : bool, default=False
799
+ If not `False`, the plot is saved. If a string is given, it is
800
+ assumed to the save filename (with the file extension). This option
801
+ is relevant only if ``plot=True``.
617
802
 
618
803
  Returns
619
804
  -------
620
805
 
621
806
  rho : numpy.array
622
- Spenctral density
807
+ Spectral density
808
+
809
+ eigs : numpy.array
810
+ Estimated eigenvalues as low-discrepancy samples of the estimated
811
+ spectral density.
623
812
 
624
813
  See Also
625
814
  --------
@@ -630,7 +819,7 @@ class FreeForm(object):
630
819
  Notes
631
820
  -----
632
821
 
633
- Not implemented.
822
+ Work in progress.
634
823
 
635
824
  References
636
825
  ----------
@@ -645,4 +834,15 @@ class FreeForm(object):
645
834
  >>> from freealg import FreeForm
646
835
  """
647
836
 
648
- pass
837
+ rho, x, (lb, ub) = decompress(self, size, x=x, delta=delta,
838
+ iterations=iterations,
839
+ step_size=step_size, tolerance=tolerance)
840
+ x, rho = x.ravel(), rho.ravel()
841
+
842
+ if plot:
843
+ plot_density(x, rho, support=(lb, ub),
844
+ label='Decompression', latex=latex, save=save)
845
+
846
+ eigs = numpy.sort(qmc_sample(x, rho, size))
847
+
848
+ return rho, eigs
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: freealg
3
- Version: 0.0.2
3
+ Version: 0.1.0
4
4
  Summary: Free probability for large matrices
5
5
  Keywords: leaderboard bot chat
6
6
  Platform: Linux
@@ -29,6 +29,7 @@ Requires-Dist: scipy
29
29
  Requires-Dist: texplot
30
30
  Requires-Dist: matplotlib
31
31
  Requires-Dist: colorcet
32
+ Requires-Dist: networkx
32
33
  Provides-Extra: test
33
34
  Provides-Extra: docs
34
35
  Dynamic: classifier
@@ -0,0 +1,21 @@
1
+ freealg/__init__.py,sha256=K92neXJZ9VE1U_j_pj28Qyq1MzlMXhOuYK2ZihgwCaU,463
2
+ freealg/__version__.py,sha256=kUR5RAFc7HCeiqdlX36dZOHkUI5wI6V_43RpEcD8b-0,22
3
+ freealg/_chebyshev.py,sha256=Cw48gXF6kd3IAQuLTEWadySeKGnY9TynzX9MNmycMUU,4697
4
+ freealg/_damp.py,sha256=k2vtBtWOxQBf4qXaWu_En81lQBXbEO4QbxxWpvuVhdE,1802
5
+ freealg/_decompress.py,sha256=H7ocq09gQnCY-q_8xHi6qyYj3qp239MCgj406hn0yeE,3344
6
+ freealg/_jacobi.py,sha256=HVnaujwAcaILVFGEgxk26UyIaLdP2FZMY44C8TG3Qcc,4763
7
+ freealg/_pade.py,sha256=wjFH1lfhuaQANNNMLVa8eAlKbP3sz8l8ZMw-P2X3T4A,12867
8
+ freealg/_plot_util.py,sha256=aL0u7FHdCwLTj4dZ4SSGA00wab0Voem2nBAsBVvo6XY,18400
9
+ freealg/_sample.py,sha256=K1ZxKoiuPbEKyh-swL5X7gz1kYcQno6Mof0o1xF38tg,2323
10
+ freealg/_util.py,sha256=wJ-t8LMZZFEr2PsZEVqTJP_jQTQ3rHUf0dO27F6L7YQ,2310
11
+ freealg/freeform.py,sha256=nZqGZBEHVT7Cx1UbOe2-cupWBANNdmc44vm9Eyy9I0Q,25390
12
+ freealg/distributions/__init__.py,sha256=Hnk9bJi4Wy8I_1uuskRyrT2DUpPN1YmBY5uK7XI3U_o,644
13
+ freealg/distributions/kesten_mckay.py,sha256=SFYg2_6_MEX9NGfOIW_rRfM9kgF-bdjQiVS0ENJBemQ,15379
14
+ freealg/distributions/marchenko_pastur.py,sha256=0XHhjw1ZDkigGfjU9lmPRgmwecM1-n6VBkGjRJbxpeY,15869
15
+ freealg/distributions/wachter.py,sha256=cG-3XTP5CHSK5o6SQfa3lqQR__ZibeWZ9AoeMxr3Nnk,15602
16
+ freealg/distributions/wigner.py,sha256=gj3XWK4X7wLsB-inoVpn4pHUUM7NOQdgMGw01LEqkcw,14914
17
+ freealg-0.1.0.dist-info/licenses/LICENSE.txt,sha256=J-EEYEtxb3VVf_Bn1TYfWnpY5lMFIM15iLDDcnaDTPA,1443
18
+ freealg-0.1.0.dist-info/METADATA,sha256=hsJ1JS0j2HpMLsplIIdHlQeqOzMZamBkAVRL5xc1pYQ,2912
19
+ freealg-0.1.0.dist-info/WHEEL,sha256=DnLRTWE75wApRYVsjgc6wsVswC54sMSJhAEd4xhDpBk,91
20
+ freealg-0.1.0.dist-info/top_level.txt,sha256=eR2wrgYwDdnnJ9Zf5PruPqe4kQav0GMvRsqct6y00Q8,8
21
+ freealg-0.1.0.dist-info/RECORD,,
@@ -1,16 +0,0 @@
1
- freealg/__init__.py,sha256=K92neXJZ9VE1U_j_pj28Qyq1MzlMXhOuYK2ZihgwCaU,463
2
- freealg/__version__.py,sha256=QvlVh4JTl3JL7jQAja76yKtT-IvF4631ASjWY1wS6AQ,22
3
- freealg/_chebyshev.py,sha256=Cw48gXF6kd3IAQuLTEWadySeKGnY9TynzX9MNmycMUU,4697
4
- freealg/_damp.py,sha256=k2vtBtWOxQBf4qXaWu_En81lQBXbEO4QbxxWpvuVhdE,1802
5
- freealg/_jacobi.py,sha256=F9K0IzbExpxGQY2OJQb-XT4Ee1zWTZvMXRUdU8cbwSE,4916
6
- freealg/_pade.py,sha256=rWILLpEL910YdixC2c5Cw77HqogAYoxi2YZgRVXEELM,4022
7
- freealg/_plot_util.py,sha256=OILqOqKdLGEabk41KnmxNftItzMMFHOL-LZ35yNvdyk,18653
8
- freealg/_util.py,sha256=wJ-t8LMZZFEr2PsZEVqTJP_jQTQ3rHUf0dO27F6L7YQ,2310
9
- freealg/freeform.py,sha256=LLkGoPLxvn8VMHuVQ1LGNkcAYZUi3GduFB8qlsr69qg,18595
10
- freealg/distributions/__init__.py,sha256=7t4HbP_EofiFDYLH6jbD94AIumOdcHn1y_Qo54mpLFM,614
11
- freealg/distributions/marchenko_pastur.py,sha256=k8SoEgB2DFXLqGF-Hyqm8Sfh1DlF3khMssuxak5Uaqw,15827
12
- freealg-0.0.2.dist-info/licenses/LICENSE.txt,sha256=J-EEYEtxb3VVf_Bn1TYfWnpY5lMFIM15iLDDcnaDTPA,1443
13
- freealg-0.0.2.dist-info/METADATA,sha256=lMjLvkUpPUAr_QPthjnrIfg9KTs8DLEpTG68w8Ak0rU,2888
14
- freealg-0.0.2.dist-info/WHEEL,sha256=DnLRTWE75wApRYVsjgc6wsVswC54sMSJhAEd4xhDpBk,91
15
- freealg-0.0.2.dist-info/top_level.txt,sha256=eR2wrgYwDdnnJ9Zf5PruPqe4kQav0GMvRsqct6y00Q8,8
16
- freealg-0.0.2.dist-info/RECORD,,