freealg 0.1.7__tar.gz → 0.1.8__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 (34) hide show
  1. {freealg-0.1.7 → freealg-0.1.8}/PKG-INFO +1 -2
  2. freealg-0.1.8/freealg/__version__.py +1 -0
  3. {freealg-0.1.7 → freealg-0.1.8}/freealg/distributions/kesten_mckay.py +106 -10
  4. {freealg-0.1.7 → freealg-0.1.8}/freealg/distributions/marchenko_pastur.py +1 -1
  5. {freealg-0.1.7 → freealg-0.1.8}/freealg/distributions/meixner.py +49 -32
  6. {freealg-0.1.7 → freealg-0.1.8}/freealg/distributions/wachter.py +1 -1
  7. {freealg-0.1.7 → freealg-0.1.8}/freealg/distributions/wigner.py +17 -26
  8. {freealg-0.1.7 → freealg-0.1.8}/freealg.egg-info/PKG-INFO +1 -2
  9. {freealg-0.1.7 → freealg-0.1.8}/freealg.egg-info/requires.txt +0 -1
  10. {freealg-0.1.7 → freealg-0.1.8}/requirements.txt +0 -1
  11. freealg-0.1.7/freealg/__version__.py +0 -1
  12. {freealg-0.1.7 → freealg-0.1.8}/AUTHORS.txt +0 -0
  13. {freealg-0.1.7 → freealg-0.1.8}/CHANGELOG.rst +0 -0
  14. {freealg-0.1.7 → freealg-0.1.8}/LICENSE.txt +0 -0
  15. {freealg-0.1.7 → freealg-0.1.8}/MANIFEST.in +0 -0
  16. {freealg-0.1.7 → freealg-0.1.8}/README.rst +0 -0
  17. {freealg-0.1.7 → freealg-0.1.8}/freealg/__init__.py +0 -0
  18. {freealg-0.1.7 → freealg-0.1.8}/freealg/_chebyshev.py +0 -0
  19. {freealg-0.1.7 → freealg-0.1.8}/freealg/_damp.py +0 -0
  20. {freealg-0.1.7 → freealg-0.1.8}/freealg/_decompress.py +0 -0
  21. {freealg-0.1.7 → freealg-0.1.8}/freealg/_jacobi.py +0 -0
  22. {freealg-0.1.7 → freealg-0.1.8}/freealg/_pade.py +0 -0
  23. {freealg-0.1.7 → freealg-0.1.8}/freealg/_plot_util.py +0 -0
  24. {freealg-0.1.7 → freealg-0.1.8}/freealg/_sample.py +0 -0
  25. {freealg-0.1.7 → freealg-0.1.8}/freealg/_util.py +0 -0
  26. {freealg-0.1.7 → freealg-0.1.8}/freealg/distributions/__init__.py +0 -0
  27. {freealg-0.1.7 → freealg-0.1.8}/freealg/freeform.py +0 -0
  28. {freealg-0.1.7 → freealg-0.1.8}/freealg.egg-info/SOURCES.txt +0 -0
  29. {freealg-0.1.7 → freealg-0.1.8}/freealg.egg-info/dependency_links.txt +0 -0
  30. {freealg-0.1.7 → freealg-0.1.8}/freealg.egg-info/not-zip-safe +0 -0
  31. {freealg-0.1.7 → freealg-0.1.8}/freealg.egg-info/top_level.txt +0 -0
  32. {freealg-0.1.7 → freealg-0.1.8}/pyproject.toml +0 -0
  33. {freealg-0.1.7 → freealg-0.1.8}/setup.cfg +0 -0
  34. {freealg-0.1.7 → freealg-0.1.8}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: freealg
3
- Version: 0.1.7
3
+ Version: 0.1.8
4
4
  Summary: Free probability for large matrices
5
5
  Keywords: leaderboard bot chat
6
6
  Platform: Linux
@@ -30,7 +30,6 @@ Requires-Dist: scipy
30
30
  Requires-Dist: texplot
31
31
  Requires-Dist: matplotlib
32
32
  Requires-Dist: colorcet
