freealg 0.1.6__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.
- freealg-0.1.8/AUTHORS.txt +2 -0
- {freealg-0.1.6/freealg.egg-info → freealg-0.1.8}/PKG-INFO +2 -2
- freealg-0.1.8/freealg/__version__.py +1 -0
- {freealg-0.1.6 → freealg-0.1.8}/freealg/_plot_util.py +1 -37
- {freealg-0.1.6 → freealg-0.1.8}/freealg/distributions/__init__.py +2 -3
- {freealg-0.1.6 → freealg-0.1.8}/freealg/distributions/kesten_mckay.py +114 -18
- {freealg-0.1.6 → freealg-0.1.8}/freealg/distributions/marchenko_pastur.py +1 -1
- freealg-0.1.8/freealg/distributions/meixner.py +601 -0
- {freealg-0.1.6 → freealg-0.1.8}/freealg/distributions/wachter.py +12 -9
- {freealg-0.1.6 → freealg-0.1.8}/freealg/distributions/wigner.py +21 -30
- {freealg-0.1.6 → freealg-0.1.8}/freealg/freeform.py +2 -9
- {freealg-0.1.6 → freealg-0.1.8/freealg.egg-info}/PKG-INFO +2 -2
- {freealg-0.1.6 → freealg-0.1.8}/freealg.egg-info/SOURCES.txt +2 -0
- {freealg-0.1.6 → freealg-0.1.8}/freealg.egg-info/requires.txt +0 -1
- {freealg-0.1.6 → freealg-0.1.8}/requirements.txt +0 -1
- freealg-0.1.6/freealg/__version__.py +0 -1
- {freealg-0.1.6 → freealg-0.1.8}/CHANGELOG.rst +0 -0
- {freealg-0.1.6 → freealg-0.1.8}/LICENSE.txt +0 -0
- {freealg-0.1.6 → freealg-0.1.8}/MANIFEST.in +0 -0
- {freealg-0.1.6 → freealg-0.1.8}/README.rst +0 -0
- {freealg-0.1.6 → freealg-0.1.8}/freealg/__init__.py +0 -0
- {freealg-0.1.6 → freealg-0.1.8}/freealg/_chebyshev.py +0 -0
- {freealg-0.1.6 → freealg-0.1.8}/freealg/_damp.py +0 -0
- {freealg-0.1.6 → freealg-0.1.8}/freealg/_decompress.py +0 -0
- {freealg-0.1.6 → freealg-0.1.8}/freealg/_jacobi.py +0 -0
- {freealg-0.1.6 → freealg-0.1.8}/freealg/_pade.py +0 -0
- {freealg-0.1.6 → freealg-0.1.8}/freealg/_sample.py +0 -0
- {freealg-0.1.6 → freealg-0.1.8}/freealg/_util.py +0 -0
- {freealg-0.1.6 → freealg-0.1.8}/freealg.egg-info/dependency_links.txt +0 -0
- {freealg-0.1.6 → freealg-0.1.8}/freealg.egg-info/not-zip-safe +0 -0
- {freealg-0.1.6 → freealg-0.1.8}/freealg.egg-info/top_level.txt +0 -0
- {freealg-0.1.6 → freealg-0.1.8}/pyproject.toml +0 -0
- {freealg-0.1.6 → freealg-0.1.8}/setup.cfg +0 -0
- {freealg-0.1.6 → 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.
|
|
3
|
+
Version: 0.1.8
|
|
4
4
|
Summary: Free probability for large matrices
|
|
5
5
|
Keywords: leaderboard bot chat
|
|
6
6
|
Platform: Linux
|
|
@@ -24,12 +24,12 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
|
24
24
|
Requires-Python: >=3.9
|
|
25
25
|
Description-Content-Type: text/x-rst
|
|
26
26
|
License-File: LICENSE.txt
|
|
27
|
+
License-File: AUTHORS.txt
|
|
27
28
|
Requires-Dist: numpy
|
|
28
29
|
Requires-Dist: scipy
|
|
29
30
|
Requires-Dist: texplot
|
|
30
31
|
Requires-Dist: matplotlib
|
|
31
32
|
Requires-Dist: colorcet
|
|
32
|
-
Requires-Dist: networkx
|
|
33
33
|
Requires-Dist: statsmodels
|
|
34
34
|
Provides-Extra: test
|
|
35
35
|
Requires-Dist: tox; extra == "test"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.8"
|
|
@@ -18,7 +18,6 @@ import matplotlib
|
|
|
18
18
|
import colorsys
|
|
19
19
|
import matplotlib.ticker as ticker
|
|
20
20
|
import matplotlib.gridspec as gridspec
|
|
21
|
-
from ._decompress import reverse_characteristics
|
|
22
21
|
|
|
23
22
|
__all__ = ['plot_fit', 'plot_density', 'plot_hilbert', 'plot_stieltjes',
|
|
24
23
|
'plot_stieltjes_on_disk']
|
|
@@ -244,42 +243,11 @@ def _value_formatter(v, pos):
|
|
|
244
243
|
return f"{m_val:.1f}"
|
|
245
244
|
|
|
246
245
|
|
|
247
|
-
# ================
|
|
248
|
-
# plot char curves
|
|
249
|
-
# ================
|
|
250
|
-
def _plot_char_curves(ax, char_curves):
|
|
251
|
-
"""
|
|
252
|
-
"""
|
|
253
|
-
|
|
254
|
-
curves = reverse_characteristics(char_curves['matrix'],
|
|
255
|
-
char_curves['z'], 4)
|
|
256
|
-
lw = 2
|
|
257
|
-
for idx in range(curves.shape[1]):
|
|
258
|
-
|
|
259
|
-
creal, cimag = curves[:, idx].real, curves[:, idx].imag
|
|
260
|
-
|
|
261
|
-
ax.plot(creal, cimag, ':', color='white', linewidth=lw,
|
|
262
|
-
alpha=0.75)
|
|
263
|
-
|
|
264
|
-
ax.annotate(
|
|
265
|
-
'', # no text
|
|
266
|
-
xy=(creal[-1], cimag[-1]), # arrow tip at final point
|
|
267
|
-
xytext=(creal[-2], cimag[-2]), # tail at penultimate point
|
|
268
|
-
arrowprops=dict(
|
|
269
|
-
arrowstyle='-|>', # simple arrow head
|
|
270
|
-
mutation_scale=5, # size of the head
|
|
271
|
-
color='white',
|
|
272
|
-
lw=lw, alpha=0.75, # arrow shaft line width
|
|
273
|
-
)
|
|
274
|
-
)
|
|
275
|
-
|
|
276
|
-
|
|
277
246
|
# ==============
|
|
278
247
|
# plot stieltjes
|
|
279
248
|
# ==============
|
|
280
249
|
|
|
281
|
-
def plot_stieltjes(x, y, m1, m2, support, latex=False,
|
|
282
|
-
save=False):
|
|
250
|
+
def plot_stieltjes(x, y, m1, m2, support, latex=False, save=False):
|
|
283
251
|
"""
|
|
284
252
|
"""
|
|
285
253
|
|
|
@@ -329,10 +297,6 @@ def plot_stieltjes(x, y, m1, m2, support, latex=False, char_curves=None,
|
|
|
329
297
|
ax1.set_xlim([x_min, x_max])
|
|
330
298
|
ax1.set_ylim([y_min, y_max])
|
|
331
299
|
|
|
332
|
-
# Plot characteristic curves
|
|
333
|
-
if char_curves is not None:
|
|
334
|
-
_plot_char_curves(ax1, char_curves)
|
|
335
|
-
|
|
336
300
|
pos = ax1.get_position()
|
|
337
301
|
cbar_width = 0.013
|
|
338
302
|
pad = 0.013
|
|
@@ -10,7 +10,6 @@ from .marchenko_pastur import MarchenkoPastur
|
|
|
10
10
|
from .wigner import Wigner
|
|
11
11
|
from .kesten_mckay import KestenMcKay
|
|
12
12
|
from .wachter import Wachter
|
|
13
|
-
|
|
13
|
+
from .meixner import Meixner
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
__all__ = ['MarchenkoPastur', 'Wigner', 'KestenMcKay', 'Wachter']
|
|
15
|
+
__all__ = ['MarchenkoPastur', 'Wigner', 'KestenMcKay', 'Wachter', 'Meixner']
|
|
@@ -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
|
|
@@ -61,7 +60,7 @@ class KestenMcKay(object):
|
|
|
61
60
|
Notes
|
|
62
61
|
-----
|
|
63
62
|
|
|
64
|
-
The
|
|
63
|
+
The Kesten-McKay distribution has the absolutely-continuous density
|
|
65
64
|
|
|
66
65
|
.. math::
|
|
67
66
|
|
|
@@ -236,10 +235,10 @@ class KestenMcKay(object):
|
|
|
236
235
|
x = numpy.linspace(x_min, x_max, 500)
|
|
237
236
|
|
|
238
237
|
def _P(x):
|
|
239
|
-
return (self.d - 2.0) * x
|
|
238
|
+
return (self.d - 2.0) * x / (self.d - 1.0)
|
|
240
239
|
|
|
241
240
|
def _Q(x):
|
|
242
|
-
return self.d**2 - x**2
|
|
241
|
+
return (self.d**2 - x**2) / (self.d - 1.0)
|
|
243
242
|
|
|
244
243
|
P = _P(x)
|
|
245
244
|
Q = _Q(x)
|
|
@@ -262,15 +261,15 @@ class KestenMcKay(object):
|
|
|
262
261
|
|
|
263
262
|
def _m_mp_numeric_vectorized(self, z, alt_branch=False, tol=1e-8):
|
|
264
263
|
"""
|
|
265
|
-
Stieltjes transform (principal or secondary branch)
|
|
266
|
-
|
|
264
|
+
Stieltjes transform (principal or secondary branch) for Kesten-McKay
|
|
265
|
+
distribution on upper half-plane.
|
|
267
266
|
"""
|
|
268
267
|
|
|
269
268
|
m = numpy.empty_like(z, dtype=complex)
|
|
270
269
|
|
|
271
270
|
sign = -1 if alt_branch else 1
|
|
272
|
-
A = self.d**2 - z**2
|
|
273
|
-
B = (self.d - 2.0) * z
|
|
271
|
+
A = (self.d**2 - z**2) / (self.d - 1.0)
|
|
272
|
+
B = ((self.d - 2.0) * z) / (self.d - 1.0)
|
|
274
273
|
D = B**2 - 4 * A
|
|
275
274
|
sqrtD = numpy.sqrt(D)
|
|
276
275
|
m1 = (-B + sqrtD) / (2 * A)
|
|
@@ -370,7 +369,7 @@ class KestenMcKay(object):
|
|
|
370
369
|
|
|
371
370
|
.. code-block:: python
|
|
372
371
|
|
|
373
|
-
>>> m1, m2 =
|
|
372
|
+
>>> m1, m2 = km.stieltjes(plot=True, on_disk=True)
|
|
374
373
|
|
|
375
374
|
.. image:: ../_static/images/plots/km_stieltjes_disk.png
|
|
376
375
|
:align: center
|
|
@@ -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
|
|
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
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
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
|
|
459
|
+
Method of drawing samples from uniform distribution:
|
|
460
460
|
|
|
461
461
|
* ``'mc'``: Monte Carlo
|
|
462
462
|
* ``'qmc'``: Quasi Monte Carlo
|
|
@@ -0,0 +1,601 @@
|
|
|
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
|
|
6
|
+
# under the terms of the license found in the LICENSE.txt file in the root
|
|
7
|
+
# directory of this source tree.
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# =======
|
|
11
|
+
# Imports
|
|
12
|
+
# =======
|
|
13
|
+
|
|
14
|
+
import numpy
|
|
15
|
+
from scipy.interpolate import interp1d
|
|
16
|
+
from .._plot_util import plot_density, plot_hilbert, plot_stieltjes, \
|
|
17
|
+
plot_stieltjes_on_disk, plot_samples
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
from scipy.integrate import cumtrapz
|
|
21
|
+
except ImportError:
|
|
22
|
+
from scipy.integrate import cumulative_trapezoid as cumtrapz
|
|
23
|
+
from scipy.stats import qmc
|
|
24
|
+
|
|
25
|
+
__all__ = ['Meixner']
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# =======
|
|
29
|
+
# Meixner
|
|
30
|
+
# =======
|
|
31
|
+
|
|
32
|
+
class Meixner(object):
|
|
33
|
+
"""
|
|
34
|
+
Meixner distribution.
|
|
35
|
+
|
|
36
|
+
Parameters
|
|
37
|
+
----------
|
|
38
|
+
|
|
39
|
+
a : float
|
|
40
|
+
Parameter :math:`a` of the distribution. See Notes.
|
|
41
|
+
|
|
42
|
+
b : float
|
|
43
|
+
Parameter :math:`b` of the distribution. See Notes.
|
|
44
|
+
|
|
45
|
+
Methods
|
|
46
|
+
-------
|
|
47
|
+
|
|
48
|
+
density
|
|
49
|
+
Spectral density of distribution.
|
|
50
|
+
|
|
51
|
+
hilbert
|
|
52
|
+
Hilbert transform of distribution.
|
|
53
|
+
|
|
54
|
+
stieltjes
|
|
55
|
+
Stieltjes transform of distribution.
|
|
56
|
+
|
|
57
|
+
sample
|
|
58
|
+
Sample from distribution.
|
|
59
|
+
|
|
60
|
+
matrix
|
|
61
|
+
Generate matrix with its empirical spectral density of distribution
|
|
62
|
+
|
|
63
|
+
Notes
|
|
64
|
+
-----
|
|
65
|
+
|
|
66
|
+
The Meixner distribution has the absolutely-continuous density
|
|
67
|
+
|
|
68
|
+
.. math::
|
|
69
|
+
|
|
70
|
+
\\mathrm{d} \\rho(x) =
|
|
71
|
+
\\frac{4(1+b) - (x-a)^2}{2 \\pi (b x^2 + a x + 1)}
|
|
72
|
+
\\mathbf{1}_{x \\in [\\lambda_{-}, \\lambda_{+}]} \\mathrm{d}{x}
|
|
73
|
+
|
|
74
|
+
where :math:`a, b` are the shape parameters of the distribution. The edges
|
|
75
|
+
of the support are
|
|
76
|
+
|
|
77
|
+
.. math::
|
|
78
|
+
|
|
79
|
+
\\lambda_{\\pm} = a \\pm 2 \\sqrt{1 + b}.
|
|
80
|
+
|
|
81
|
+
References
|
|
82
|
+
----------
|
|
83
|
+
|
|
84
|
+
.. [1] Saitoh, N. & Yosnida, M. (2001). The infinite divisibility and
|
|
85
|
+
orthogonal polynomials with a constant recursion formula in free
|
|
86
|
+
probability theory. Probab. Math. Statist., 21, 159–170.
|
|
87
|
+
|
|
88
|
+
Examples
|
|
89
|
+
--------
|
|
90
|
+
|
|
91
|
+
.. code-block:: python
|
|
92
|
+
|
|
93
|
+
>>> from freealg.distributions import Meixner
|
|
94
|
+
>>> mx = Meixner(2, 3)
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
# ====
|
|
98
|
+
# init
|
|
99
|
+
# ====
|
|
100
|
+
|
|
101
|
+
def __init__(self, a, b, c):
|
|
102
|
+
"""
|
|
103
|
+
Initialization.
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
self.a = a
|
|
107
|
+
self.b = 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)
|
|
111
|
+
self.support = (self.lam_m, self.lam_p)
|
|
112
|
+
|
|
113
|
+
# =======
|
|
114
|
+
# density
|
|
115
|
+
# =======
|
|
116
|
+
|
|
117
|
+
def density(self, x=None, plot=False, latex=False, save=False):
|
|
118
|
+
"""
|
|
119
|
+
Density of distribution.
|
|
120
|
+
|
|
121
|
+
Parameters
|
|
122
|
+
----------
|
|
123
|
+
|
|
124
|
+
x : numpy.array, default=None
|
|
125
|
+
The locations where density is evaluated at. If `None`, an interval
|
|
126
|
+
slightly larger than the support interval of the spectral density
|
|
127
|
+
is used.
|
|
128
|
+
|
|
129
|
+
rho : numpy.array, default=None
|
|
130
|
+
Density. If `None`, it will be computed.
|
|
131
|
+
|
|
132
|
+
plot : bool, default=False
|
|
133
|
+
If `True`, density is plotted.
|
|
134
|
+
|
|
135
|
+
latex : bool, default=False
|
|
136
|
+
If `True`, the plot is rendered using LaTeX. This option is
|
|
137
|
+
relevant only if ``plot=True``.
|
|
138
|
+
|
|
139
|
+
save : bool, default=False
|
|
140
|
+
If not `False`, the plot is saved. If a string is given, it is
|
|
141
|
+
assumed to the save filename (with the file extension). This option
|
|
142
|
+
is relevant only if ``plot=True``.
|
|
143
|
+
|
|
144
|
+
Returns
|
|
145
|
+
-------
|
|
146
|
+
|
|
147
|
+
rho : numpy.array
|
|
148
|
+
Density.
|
|
149
|
+
|
|
150
|
+
Examples
|
|
151
|
+
--------
|
|
152
|
+
|
|
153
|
+
.. code-block::python
|
|
154
|
+
|
|
155
|
+
>>> from freealg.distributions import Meixner
|
|
156
|
+
>>> mx = Meixner(2, 3)
|
|
157
|
+
>>> rho = mx.density(plot=True)
|
|
158
|
+
|
|
159
|
+
.. image:: ../_static/images/plots/mx_density.png
|
|
160
|
+
:align: center
|
|
161
|
+
:class: custom-dark
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
# Create x if not given
|
|
165
|
+
if x is None:
|
|
166
|
+
radius = 0.5 * (self.lam_p - self.lam_m)
|
|
167
|
+
center = 0.5 * (self.lam_p + self.lam_m)
|
|
168
|
+
scale = 1.25
|
|
169
|
+
x_min = numpy.floor(center - radius * scale)
|
|
170
|
+
x_max = numpy.ceil(center + radius * scale)
|
|
171
|
+
x = numpy.linspace(x_min, x_max, 500)
|
|
172
|
+
|
|
173
|
+
rho = numpy.zeros_like(x)
|
|
174
|
+
mask = numpy.logical_and(x > self.lam_m, x < self.lam_p)
|
|
175
|
+
|
|
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]
|
|
189
|
+
|
|
190
|
+
if plot:
|
|
191
|
+
plot_density(x, rho, label='', latex=latex, save=save)
|
|
192
|
+
|
|
193
|
+
return rho
|
|
194
|
+
|
|
195
|
+
# =======
|
|
196
|
+
# hilbert
|
|
197
|
+
# =======
|
|
198
|
+
|
|
199
|
+
def hilbert(self, x=None, plot=False, latex=False, save=False):
|
|
200
|
+
"""
|
|
201
|
+
Hilbert transform of the distribution.
|
|
202
|
+
|
|
203
|
+
Parameters
|
|
204
|
+
----------
|
|
205
|
+
|
|
206
|
+
x : numpy.array, default=None
|
|
207
|
+
The locations where Hilbert transform is evaluated at. If `None`,
|
|
208
|
+
an interval slightly larger than the support interval of the
|
|
209
|
+
spectral density is used.
|
|
210
|
+
|
|
211
|
+
plot : bool, default=False
|
|
212
|
+
If `True`, Hilbert transform is plotted.
|
|
213
|
+
|
|
214
|
+
latex : bool, default=False
|
|
215
|
+
If `True`, the plot is rendered using LaTeX. This option is
|
|
216
|
+
relevant only if ``plot=True``.
|
|
217
|
+
|
|
218
|
+
save : bool, default=False
|
|
219
|
+
If not `False`, the plot is saved. If a string is given, it is
|
|
220
|
+
assumed to the save filename (with the file extension). This option
|
|
221
|
+
is relevant only if ``plot=True``.
|
|
222
|
+
|
|
223
|
+
Returns
|
|
224
|
+
-------
|
|
225
|
+
|
|
226
|
+
hilb : numpy.array
|
|
227
|
+
Hilbert transform.
|
|
228
|
+
|
|
229
|
+
Examples
|
|
230
|
+
--------
|
|
231
|
+
|
|
232
|
+
.. code-block::python
|
|
233
|
+
|
|
234
|
+
>>> from freealg.distributions import Meixner
|
|
235
|
+
>>> mx = Meixner(2, 3)
|
|
236
|
+
>>> hilb = mx.hilbert(plot=True)
|
|
237
|
+
|
|
238
|
+
.. image:: ../_static/images/plots/mx_hilbert.png
|
|
239
|
+
:align: center
|
|
240
|
+
:class: custom-dark
|
|
241
|
+
"""
|
|
242
|
+
|
|
243
|
+
# Create x if not given
|
|
244
|
+
if x is None:
|
|
245
|
+
radius = 0.5 * (self.lam_p - self.lam_m)
|
|
246
|
+
center = 0.5 * (self.lam_p + self.lam_m)
|
|
247
|
+
scale = 1.25
|
|
248
|
+
x_min = numpy.floor(center - radius * scale)
|
|
249
|
+
x_max = numpy.ceil(center + radius * scale)
|
|
250
|
+
x = numpy.linspace(x_min, x_max, 500)
|
|
251
|
+
|
|
252
|
+
def _P(x):
|
|
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
|
|
257
|
+
|
|
258
|
+
def _Q(x):
|
|
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
|
|
264
|
+
|
|
265
|
+
P = _P(x)
|
|
266
|
+
Q = _Q(x)
|
|
267
|
+
Delta2 = P**2 - 4.0 * Q
|
|
268
|
+
Delta = numpy.sqrt(numpy.maximum(Delta2, 0))
|
|
269
|
+
sign = numpy.sign(P)
|
|
270
|
+
hilb = (P - sign * Delta) / (2.0 * Q)
|
|
271
|
+
|
|
272
|
+
# using negative sign convention
|
|
273
|
+
hilb = -hilb
|
|
274
|
+
|
|
275
|
+
if plot:
|
|
276
|
+
plot_hilbert(x, hilb, support=self.support, latex=latex, save=save)
|
|
277
|
+
|
|
278
|
+
return hilb
|
|
279
|
+
|
|
280
|
+
# =======================
|
|
281
|
+
# m mp numeric vectorized
|
|
282
|
+
# =======================
|
|
283
|
+
|
|
284
|
+
def _m_mp_numeric_vectorized(self, z, alt_branch=False, tol=1e-8):
|
|
285
|
+
"""
|
|
286
|
+
Stieltjes transform (principal or secondary branch) for Meixner
|
|
287
|
+
distribution on upper half-plane.
|
|
288
|
+
"""
|
|
289
|
+
|
|
290
|
+
sign = -1 if alt_branch else 1
|
|
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
|
+
|
|
307
|
+
m1 = (-B + sqrtD) / (2 * A)
|
|
308
|
+
m2 = (-B - sqrtD) / (2 * A)
|
|
309
|
+
|
|
310
|
+
# pick correct branch only for non‑masked entries
|
|
311
|
+
upper = z.imag >= 0
|
|
312
|
+
branch = numpy.empty_like(m1)
|
|
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])
|
|
317
|
+
m = branch
|
|
318
|
+
|
|
319
|
+
return m
|
|
320
|
+
|
|
321
|
+
# ============
|
|
322
|
+
# m mp reflect
|
|
323
|
+
# ============
|
|
324
|
+
|
|
325
|
+
def _m_mp_reflect(self, z, alt_branch=False):
|
|
326
|
+
"""
|
|
327
|
+
Analytic continuation using Schwarz reflection.
|
|
328
|
+
"""
|
|
329
|
+
|
|
330
|
+
mask_p = z.imag >= 0.0
|
|
331
|
+
mask_n = z.imag < 0.0
|
|
332
|
+
|
|
333
|
+
m = numpy.zeros_like(z)
|
|
334
|
+
|
|
335
|
+
f = self._m_mp_numeric_vectorized
|
|
336
|
+
m[mask_p] = f(z[mask_p], alt_branch=False)
|
|
337
|
+
m[mask_n] = f(z[mask_n], alt_branch=alt_branch)
|
|
338
|
+
|
|
339
|
+
return m
|
|
340
|
+
|
|
341
|
+
# =========
|
|
342
|
+
# stieltjes
|
|
343
|
+
# =========
|
|
344
|
+
|
|
345
|
+
def stieltjes(self, x=None, y=None, plot=False, on_disk=False, latex=False,
|
|
346
|
+
save=False):
|
|
347
|
+
"""
|
|
348
|
+
Stieltjes transform of distribution.
|
|
349
|
+
|
|
350
|
+
Parameters
|
|
351
|
+
----------
|
|
352
|
+
|
|
353
|
+
x : numpy.array, default=None
|
|
354
|
+
The x axis of the grid where the Stieltjes transform is evaluated.
|
|
355
|
+
If `None`, an interval slightly larger than the support interval of
|
|
356
|
+
the spectral density is used.
|
|
357
|
+
|
|
358
|
+
y : numpy.array, default=None
|
|
359
|
+
The y axis of the grid where the Stieltjes transform is evaluated.
|
|
360
|
+
If `None`, a grid on the interval ``[-1, 1]`` is used.
|
|
361
|
+
|
|
362
|
+
plot : bool, default=False
|
|
363
|
+
If `True`, Stieltjes transform is plotted.
|
|
364
|
+
|
|
365
|
+
on_disk : bool, default=False
|
|
366
|
+
If `True`, the Stieltjes transform is mapped on unit disk. This
|
|
367
|
+
option relevant only if ``plot=True``.
|
|
368
|
+
|
|
369
|
+
latex : bool, default=False
|
|
370
|
+
If `True`, the plot is rendered using LaTeX. This option is
|
|
371
|
+
relevant only if ``plot=True``.
|
|
372
|
+
|
|
373
|
+
save : bool, default=False
|
|
374
|
+
If not `False`, the plot is saved. If a string is given, it is
|
|
375
|
+
assumed to the save filename (with the file extension). This option
|
|
376
|
+
is relevant only if ``plot=True``.
|
|
377
|
+
|
|
378
|
+
Returns
|
|
379
|
+
-------
|
|
380
|
+
|
|
381
|
+
m1 : numpy.array
|
|
382
|
+
Stieltjes transform on principal branch.
|
|
383
|
+
|
|
384
|
+
m12 : numpy.array
|
|
385
|
+
Stieltjes transform on secondary branch.
|
|
386
|
+
|
|
387
|
+
Examples
|
|
388
|
+
--------
|
|
389
|
+
|
|
390
|
+
.. code-block:: python
|
|
391
|
+
|
|
392
|
+
>>> from freealg.distributions import Meixner
|
|
393
|
+
>>> mx = Meixner(2, 3)
|
|
394
|
+
>>> m1, m2 = mx.stieltjes(plot=True)
|
|
395
|
+
|
|
396
|
+
.. image:: ../_static/images/plots/mx_stieltjes.png
|
|
397
|
+
:align: center
|
|
398
|
+
:class: custom-dark
|
|
399
|
+
|
|
400
|
+
Plot on unit disk using Cayley transform:
|
|
401
|
+
|
|
402
|
+
.. code-block:: python
|
|
403
|
+
|
|
404
|
+
>>> m1, m2 = mx.stieltjes(plot=True, on_disk=True)
|
|
405
|
+
|
|
406
|
+
.. image:: ../_static/images/plots/mx_stieltjes_disk.png
|
|
407
|
+
:align: center
|
|
408
|
+
:class: custom-dark
|
|
409
|
+
"""
|
|
410
|
+
|
|
411
|
+
if (plot is True) and (on_disk is True):
|
|
412
|
+
n_r = 1000
|
|
413
|
+
n_t = 1000
|
|
414
|
+
r_min, r_max = 0, 2.5
|
|
415
|
+
t_min, t_max = 0, 2.0 * numpy.pi
|
|
416
|
+
r = numpy.linspace(r_min, r_max, n_r)
|
|
417
|
+
t = numpy.linspace(t_min, t_max, n_t + 1)[:-1]
|
|
418
|
+
grid_r, grid_t = numpy.meshgrid(r, t)
|
|
419
|
+
|
|
420
|
+
grid_x_D = grid_r * numpy.cos(grid_t)
|
|
421
|
+
grid_y_D = grid_r * numpy.sin(grid_t)
|
|
422
|
+
zeta = grid_x_D + 1j * grid_y_D
|
|
423
|
+
|
|
424
|
+
# Cayley transform mapping zeta on D to z on H
|
|
425
|
+
z_H = 1j * (1 + zeta) / (1 - zeta)
|
|
426
|
+
|
|
427
|
+
m1_D = self._m_mp_reflect(z_H, alt_branch=False)
|
|
428
|
+
m2_D = self._m_mp_reflect(z_H, alt_branch=True)
|
|
429
|
+
|
|
430
|
+
plot_stieltjes_on_disk(r, t, m1_D, m2_D, support=self.support,
|
|
431
|
+
latex=latex, save=save)
|
|
432
|
+
|
|
433
|
+
return m1_D, m2_D
|
|
434
|
+
|
|
435
|
+
# Create x if not given
|
|
436
|
+
if x is None:
|
|
437
|
+
radius = 0.5 * (self.lam_p - self.lam_m)
|
|
438
|
+
center = 0.5 * (self.lam_p + self.lam_m)
|
|
439
|
+
scale = 2.0
|
|
440
|
+
x_min = numpy.floor(2.0 * (center - 2.0 * radius * scale)) / 2.0
|
|
441
|
+
x_max = numpy.ceil(2.0 * (center + 2.0 * radius * scale)) / 2.0
|
|
442
|
+
x = numpy.linspace(x_min, x_max, 500)
|
|
443
|
+
|
|
444
|
+
# Create y if not given
|
|
445
|
+
if y is None:
|
|
446
|
+
y = numpy.linspace(-1, 1, 400)
|
|
447
|
+
|
|
448
|
+
x_grid, y_grid = numpy.meshgrid(x, y)
|
|
449
|
+
z = x_grid + 1j * y_grid # shape (Ny, Nx)
|
|
450
|
+
|
|
451
|
+
m1 = self._m_mp_reflect(z, alt_branch=False)
|
|
452
|
+
m2 = self._m_mp_reflect(z, alt_branch=True)
|
|
453
|
+
|
|
454
|
+
if plot:
|
|
455
|
+
plot_stieltjes(x, y, m1, m2, support=self.support, latex=latex,
|
|
456
|
+
save=save)
|
|
457
|
+
|
|
458
|
+
return m1, m2
|
|
459
|
+
|
|
460
|
+
# ======
|
|
461
|
+
# sample
|
|
462
|
+
# ======
|
|
463
|
+
|
|
464
|
+
def sample(self, size, x_min=None, x_max=None, method='qmc', plot=False,
|
|
465
|
+
latex=False, save=False):
|
|
466
|
+
"""
|
|
467
|
+
Sample from distribution.
|
|
468
|
+
|
|
469
|
+
Parameters
|
|
470
|
+
----------
|
|
471
|
+
|
|
472
|
+
size : int
|
|
473
|
+
Size of sample.
|
|
474
|
+
|
|
475
|
+
x_min : float, default=None
|
|
476
|
+
Minimum of sample values. If `None`, the left edge of the support
|
|
477
|
+
is used.
|
|
478
|
+
|
|
479
|
+
x_max : float, default=None
|
|
480
|
+
Maximum of sample values. If `None`, the right edge of the support
|
|
481
|
+
is used.
|
|
482
|
+
|
|
483
|
+
method : {``'mc'``, ``'qmc'``}, default= ``'qmc'``
|
|
484
|
+
Method of drawing samples from uniform distribution:
|
|
485
|
+
|
|
486
|
+
* ``'mc'``: Monte Carlo
|
|
487
|
+
* ``'qmc'``: Quasi Monte Carlo
|
|
488
|
+
|
|
489
|
+
plot : bool, default=False
|
|
490
|
+
If `True`, samples histogram is plotted.
|
|
491
|
+
|
|
492
|
+
latex : bool, default=False
|
|
493
|
+
If `True`, the plot is rendered using LaTeX. This option is
|
|
494
|
+
relevant only if ``plot=True``.
|
|
495
|
+
|
|
496
|
+
save : bool, default=False
|
|
497
|
+
If not `False`, the plot is saved. If a string is given, it is
|
|
498
|
+
assumed to the save filename (with the file extension). This option
|
|
499
|
+
is relevant only if ``plot=True``.
|
|
500
|
+
|
|
501
|
+
Returns
|
|
502
|
+
-------
|
|
503
|
+
|
|
504
|
+
s : numpy.ndarray
|
|
505
|
+
Samples.
|
|
506
|
+
|
|
507
|
+
Notes
|
|
508
|
+
-----
|
|
509
|
+
|
|
510
|
+
This method uses inverse transform sampling.
|
|
511
|
+
|
|
512
|
+
Examples
|
|
513
|
+
--------
|
|
514
|
+
|
|
515
|
+
.. code-block::python
|
|
516
|
+
|
|
517
|
+
>>> from freealg.distributions import Meixner
|
|
518
|
+
>>> mx = Meixner(2, 3)
|
|
519
|
+
>>> s = mx.sample(2000)
|
|
520
|
+
|
|
521
|
+
.. image:: ../_static/images/plots/mx_samples.png
|
|
522
|
+
:align: center
|
|
523
|
+
:class: custom-dark
|
|
524
|
+
"""
|
|
525
|
+
|
|
526
|
+
if x_min is None:
|
|
527
|
+
x_min = self.lam_m
|
|
528
|
+
|
|
529
|
+
if x_max is None:
|
|
530
|
+
x_max = self.lam_p
|
|
531
|
+
|
|
532
|
+
# Grid and PDF
|
|
533
|
+
xs = numpy.linspace(x_min, x_max, size)
|
|
534
|
+
pdf = self.density(xs)
|
|
535
|
+
|
|
536
|
+
# CDF (using cumulative trapezoidal rule)
|
|
537
|
+
cdf = cumtrapz(pdf, xs, initial=0)
|
|
538
|
+
cdf /= cdf[-1] # normalize CDF to 1
|
|
539
|
+
|
|
540
|
+
# Inverse CDF interpolator
|
|
541
|
+
inv_cdf = interp1d(cdf, xs, bounds_error=False,
|
|
542
|
+
fill_value=(x_min, x_max))
|
|
543
|
+
|
|
544
|
+
# Draw from uniform distribution
|
|
545
|
+
if method == 'mc':
|
|
546
|
+
u = numpy.random.rand(size)
|
|
547
|
+
elif method == 'qmc':
|
|
548
|
+
engine = qmc.Halton(d=1)
|
|
549
|
+
u = engine.random(size)
|
|
550
|
+
else:
|
|
551
|
+
raise ValueError('"method" is invalid.')
|
|
552
|
+
|
|
553
|
+
# Draw from distribution by mapping from inverse CDF
|
|
554
|
+
samples = inv_cdf(u).ravel()
|
|
555
|
+
|
|
556
|
+
if plot:
|
|
557
|
+
radius = 0.5 * (self.lam_p - self.lam_m)
|
|
558
|
+
center = 0.5 * (self.lam_p + self.lam_m)
|
|
559
|
+
scale = 1.25
|
|
560
|
+
x_min = numpy.floor(center - radius * scale)
|
|
561
|
+
x_max = numpy.ceil(center + radius * scale)
|
|
562
|
+
x = numpy.linspace(x_min, x_max, 500)
|
|
563
|
+
rho = self.density(x)
|
|
564
|
+
plot_samples(x, rho, x_min, x_max, samples, latex=latex, save=save)
|
|
565
|
+
|
|
566
|
+
return samples
|
|
567
|
+
|
|
568
|
+
# ======
|
|
569
|
+
# matrix
|
|
570
|
+
# ======
|
|
571
|
+
|
|
572
|
+
def matrix(self, size):
|
|
573
|
+
"""
|
|
574
|
+
Generate matrix with the spectral density of the distribution.
|
|
575
|
+
|
|
576
|
+
Parameters
|
|
577
|
+
----------
|
|
578
|
+
|
|
579
|
+
size : int
|
|
580
|
+
Size :math:`n` of the matrix.
|
|
581
|
+
|
|
582
|
+
Returns
|
|
583
|
+
-------
|
|
584
|
+
|
|
585
|
+
Sx : numpy.ndarray
|
|
586
|
+
A matrix of the size :math:`n \\times n`.
|
|
587
|
+
|
|
588
|
+
Sy : numpy.ndarray
|
|
589
|
+
A matrix of the size :math:`n \\times n`.
|
|
590
|
+
|
|
591
|
+
Examples
|
|
592
|
+
--------
|
|
593
|
+
|
|
594
|
+
.. code-block::python
|
|
595
|
+
|
|
596
|
+
>>> from freealg.distributions import Meixner
|
|
597
|
+
>>> mx = Meixner(2, 3)
|
|
598
|
+
>>> A = mx.matrix(2000)
|
|
599
|
+
"""
|
|
600
|
+
|
|
601
|
+
raise NotImplementedError
|
|
@@ -63,7 +63,7 @@ class Wachter(object):
|
|
|
63
63
|
Notes
|
|
64
64
|
-----
|
|
65
65
|
|
|
66
|
-
The
|
|
66
|
+
The Wachter distribution has the absolutely-continuous density
|
|
67
67
|
|
|
68
68
|
.. math::
|
|
69
69
|
|
|
@@ -241,10 +241,12 @@ class Wachter(object):
|
|
|
241
241
|
x = numpy.linspace(x_min, x_max, 500)
|
|
242
242
|
|
|
243
243
|
def _P(x):
|
|
244
|
-
|
|
244
|
+
denom = self.a + self.b - 1.0
|
|
245
|
+
return (1.0 - self.a + (self.a + self.b - 2.0) * x) / denom
|
|
245
246
|
|
|
246
247
|
def _Q(x):
|
|
247
|
-
|
|
248
|
+
denom = self.a + self.b - 1.0
|
|
249
|
+
return x * (1.0 - x) / denom
|
|
248
250
|
|
|
249
251
|
P = _P(x)
|
|
250
252
|
Q = _Q(x)
|
|
@@ -267,13 +269,14 @@ class Wachter(object):
|
|
|
267
269
|
|
|
268
270
|
def _m_mp_numeric_vectorized(self, z, alt_branch=False, tol=1e-8):
|
|
269
271
|
"""
|
|
270
|
-
Stieltjes transform (principal or secondary branch)
|
|
271
|
-
|
|
272
|
+
Stieltjes transform (principal or secondary branch) for Wachter
|
|
273
|
+
distribution on upper half-plane.
|
|
272
274
|
"""
|
|
273
275
|
|
|
274
276
|
sign = -1 if alt_branch else 1
|
|
275
|
-
|
|
276
|
-
|
|
277
|
+
denom = self.a + self.b - 1.0
|
|
278
|
+
A = (z * (1.0 - z)) / denom
|
|
279
|
+
B = (1.0 - self.a + (self.a + self.b - 2.0) * z) / denom
|
|
277
280
|
D = B**2 - 4 * A
|
|
278
281
|
sqrtD = numpy.sqrt(D)
|
|
279
282
|
m1 = (-B + sqrtD) / (2 * A)
|
|
@@ -373,7 +376,7 @@ class Wachter(object):
|
|
|
373
376
|
|
|
374
377
|
.. code-block:: python
|
|
375
378
|
|
|
376
|
-
>>> m1, m2 =
|
|
379
|
+
>>> m1, m2 = wa.stieltjes(plot=True, on_disk=True)
|
|
377
380
|
|
|
378
381
|
.. image:: ../_static/images/plots/wa_stieltjes_disk.png
|
|
379
382
|
:align: center
|
|
@@ -453,7 +456,7 @@ class Wachter(object):
|
|
|
453
456
|
is used.
|
|
454
457
|
|
|
455
458
|
method : {``'mc'``, ``'qmc'``}, default= ``'qmc'``
|
|
456
|
-
Method of drawing samples from uniform
|
|
459
|
+
Method of drawing samples from uniform distribution:
|
|
457
460
|
|
|
458
461
|
* ``'mc'``: Monte Carlo
|
|
459
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
|
|
@@ -55,7 +54,7 @@ class Wigner(object):
|
|
|
55
54
|
Notes
|
|
56
55
|
-----
|
|
57
56
|
|
|
58
|
-
The
|
|
57
|
+
The Wigner distribution has the absolutely-continuous density
|
|
59
58
|
|
|
60
59
|
.. math::
|
|
61
60
|
|
|
@@ -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 =
|
|
93
|
-
self.lam_m = -
|
|
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] = (
|
|
160
|
-
numpy.sqrt(
|
|
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
|
|
227
|
+
return (self.r**2) / 4.0
|
|
229
228
|
|
|
230
229
|
P = _P(x)
|
|
231
230
|
Q = _Q(x)
|
|
@@ -248,15 +247,15 @@ class Wigner(object):
|
|
|
248
247
|
|
|
249
248
|
def _m_mp_numeric_vectorized(self, z, alt_branch=False, tol=1e-8):
|
|
250
249
|
"""
|
|
251
|
-
Stieltjes transform (principal or secondary branch)
|
|
252
|
-
|
|
250
|
+
Stieltjes transform (principal or secondary branch) for Wigner
|
|
251
|
+
distribution on upper half-plane.
|
|
253
252
|
"""
|
|
254
253
|
|
|
255
254
|
m = numpy.empty_like(z, dtype=complex)
|
|
256
255
|
|
|
257
256
|
# Use quadratic form
|
|
258
257
|
sign = -1 if alt_branch else 1
|
|
259
|
-
A =
|
|
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
|
|
@@ -357,7 +356,7 @@ class Wigner(object):
|
|
|
357
356
|
|
|
358
357
|
.. code-block:: python
|
|
359
358
|
|
|
360
|
-
>>> m1, m2 =
|
|
359
|
+
>>> m1, m2 = wg.stieltjes(plot=True, on_disk=True)
|
|
361
360
|
|
|
362
361
|
.. image:: ../_static/images/plots/wg_stieltjes_disk.png
|
|
363
362
|
:align: center
|
|
@@ -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
|
|
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
|
-
|
|
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
|
|
558
|
+
return X * (self.r / (2.0 * numpy.sqrt(n)))
|
|
@@ -631,8 +631,7 @@ class FreeForm(object):
|
|
|
631
631
|
# stieltjes
|
|
632
632
|
# =========
|
|
633
633
|
|
|
634
|
-
def stieltjes(self, x, y, plot=False, latex=False,
|
|
635
|
-
save=False):
|
|
634
|
+
def stieltjes(self, x, y, plot=False, latex=False, save=False):
|
|
636
635
|
"""
|
|
637
636
|
Compute Stieltjes transform of the spectral density over a 2D Cartesian
|
|
638
637
|
grid on the complex plane.
|
|
@@ -656,10 +655,6 @@ class FreeForm(object):
|
|
|
656
655
|
If `True`, the plot is rendered using LaTeX. This option is
|
|
657
656
|
relevant only if ``plot=True``.
|
|
658
657
|
|
|
659
|
-
char_curves : numpy.array, default=None
|
|
660
|
-
If ``plot=True``, also plot characteristic curves starting from
|
|
661
|
-
these locations.
|
|
662
|
-
|
|
663
658
|
save : bool, default=False
|
|
664
659
|
If not `False`, the plot is saved. If a string is given, it is
|
|
665
660
|
assumed to the save filename (with the file extension). This option
|
|
@@ -747,9 +742,7 @@ class FreeForm(object):
|
|
|
747
742
|
m2[mask_m, :] = -m1[mask_m, :] + self._glue(z[mask_m, :])
|
|
748
743
|
|
|
749
744
|
if plot:
|
|
750
|
-
plot_stieltjes(x, y, m1, m2, self.support, latex=latex,
|
|
751
|
-
char_curves={'matrix': self, 'z': char_curves},
|
|
752
|
-
save=save)
|
|
745
|
+
plot_stieltjes(x, y, m1, m2, self.support, latex=latex, save=save)
|
|
753
746
|
|
|
754
747
|
return m1, m2
|
|
755
748
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: freealg
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.8
|
|
4
4
|
Summary: Free probability for large matrices
|
|
5
5
|
Keywords: leaderboard bot chat
|
|
6
6
|
Platform: Linux
|
|
@@ -24,12 +24,12 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
|
24
24
|
Requires-Python: >=3.9
|
|
25
25
|
Description-Content-Type: text/x-rst
|
|
26
26
|
License-File: LICENSE.txt
|
|
27
|
+
License-File: AUTHORS.txt
|
|
27
28
|
Requires-Dist: numpy
|
|
28
29
|
Requires-Dist: scipy
|
|
29
30
|
Requires-Dist: texplot
|
|
30
31
|
Requires-Dist: matplotlib
|
|
31
32
|
Requires-Dist: colorcet
|
|
32
|
-
Requires-Dist: networkx
|
|
33
33
|
Requires-Dist: statsmodels
|
|
34
34
|
Provides-Extra: test
|
|
35
35
|
Requires-Dist: tox; extra == "test"
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
AUTHORS.txt
|
|
1
2
|
CHANGELOG.rst
|
|
2
3
|
LICENSE.txt
|
|
3
4
|
MANIFEST.in
|
|
@@ -26,5 +27,6 @@ freealg.egg-info/top_level.txt
|
|
|
26
27
|
freealg/distributions/__init__.py
|
|
27
28
|
freealg/distributions/kesten_mckay.py
|
|
28
29
|
freealg/distributions/marchenko_pastur.py
|
|
30
|
+
freealg/distributions/meixner.py
|
|
29
31
|
freealg/distributions/wachter.py
|
|
30
32
|
freealg/distributions/wigner.py
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.1.6"
|
|
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
|