33
- Requires-Dist: networkx
34
33
  Requires-Dist: statsmodels
35
34
  Provides-Extra: test
36
35
  Requires-Dist: tox; extra == "test"
@@ -0,0 +1 @@
1
+ __version__ = "0.1.8"
@@ -12,7 +12,6 @@
12
12
  # =======
13
13
 
14
14
  import numpy
15
- import networkx as nx
16
15
  from scipy.interpolate import interp1d
17
16
  from .._plot_util import plot_density, plot_hilbert, plot_stieltjes, \
18
17
  plot_stieltjes_on_disk, plot_samples
@@ -450,7 +449,7 @@ class KestenMcKay(object):
450
449
  is used.
451
450
 
452
451
  method : {``'mc'``, ``'qmc'``}, default= ``'qmc'``
453
- Method of drawing samples from uniform distirbution:
452
+ Method of drawing samples from uniform distribution:
454
453
 
455
454
  * ``'mc'``: Monte Carlo
456
455
  * ``'qmc'``: Quasi Monte Carlo
@@ -534,6 +533,39 @@ class KestenMcKay(object):
534
533
 
535
534
  return samples
536
535
 
536
+ # ============
537
+ # Haar unitary
538
+ # ============
539
+
540
+ def _haar_orthogonal(self, n, k):
541
+ """
542
+ Haar-distributed O(n) via the Mezzadri QR trick.
543
+
544
+ References
545
+ ----------
546
+
547
+ .. [1] Francesco Mezzadri. How to generate random matrices from the
548
+ classical compact groups. https://arxiv.org/pdf/math-ph/0609050
549
+
550
+ Notes
551
+ -----
552
+
553
+ Taking the QR of a normal-Gaussian matrix gives an orthonormal basis,
554
+ but the columns of that Q are not uniform on the sphere, as they are
555
+ biased by the signs or phases in the R-factor.
556
+
557
+ With Mezzadri method, columns of Q are rescaled by the reciprocals of
558
+ the diagonals of R phase, resulting in a matrix that is exactly
559
+ uniformly distributed under Haar measure O(n).
560
+ """
561
+
562
+ rng = numpy.random.default_rng()
563
+ Z = rng.standard_normal((n, k))
564
+ Q, R = numpy.linalg.qr(Z, mode='reduced') # Q is n by k
565
+ Q *= numpy.sign(numpy.diag(R))
566
+
567
+ return Q
568
+
537
569
  # ======
538
570
  # matrix
539
571
  # ======
@@ -554,6 +586,43 @@ class KestenMcKay(object):
554
586
  A : numpy.ndarray
555
587
  A matrix of the size :math:`n \\times n`.
556
588
 
589
+ Notes
590
+ -----
591
+
592
+ If the parameter :math:`d` is even, the matrtix is generated from
593
+
594
+ .. math::
595
+
596
+ \\mathbf{A} = \\sum_{i=1}^{d/2} \\mathbf{O}_i +
597
+ \\mathbf{O}_o^{\\intercal},
598
+
599
+ where :math:`\\mathbf{O}_i` are randomly generated orthogonal matrices
600
+ with Haar. This method is fast but :math:`d` has to be even.
601
+
602
+ If all other :math:`d`, the following is used:
603
+
604
+ .. math::
605
+
606
+ \\mathbf{A} = \\mathbf{P} \\mathbf{O} \\mathbf{D} \\mathbf{O}^{-1}
607
+ \\mathbf{P},
608
+
609
+ where :math:`\\mathbf{D}` is diagonal matrix with entries
610
+ :math:`\\pm 1`, :math:`\\mathbf{O}` is orthogonal with Haar measure,
611
+ and :math:`\\mathbf{P}` is a projection matrix. For more details, see
612
+ Section 5 and 6 of [1]_.
613
+
614
+ The orthogonal matrices are genrated using the method of [2]_.
615
+
616
+ References
617
+ ----------
618
+
619
+ .. [1] Iris S. A. Longoria and James A. Mingo, Freely Independent Coin
620
+ Tosses, Standard Young Tableaux, and the Kesten--McKay Law.
621
+ https://arxiv.org/abs/2009.11950
622
+
623
+ .. [2] Francesco Mezzadri. How to generate random matrices from the
624
+ classical compact groups. https://arxiv.org/pdf/math-ph/0609050
625
+
557
626
  Examples
558
627
  --------
559
628
 
@@ -564,11 +633,38 @@ class KestenMcKay(object):
564
633
  >>> A = km.matrix(2000)
565
634
  """
566
635
 
567
- n = size
568
- G = nx.random_regular_graph(self.d, n)
569
- A = nx.to_numpy_array(G, dtype=float) # shape (n,n)
570
-
571
- mu = self.d / n
572
- A_c = A - mu * numpy.ones((n, n))
573
-
574
- return A_c
636
+ if (self.d >= 2) and (self.d % 2 == 0):
637
+ # Uses algorithm 1 . Only if d is even. This is much faster than
638
+ # algorithm 2.
639
+ n = size
640
+ rng = numpy.random.default_rng()
641
+ m = self.d // 2
642
+ A = numpy.zeros((n, n))
643
+
644
+ for _ in range(m):
645
+ O_ = self._haar_orthogonal(n, n)
646
+ A += O_ + O_.T
647
+ else:
648
+ # Uses algorithm 2. Only when d is odd, but this algorithm works
649
+ # for any d (even and odd), but it takes much longer to comute
650
+ # especially if d is larger. As such, as only use algorithm 1 when
651
+ # d is even and use algorithm 2 for the rest.
652
+ n = size * self.d
653
+ rng = numpy.random.default_rng()
654
+
655
+ # Deterministic pieces
656
+ k = size
657
+ if k == 0:
658
+ raise ValueError('Choose size larger then d.')
659
+
660
+ # Projection rows of O
661
+ Q = self._haar_orthogonal(n, k)
662
+ O_k = Q.T
663
+
664
+ # diagonal D with equal \pm 1 (trace 0)
665
+ diag = numpy.ones(n, dtype=float)
666
+ diag[:n//2] = -1
667
+ rng.shuffle(diag)
668
+ A = (n/k) * (O_k * diag) @ O_k.T
669
+
670
+ return A
@@ -456,7 +456,7 @@ class MarchenkoPastur(object):
456
456
  is used.
457
457
 
458
458
  method : {``'mc'``, ``'qmc'``}, default= ``'qmc'``
459
- Method of drawing samples from uniform distirbution:
459
+ Method of drawing samples from uniform distribution:
460
460
 
461
461
  * ``'mc'``: Monte Carlo
462
462
  * ``'qmc'``: Quasi Monte Carlo
@@ -71,7 +71,7 @@ class Meixner(object):
71
71
  \\frac{4(1+b) - (x-a)^2}{2 \\pi (b x^2 + a x + 1)}
72
72
  \\mathbf{1}_{x \\in [\\lambda_{-}, \\lambda_{+}]} \\mathrm{d}{x}
73
73
 
74
- where :math:`a, b` are the shape parameters of the distributon. The edges
74
+ where :math:`a, b` are the shape parameters of the distribution. The edges
75
75
  of the support are
76
76
 
77
77
  .. math::
@@ -98,15 +98,16 @@ class Meixner(object):
98
98
  # init
99
99
  # ====
100
100
 
101
- def __init__(self, a, b):
101
+ def __init__(self, a, b, c):
102
102
  """
103
103
  Initialization.
104
104
  """
105
105
 
106
106
  self.a = a
107
107
  self.b = b
108
- self.lam_p = self.a + 2.0 * numpy.sqrt(1.0 + self.b)
109
- self.lam_m = self.a - 2.0 * numpy.sqrt(1.0 + self.b)
108
+ self.c = c
109
+ self.lam_p = self.a + 2.0 * numpy.sqrt(self.b)
110
+ self.lam_m = self.a - 2.0 * numpy.sqrt(self.b)
110
111
  self.support = (self.lam_m, self.lam_p)
111
112
 
112
113
  # =======
@@ -172,9 +173,19 @@ class Meixner(object):
172
173
  rho = numpy.zeros_like(x)
173
174
  mask = numpy.logical_and(x > self.lam_m, x < self.lam_p)
174
175
 
175
- rho[mask] = \
176
- numpy.sqrt(4.0 * (1.0 + self.b) - (x[mask] - self.a)**2) / \
177
- (2.0 * numpy.pi * (self.b * x[mask]**2 + self.a * x[mask] + 1))
176
+ # rho[mask] = \
177
+ # numpy.sqrt(4.0 * (1.0 + self.b) - (x[mask] - self.a)**2) / \
178
+ # (2.0 * numpy.pi * (self.b * x[mask]**2 + self.a * x[mask] + 1))
179
+
180
+ numer = numpy.zeros_like(x)
181
+ denom = numpy.ones_like(x)
182
+ numer[mask] = self.c * numpy.sqrt(4.0 * self.b - (x[mask] - self.a)**2)
183
+ denom[mask] = (1 - self.c)*(x[mask] - self.a)**2
184
+ denom[mask] += self.a * (2 - self.c)*(x[mask] - self.a)
185
+ denom[mask] += self.a**2 + self.b * self.c**2
186
+ denom[mask] *= 2 * numpy.pi
187
+
188
+ rho[mask] = numer[mask] / denom[mask]
178
189
 
179
190
  if plot:
180
191
  plot_density(x, rho, label='', latex=latex, save=save)
@@ -239,12 +250,17 @@ class Meixner(object):
239
250
  x = numpy.linspace(x_min, x_max, 500)
240
251
 
241
252
  def _P(x):
242
- denom = 1.0 + self.b
243
- return ((1.0 + 2.0 * self.b) * x + self.a) / denom
253
+ # denom = 1.0 + self.b
254
+ # return ((1.0 + 2.0 * self.b) * x + self.a) / denom
255
+ P = ((self.c - 2.0) * x - self.a * self.c) / 2.0
256
+ return P
244
257
 
245
258
  def _Q(x):
246
- denom = 1.0 + self.b
247
- return (self.b * x**2 + self.a * x + 1.0) / denom
259
+ # denom = 1.0 + self.b
260
+ # return (self.b * x**2 + self.a * x + 1.0) / denom
261
+ Q = ((1.0 - self.c) * x**2 + self.a * self.c * x +
262
+ self.b * self.c**2) / 4.0
263
+ return Q
248
264
 
249
265
  P = _P(x)
250
266
  Q = _Q(x)
@@ -272,21 +288,32 @@ class Meixner(object):
272
288
  """
273
289
 
274
290
  sign = -1 if alt_branch else 1
275
- denom = 1.0 + self.b
276
- A = (self.b * z**2 + self.a * z + 1.0) / denom
277
- B = ((1.0 + 2.0 * self.b) * z + self.a) / denom
278
- D = B**2 - 4 * A
279
- sqrtD = numpy.sqrt(D)
291
+ # denom = 1.0 + self.b
292
+ # A = (self.b * z**2 + self.a * z + 1.0) / denom
293
+ # B = ((1.0 + 2.0 * self.b) * z + self.a) / denom
294
+ A = ((1.0 - self.c) * z**2 + self.a * self.c * z +
295
+ self.b * self.c**2) / 4.0
296
+ B = ((self.c - 2.0) * z - self.a * self.c) / 2.0
297
+
298
+ # D = B**2 - 4 * A
299
+ # sqrtD = numpy.sqrt(D)
300
+
301
+ # Avoid numpy picking the wrong branch
302
+ d = 2 * numpy.sqrt(1.0 + self.b)
303
+ r_min = self.a - d
304
+ r_max = self.a + d
305
+ sqrtD = numpy.sqrt(z - r_min) * numpy.sqrt(z - r_max)
306
+
280
307
  m1 = (-B + sqrtD) / (2 * A)
281
308
  m2 = (-B - sqrtD) / (2 * A)
282
309
 
283
310
  # pick correct branch only for non‑masked entries
284
311
  upper = z.imag >= 0
285
312
  branch = numpy.empty_like(m1)
286
- branch[upper] = numpy.where(sign*m1[upper].imag > 0, m1[upper],
287
- m2[upper])
288
- branch[~upper] = numpy.where(sign*m1[~upper].imag < 0, m1[~upper],
289
- m2[~upper])
313
+ branch[upper] = numpy.where(
314
+ sign*m1[upper].imag > 0, m1[upper], m2[upper])
315
+ branch[~upper] = numpy.where(
316
+ sign*m1[~upper].imag < 0, m1[~upper], m2[~upper])
290
317
  m = branch
291
318
 
292
319
  return m
@@ -454,7 +481,7 @@ class Meixner(object):
454
481
  is used.
455
482
 
456
483
  method : {``'mc'``, ``'qmc'``}, default= ``'qmc'``
457
- Method of drawing samples from uniform distirbution:
484
+ Method of drawing samples from uniform distribution:
458
485
 
459
486
  * ``'mc'``: Monte Carlo
460
487
  * ``'qmc'``: Quasi Monte Carlo
@@ -571,14 +598,4 @@ class Meixner(object):
571
598
  >>> A = mx.matrix(2000)
572
599
  """
573
600
 
574
- n = size
575
- m1 = int(self.a * n)
576
- m2 = int(self.b * n)
577
-
578
- X = numpy.random.randn(n, m1)
579
- Y = numpy.random.randn(n, m2)
580
-
581
- Sx = X @ X.T
582
- Sy = Y @ Y.T
583
-
584
- return Sx, Sy
601
+ raise NotImplementedError
@@ -456,7 +456,7 @@ class Wachter(object):
456
456
  is used.
457
457
 
458
458
  method : {``'mc'``, ``'qmc'``}, default= ``'qmc'``
459
- Method of drawing samples from uniform distirbution:
459
+ Method of drawing samples from uniform distribution:
460
460
 
461
461
  * ``'mc'``: Monte Carlo
462
462
  * ``'qmc'``: Quasi Monte Carlo
@@ -12,7 +12,6 @@
12
12
  # =======
13
13
 
14
14
  import numpy
15
- import networkx as nx
16
15
  from scipy.interpolate import interp1d
17
16
  from .._plot_util import plot_density, plot_hilbert, plot_stieltjes, \
18
17
  plot_stieltjes_on_disk, plot_samples
@@ -77,20 +76,20 @@ class Wigner(object):
77
76
  .. code-block:: python
78
77
 
79
78
  >>> from freealg.distributions import Wigner
80
- >>> wg = Wigner()
79
+ >>> wg = Wigner(1)
81
80
  """
82
81
 
83
82
  # ====
84
83
  # init
85
84
  # ====
86
85
 
87
- def __init__(self):
86
+ def __init__(self, r):
88
87
  """
89
88
  Initialization.
90
89
  """
91
-
92
- self.lam_p = 2.0
93
- self.lam_m = -2.0
90
+ self.r = r
91
+ self.lam_p = self.r
92
+ self.lam_m = -self.r
94
93
  self.support = (self.lam_m, self.lam_p)
95
94
 
96
95
  # =======
@@ -136,7 +135,7 @@ class Wigner(object):
136
135
  .. code-block::python
137
136
 
138
137
  >>> from freealg.distributions import Wigner
139
- >>> wg = Wigner()
138
+ >>> wg = Wigner(1)
140
139
  >>> rho = wg.density(plot=True)
141
140
 
142
141
  .. image:: ../_static/images/plots/wg_density.png
@@ -156,8 +155,8 @@ class Wigner(object):
156
155
  rho = numpy.zeros_like(x)
157
156
  mask = numpy.logical_and(x >= self.lam_m, x <= self.lam_p)
158
157
 
159
- rho[mask] = (1.0 / (2.0 * numpy.pi)) * \
160
- numpy.sqrt(4.0 - x[mask]**2)
158
+ rho[mask] = (2.0 / (numpy.pi * self.r**2)) * \
159
+ numpy.sqrt(self.r**2 - x[mask]**2)
161
160
 
162
161
  if plot:
163
162
  plot_density(x, rho, label='', latex=latex, save=save)
@@ -204,7 +203,7 @@ class Wigner(object):
204
203
  .. code-block::python
205
204
 
206
205
  >>> from freealg.distributions import Wigner
207
- >>> wg = Wigner()
206
+ >>> wg = Wigner(1)
208
207
  >>> hilb = wg.hilbert(plot=True)
209
208
 
210
209
  .. image:: ../_static/images/plots/wg_hilbert.png
@@ -225,7 +224,7 @@ class Wigner(object):
225
224
  return x
226
225
 
227
226
  def _Q(x):
228
- return 1.0
227
+ return (self.r**2) / 4.0
229
228
 
230
229
  P = _P(x)
231
230
  Q = _Q(x)
@@ -256,7 +255,7 @@ class Wigner(object):
256
255
 
257
256
  # Use quadratic form
258
257
  sign = -1 if alt_branch else 1
259
- A = 1.0
258
+ A = (self.r**2) / 4.0
260
259
  B = z
261
260
  D = B**2 - 4 * A
262
261
  sqrtD = numpy.sqrt(D)
@@ -346,7 +345,7 @@ class Wigner(object):
346
345
  .. code-block:: python
347
346
 
348
347
  >>> from freealg.distributions import Wigner
349
- >>> wg = Wigner()
348
+ >>> wg = Wigner(1)
350
349
  >>> m1, m2 = wg.stieltjes(plot=True)
351
350
 
352
351
  .. image:: ../_static/images/plots/wg_stieltjes.png
@@ -437,7 +436,7 @@ class Wigner(object):
437
436
  is used.
438
437
 
439
438
  method : {``'mc'``, ``'qmc'``}, default= ``'qmc'``
440
- Method of drawing samples from uniform distirbution:
439
+ Method of drawing samples from uniform distribution:
441
440
 
442
441
  * ``'mc'``: Monte Carlo
443
442
  * ``'qmc'``: Quasi Monte Carlo
@@ -471,7 +470,7 @@ class Wigner(object):
471
470
  .. code-block::python
472
471
 
473
472
  >>> from freealg.distributions import Wigner
474
- >>> wg = Wigner()
473
+ >>> wg = Wigner(1)
475
474
  >>> s = wg.sample(2000)
476
475
 
477
476
  .. image:: ../_static/images/plots/wg_samples.png
@@ -553,15 +552,7 @@ class Wigner(object):
553
552
 
554
553
  # Parameters
555
554
  n = size
556
- p = 1.0 / size
557
-
558
- # Random graph
559
- G = nx.erdos_renyi_graph(n, p)
560
-
561
- # Adjancency
562
- A = nx.to_numpy_array(G) # shape (n,n), 0/1 entries
563
-
564
- # Center & scale to get the semicircle
565
- A_c = (A - p) / numpy.sqrt(n * p * (1-p))
555
+ X = numpy.random.randn(n, n)
556
+ X = (numpy.triu(X, 0) + numpy.triu(X, 1).T)
566
557
 
567
- return A_c
558
+ return X * (self.r / (2.0 * numpy.sqrt(n)))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: freealg
3
- Version: 0.1.7
3
+ Version: 0.1.8
4
4
  Summary: Free probability for large matrices
5
5
  Keywords: leaderboard bot chat
6
6
  Platform: Linux
@@ -30,7 +30,6 @@ Requires-Dist: scipy
30
30
  Requires-Dist: texplot
31
31
  Requires-Dist: matplotlib
32
32
  Requires-Dist: colorcet
33
- Requires-Dist: networkx
34
33
  Requires-Dist: statsmodels
35
34
  Provides-Extra: test
36
35
  Requires-Dist: tox; extra == "test"
@@ -3,7 +3,6 @@ scipy
3
3
  texplot
4
4
  matplotlib
5
5
  colorcet
6
- networkx
7
6
  statsmodels
8
7
 
9
8
  [docs]
@@ -3,5 +3,4 @@ scipy
3
3
  texplot
4
4
  matplotlib
5
5
  colorcet
6
- networkx
7
6
  statsmodels
@@ -1 +0,0 @@
1
- __version__ = "0.1.7"
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
File without changes
File without changes
File without changes
File without changes
File without changes