freealg 0.1.5__py3-none-any.whl → 0.1.7__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/__version__.py +1 -1
- freealg/_decompress.py +45 -1
- freealg/distributions/__init__.py +2 -3
- freealg/distributions/kesten_mckay.py +8 -8
- freealg/distributions/meixner.py +584 -0
- freealg/distributions/wachter.py +11 -8
- freealg/distributions/wigner.py +4 -4
- {freealg-0.1.5.dist-info → freealg-0.1.7.dist-info}/METADATA +2 -1
- freealg-0.1.7.dist-info/RECORD +23 -0
- freealg-0.1.7.dist-info/licenses/AUTHORS.txt +2 -0
- freealg-0.1.5.dist-info/RECORD +0 -21
- {freealg-0.1.5.dist-info → freealg-0.1.7.dist-info}/WHEEL +0 -0
- {freealg-0.1.5.dist-info → freealg-0.1.7.dist-info}/licenses/LICENSE.txt +0 -0
- {freealg-0.1.5.dist-info → freealg-0.1.7.dist-info}/top_level.txt +0 -0
freealg/__version__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.1.
|
|
1
|
+
__version__ = "0.1.7"
|
freealg/_decompress.py
CHANGED
|
@@ -11,8 +11,9 @@
|
|
|
11
11
|
# =======
|
|
12
12
|
|
|
13
13
|
import numpy
|
|
14
|
+
# from scipy.integrate import solve_ivp
|
|
14
15
|
|
|
15
|
-
__all__ = ['decompress']
|
|
16
|
+
__all__ = ['decompress', 'reverse_characteristics']
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
# ==========
|
|
@@ -134,3 +135,46 @@ def decompress(matrix, size, x=None, delta=1e-4, iterations=500, step_size=0.1,
|
|
|
134
135
|
rho = rho.reshape(*x.shape)
|
|
135
136
|
|
|
136
137
|
return rho, x, (lb, ub)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
# =======================
|
|
141
|
+
# reverse characteristics
|
|
142
|
+
# =======================
|
|
143
|
+
|
|
144
|
+
def reverse_characteristics(matrix, z_inits, T, iterations=500, step_size=0.1,
|
|
145
|
+
tolerance=1e-8):
|
|
146
|
+
"""
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
t_span = (0, T)
|
|
150
|
+
t_eval = numpy.linspace(t_span[0], t_span[1], 50)
|
|
151
|
+
|
|
152
|
+
m = matrix._eval_stieltjes
|
|
153
|
+
|
|
154
|
+
def _char_z(z, t):
|
|
155
|
+
return z + (1 / m(z)[1]) * (1 - numpy.exp(t))
|
|
156
|
+
|
|
157
|
+
target_z, target_t = numpy.meshgrid(z_inits, t_eval)
|
|
158
|
+
|
|
159
|
+
z = numpy.full(target_z.shape, numpy.mean(matrix.support) - .1j,
|
|
160
|
+
dtype=numpy.complex128)
|
|
161
|
+
|
|
162
|
+
# Broken Newton steps can produce a lot of warnings. Removing them for now.
|
|
163
|
+
with numpy.errstate(all='ignore'):
|
|
164
|
+
for _ in range(iterations):
|
|
165
|
+
objective = _char_z(z, target_t) - target_z
|
|
166
|
+
mask = numpy.abs(objective) >= tolerance
|
|
167
|
+
if not numpy.any(mask):
|
|
168
|
+
break
|
|
169
|
+
z_m = z[mask]
|
|
170
|
+
t_m = target_t[mask]
|
|
171
|
+
|
|
172
|
+
# Perform finite difference approximation
|
|
173
|
+
dfdz = _char_z(z_m+tolerance, t_m) - _char_z(z_m-tolerance, t_m)
|
|
174
|
+
dfdz /= 2*tolerance
|
|
175
|
+
dfdz[dfdz == 0] = 1.0
|
|
176
|
+
|
|
177
|
+
# Perform Newton step
|
|
178
|
+
z[mask] = z_m - step_size * objective[mask] / dfdz
|
|
179
|
+
|
|
180
|
+
return z
|
|
@@ -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']
|
|
@@ -61,7 +61,7 @@ class KestenMcKay(object):
|
|
|
61
61
|
Notes
|
|
62
62
|
-----
|
|
63
63
|
|
|
64
|
-
The
|
|
64
|
+
The Kesten-McKay distribution has the absolutely-continuous density
|
|
65
65
|
|
|
66
66
|
.. math::
|
|
67
67
|
|
|
@@ -236,10 +236,10 @@ class KestenMcKay(object):
|
|
|
236
236
|
x = numpy.linspace(x_min, x_max, 500)
|
|
237
237
|
|
|
238
238
|
def _P(x):
|
|
239
|
-
return (self.d - 2.0) * x
|
|
239
|
+
return (self.d - 2.0) * x / (self.d - 1.0)
|
|
240
240
|
|
|
241
241
|
def _Q(x):
|
|
242
|
-
return self.d**2 - x**2
|
|
242
|
+
return (self.d**2 - x**2) / (self.d - 1.0)
|
|
243
243
|
|
|
244
244
|
P = _P(x)
|
|
245
245
|
Q = _Q(x)
|
|
@@ -262,15 +262,15 @@ class KestenMcKay(object):
|
|
|
262
262
|
|
|
263
263
|
def _m_mp_numeric_vectorized(self, z, alt_branch=False, tol=1e-8):
|
|
264
264
|
"""
|
|
265
|
-
Stieltjes transform (principal or secondary branch)
|
|
266
|
-
|
|
265
|
+
Stieltjes transform (principal or secondary branch) for Kesten-McKay
|
|
266
|
+
distribution on upper half-plane.
|
|
267
267
|
"""
|
|
268
268
|
|
|
269
269
|
m = numpy.empty_like(z, dtype=complex)
|
|
270
270
|
|
|
271
271
|
sign = -1 if alt_branch else 1
|
|
272
|
-
A = self.d**2 - z**2
|
|
273
|
-
B = (self.d - 2.0) * z
|
|
272
|
+
A = (self.d**2 - z**2) / (self.d - 1.0)
|
|
273
|
+
B = ((self.d - 2.0) * z) / (self.d - 1.0)
|
|
274
274
|
D = B**2 - 4 * A
|
|
275
275
|
sqrtD = numpy.sqrt(D)
|
|
276
276
|
m1 = (-B + sqrtD) / (2 * A)
|
|
@@ -370,7 +370,7 @@ class KestenMcKay(object):
|
|
|
370
370
|
|
|
371
371
|
.. code-block:: python
|
|
372
372
|
|
|
373
|
-
>>> m1, m2 =
|
|
373
|
+
>>> m1, m2 = km.stieltjes(plot=True, on_disk=True)
|
|
374
374
|
|
|
375
375
|
.. image:: ../_static/images/plots/km_stieltjes_disk.png
|
|
376
376
|
:align: center
|
|
@@ -0,0 +1,584 @@
|
|
|
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 distributon. 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):
|
|
102
|
+
"""
|
|
103
|
+
Initialization.
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
self.a = a
|
|
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)
|
|
110
|
+
self.support = (self.lam_m, self.lam_p)
|
|
111
|
+
|
|
112
|
+
# =======
|
|
113
|
+
# density
|
|
114
|
+
# =======
|
|
115
|
+
|
|
116
|
+
def density(self, x=None, plot=False, latex=False, save=False):
|
|
117
|
+
"""
|
|
118
|
+
Density of distribution.
|
|
119
|
+
|
|
120
|
+
Parameters
|
|
121
|
+
----------
|
|
122
|
+
|
|
123
|
+
x : numpy.array, default=None
|
|
124
|
+
The locations where density is evaluated at. If `None`, an interval
|
|
125
|
+
slightly larger than the support interval of the spectral density
|
|
126
|
+
is used.
|
|
127
|
+
|
|
128
|
+
rho : numpy.array, default=None
|
|
129
|
+
Density. If `None`, it will be computed.
|
|
130
|
+
|
|
131
|
+
plot : bool, default=False
|
|
132
|
+
If `True`, density is plotted.
|
|
133
|
+
|
|
134
|
+
latex : bool, default=False
|
|
135
|
+
If `True`, the plot is rendered using LaTeX. This option is
|
|
136
|
+
relevant only if ``plot=True``.
|
|
137
|
+
|
|
138
|
+
save : bool, default=False
|
|
139
|
+
If not `False`, the plot is saved. If a string is given, it is
|
|
140
|
+
assumed to the save filename (with the file extension). This option
|
|
141
|
+
is relevant only if ``plot=True``.
|
|
142
|
+
|
|
143
|
+
Returns
|
|
144
|
+
-------
|
|
145
|
+
|
|
146
|
+
rho : numpy.array
|
|
147
|
+
Density.
|
|
148
|
+
|
|
149
|
+
Examples
|
|
150
|
+
--------
|
|
151
|
+
|
|
152
|
+
.. code-block::python
|
|
153
|
+
|
|
154
|
+
>>> from freealg.distributions import Meixner
|
|
155
|
+
>>> mx = Meixner(2, 3)
|
|
156
|
+
>>> rho = mx.density(plot=True)
|
|
157
|
+
|
|
158
|
+
.. image:: ../_static/images/plots/mx_density.png
|
|
159
|
+
:align: center
|
|
160
|
+
:class: custom-dark
|
|
161
|
+
"""
|
|
162
|
+
|
|
163
|
+
# Create x if not given
|
|
164
|
+
if x is None:
|
|
165
|
+
radius = 0.5 * (self.lam_p - self.lam_m)
|
|
166
|
+
center = 0.5 * (self.lam_p + self.lam_m)
|
|
167
|
+
scale = 1.25
|
|
168
|
+
x_min = numpy.floor(center - radius * scale)
|
|
169
|
+
x_max = numpy.ceil(center + radius * scale)
|
|
170
|
+
x = numpy.linspace(x_min, x_max, 500)
|
|
171
|
+
|
|
172
|
+
rho = numpy.zeros_like(x)
|
|
173
|
+
mask = numpy.logical_and(x > self.lam_m, x < self.lam_p)
|
|
174
|
+
|
|
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))
|
|
178
|
+
|
|
179
|
+
if plot:
|
|
180
|
+
plot_density(x, rho, label='', latex=latex, save=save)
|
|
181
|
+
|
|
182
|
+
return rho
|
|
183
|
+
|
|
184
|
+
# =======
|
|
185
|
+
# hilbert
|
|
186
|
+
# =======
|
|
187
|
+
|
|
188
|
+
def hilbert(self, x=None, plot=False, latex=False, save=False):
|
|
189
|
+
"""
|
|
190
|
+
Hilbert transform of the distribution.
|
|
191
|
+
|
|
192
|
+
Parameters
|
|
193
|
+
----------
|
|
194
|
+
|
|
195
|
+
x : numpy.array, default=None
|
|
196
|
+
The locations where Hilbert transform is evaluated at. If `None`,
|
|
197
|
+
an interval slightly larger than the support interval of the
|
|
198
|
+
spectral density is used.
|
|
199
|
+
|
|
200
|
+
plot : bool, default=False
|
|
201
|
+
If `True`, Hilbert transform is plotted.
|
|
202
|
+
|
|
203
|
+
latex : bool, default=False
|
|
204
|
+
If `True`, the plot is rendered using LaTeX. This option is
|
|
205
|
+
relevant only if ``plot=True``.
|
|
206
|
+
|
|
207
|
+
save : bool, default=False
|
|
208
|
+
If not `False`, the plot is saved. If a string is given, it is
|
|
209
|
+
assumed to the save filename (with the file extension). This option
|
|
210
|
+
is relevant only if ``plot=True``.
|
|
211
|
+
|
|
212
|
+
Returns
|
|
213
|
+
-------
|
|
214
|
+
|
|
215
|
+
hilb : numpy.array
|
|
216
|
+
Hilbert transform.
|
|
217
|
+
|
|
218
|
+
Examples
|
|
219
|
+
--------
|
|
220
|
+
|
|
221
|
+
.. code-block::python
|
|
222
|
+
|
|
223
|
+
>>> from freealg.distributions import Meixner
|
|
224
|
+
>>> mx = Meixner(2, 3)
|
|
225
|
+
>>> hilb = mx.hilbert(plot=True)
|
|
226
|
+
|
|
227
|
+
.. image:: ../_static/images/plots/mx_hilbert.png
|
|
228
|
+
:align: center
|
|
229
|
+
:class: custom-dark
|
|
230
|
+
"""
|
|
231
|
+
|
|
232
|
+
# Create x if not given
|
|
233
|
+
if x is None:
|
|
234
|
+
radius = 0.5 * (self.lam_p - self.lam_m)
|
|
235
|
+
center = 0.5 * (self.lam_p + self.lam_m)
|
|
236
|
+
scale = 1.25
|
|
237
|
+
x_min = numpy.floor(center - radius * scale)
|
|
238
|
+
x_max = numpy.ceil(center + radius * scale)
|
|
239
|
+
x = numpy.linspace(x_min, x_max, 500)
|
|
240
|
+
|
|
241
|
+
def _P(x):
|
|
242
|
+
denom = 1.0 + self.b
|
|
243
|
+
return ((1.0 + 2.0 * self.b) * x + self.a) / denom
|
|
244
|
+
|
|
245
|
+
def _Q(x):
|
|
246
|
+
denom = 1.0 + self.b
|
|
247
|
+
return (self.b * x**2 + self.a * x + 1.0) / denom
|
|
248
|
+
|
|
249
|
+
P = _P(x)
|
|
250
|
+
Q = _Q(x)
|
|
251
|
+
Delta2 = P**2 - 4.0 * Q
|
|
252
|
+
Delta = numpy.sqrt(numpy.maximum(Delta2, 0))
|
|
253
|
+
sign = numpy.sign(P)
|
|
254
|
+
hilb = (P - sign * Delta) / (2.0 * Q)
|
|
255
|
+
|
|
256
|
+
# using negative sign convention
|
|
257
|
+
hilb = -hilb
|
|
258
|
+
|
|
259
|
+
if plot:
|
|
260
|
+
plot_hilbert(x, hilb, support=self.support, latex=latex, save=save)
|
|
261
|
+
|
|
262
|
+
return hilb
|
|
263
|
+
|
|
264
|
+
# =======================
|
|
265
|
+
# m mp numeric vectorized
|
|
266
|
+
# =======================
|
|
267
|
+
|
|
268
|
+
def _m_mp_numeric_vectorized(self, z, alt_branch=False, tol=1e-8):
|
|
269
|
+
"""
|
|
270
|
+
Stieltjes transform (principal or secondary branch) for Meixner
|
|
271
|
+
distribution on upper half-plane.
|
|
272
|
+
"""
|
|
273
|
+
|
|
274
|
+
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)
|
|
280
|
+
m1 = (-B + sqrtD) / (2 * A)
|
|
281
|
+
m2 = (-B - sqrtD) / (2 * A)
|
|
282
|
+
|
|
283
|
+
# pick correct branch only for non‑masked entries
|
|
284
|
+
upper = z.imag >= 0
|
|
285
|
+
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])
|
|
290
|
+
m = branch
|
|
291
|
+
|
|
292
|
+
return m
|
|
293
|
+
|
|
294
|
+
# ============
|
|
295
|
+
# m mp reflect
|
|
296
|
+
# ============
|
|
297
|
+
|
|
298
|
+
def _m_mp_reflect(self, z, alt_branch=False):
|
|
299
|
+
"""
|
|
300
|
+
Analytic continuation using Schwarz reflection.
|
|
301
|
+
"""
|
|
302
|
+
|
|
303
|
+
mask_p = z.imag >= 0.0
|
|
304
|
+
mask_n = z.imag < 0.0
|
|
305
|
+
|
|
306
|
+
m = numpy.zeros_like(z)
|
|
307
|
+
|
|
308
|
+
f = self._m_mp_numeric_vectorized
|
|
309
|
+
m[mask_p] = f(z[mask_p], alt_branch=False)
|
|
310
|
+
m[mask_n] = f(z[mask_n], alt_branch=alt_branch)
|
|
311
|
+
|
|
312
|
+
return m
|
|
313
|
+
|
|
314
|
+
# =========
|
|
315
|
+
# stieltjes
|
|
316
|
+
# =========
|
|
317
|
+
|
|
318
|
+
def stieltjes(self, x=None, y=None, plot=False, on_disk=False, latex=False,
|
|
319
|
+
save=False):
|
|
320
|
+
"""
|
|
321
|
+
Stieltjes transform of distribution.
|
|
322
|
+
|
|
323
|
+
Parameters
|
|
324
|
+
----------
|
|
325
|
+
|
|
326
|
+
x : numpy.array, default=None
|
|
327
|
+
The x axis of the grid where the Stieltjes transform is evaluated.
|
|
328
|
+
If `None`, an interval slightly larger than the support interval of
|
|
329
|
+
the spectral density is used.
|
|
330
|
+
|
|
331
|
+
y : numpy.array, default=None
|
|
332
|
+
The y axis of the grid where the Stieltjes transform is evaluated.
|
|
333
|
+
If `None`, a grid on the interval ``[-1, 1]`` is used.
|
|
334
|
+
|
|
335
|
+
plot : bool, default=False
|
|
336
|
+
If `True`, Stieltjes transform is plotted.
|
|
337
|
+
|
|
338
|
+
on_disk : bool, default=False
|
|
339
|
+
If `True`, the Stieltjes transform is mapped on unit disk. This
|
|
340
|
+
option relevant only if ``plot=True``.
|
|
341
|
+
|
|
342
|
+
latex : bool, default=False
|
|
343
|
+
If `True`, the plot is rendered using LaTeX. This option is
|
|
344
|
+
relevant only if ``plot=True``.
|
|
345
|
+
|
|
346
|
+
save : bool, default=False
|
|
347
|
+
If not `False`, the plot is saved. If a string is given, it is
|
|
348
|
+
assumed to the save filename (with the file extension). This option
|
|
349
|
+
is relevant only if ``plot=True``.
|
|
350
|
+
|
|
351
|
+
Returns
|
|
352
|
+
-------
|
|
353
|
+
|
|
354
|
+
m1 : numpy.array
|
|
355
|
+
Stieltjes transform on principal branch.
|
|
356
|
+
|
|
357
|
+
m12 : numpy.array
|
|
358
|
+
Stieltjes transform on secondary branch.
|
|
359
|
+
|
|
360
|
+
Examples
|
|
361
|
+
--------
|
|
362
|
+
|
|
363
|
+
.. code-block:: python
|
|
364
|
+
|
|
365
|
+
>>> from freealg.distributions import Meixner
|
|
366
|
+
>>> mx = Meixner(2, 3)
|
|
367
|
+
>>> m1, m2 = mx.stieltjes(plot=True)
|
|
368
|
+
|
|
369
|
+
.. image:: ../_static/images/plots/mx_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 = mx.stieltjes(plot=True, on_disk=True)
|
|
378
|
+
|
|
379
|
+
.. image:: ../_static/images/plots/mx_stieltjes_disk.png
|
|
380
|
+
:align: center
|
|
381
|
+
:class: custom-dark
|
|
382
|
+
"""
|
|
383
|
+
|
|
384
|
+
if (plot is True) and (on_disk is True):
|
|
385
|
+
n_r = 1000
|
|
386
|
+
n_t = 1000
|
|
387
|
+
r_min, r_max = 0, 2.5
|
|
388
|
+
t_min, t_max = 0, 2.0 * numpy.pi
|
|
389
|
+
r = numpy.linspace(r_min, r_max, n_r)
|
|
390
|
+
t = numpy.linspace(t_min, t_max, n_t + 1)[:-1]
|
|
391
|
+
grid_r, grid_t = numpy.meshgrid(r, t)
|
|
392
|
+
|
|
393
|
+
grid_x_D = grid_r * numpy.cos(grid_t)
|
|
394
|
+
grid_y_D = grid_r * numpy.sin(grid_t)
|
|
395
|
+
zeta = grid_x_D + 1j * grid_y_D
|
|
396
|
+
|
|
397
|
+
# Cayley transform mapping zeta on D to z on H
|
|
398
|
+
z_H = 1j * (1 + zeta) / (1 - zeta)
|
|
399
|
+
|
|
400
|
+
m1_D = self._m_mp_reflect(z_H, alt_branch=False)
|
|
401
|
+
m2_D = self._m_mp_reflect(z_H, alt_branch=True)
|
|
402
|
+
|
|
403
|
+
plot_stieltjes_on_disk(r, t, m1_D, m2_D, support=self.support,
|
|
404
|
+
latex=latex, save=save)
|
|
405
|
+
|
|
406
|
+
return m1_D, m2_D
|
|
407
|
+
|
|
408
|
+
# Create x if not given
|
|
409
|
+
if x is None:
|
|
410
|
+
radius = 0.5 * (self.lam_p - self.lam_m)
|
|
411
|
+
center = 0.5 * (self.lam_p + self.lam_m)
|
|
412
|
+
scale = 2.0
|
|
413
|
+
x_min = numpy.floor(2.0 * (center - 2.0 * radius * scale)) / 2.0
|
|
414
|
+
x_max = numpy.ceil(2.0 * (center + 2.0 * radius * scale)) / 2.0
|
|
415
|
+
x = numpy.linspace(x_min, x_max, 500)
|
|
416
|
+
|
|
417
|
+
# Create y if not given
|
|
418
|
+
if y is None:
|
|
419
|
+
y = numpy.linspace(-1, 1, 400)
|
|
420
|
+
|
|
421
|
+
x_grid, y_grid = numpy.meshgrid(x, y)
|
|
422
|
+
z = x_grid + 1j * y_grid # shape (Ny, Nx)
|
|
423
|
+
|
|
424
|
+
m1 = self._m_mp_reflect(z, alt_branch=False)
|
|
425
|
+
m2 = self._m_mp_reflect(z, alt_branch=True)
|
|
426
|
+
|
|
427
|
+
if plot:
|
|
428
|
+
plot_stieltjes(x, y, m1, m2, support=self.support, latex=latex,
|
|
429
|
+
save=save)
|
|
430
|
+
|
|
431
|
+
return m1, m2
|
|
432
|
+
|
|
433
|
+
# ======
|
|
434
|
+
# sample
|
|
435
|
+
# ======
|
|
436
|
+
|
|
437
|
+
def sample(self, size, x_min=None, x_max=None, method='qmc', plot=False,
|
|
438
|
+
latex=False, save=False):
|
|
439
|
+
"""
|
|
440
|
+
Sample from distribution.
|
|
441
|
+
|
|
442
|
+
Parameters
|
|
443
|
+
----------
|
|
444
|
+
|
|
445
|
+
size : int
|
|
446
|
+
Size of sample.
|
|
447
|
+
|
|
448
|
+
x_min : float, default=None
|
|
449
|
+
Minimum of sample values. If `None`, the left edge of the support
|
|
450
|
+
is used.
|
|
451
|
+
|
|
452
|
+
x_max : float, default=None
|
|
453
|
+
Maximum of sample values. If `None`, the right edge of the support
|
|
454
|
+
is used.
|
|
455
|
+
|
|
456
|
+
method : {``'mc'``, ``'qmc'``}, default= ``'qmc'``
|
|
457
|
+
Method of drawing samples from uniform distirbution:
|
|
458
|
+
|
|
459
|
+
* ``'mc'``: Monte Carlo
|
|
460
|
+
* ``'qmc'``: Quasi Monte Carlo
|
|
461
|
+
|
|
462
|
+
plot : bool, default=False
|
|
463
|
+
If `True`, samples histogram is plotted.
|
|
464
|
+
|
|
465
|
+
latex : bool, default=False
|
|
466
|
+
If `True`, the plot is rendered using LaTeX. This option is
|
|
467
|
+
relevant only if ``plot=True``.
|
|
468
|
+
|
|
469
|
+
save : bool, default=False
|
|
470
|
+
If not `False`, the plot is saved. If a string is given, it is
|
|
471
|
+
assumed to the save filename (with the file extension). This option
|
|
472
|
+
is relevant only if ``plot=True``.
|
|
473
|
+
|
|
474
|
+
Returns
|
|
475
|
+
-------
|
|
476
|
+
|
|
477
|
+
s : numpy.ndarray
|
|
478
|
+
Samples.
|
|
479
|
+
|
|
480
|
+
Notes
|
|
481
|
+
-----
|
|
482
|
+
|
|
483
|
+
This method uses inverse transform sampling.
|
|
484
|
+
|
|
485
|
+
Examples
|
|
486
|
+
--------
|
|
487
|
+
|
|
488
|
+
.. code-block::python
|
|
489
|
+
|
|
490
|
+
>>> from freealg.distributions import Meixner
|
|
491
|
+
>>> mx = Meixner(2, 3)
|
|
492
|
+
>>> s = mx.sample(2000)
|
|
493
|
+
|
|
494
|
+
.. image:: ../_static/images/plots/mx_samples.png
|
|
495
|
+
:align: center
|
|
496
|
+
:class: custom-dark
|
|
497
|
+
"""
|
|
498
|
+
|
|
499
|
+
if x_min is None:
|
|
500
|
+
x_min = self.lam_m
|
|
501
|
+
|
|
502
|
+
if x_max is None:
|
|
503
|
+
x_max = self.lam_p
|
|
504
|
+
|
|
505
|
+
# Grid and PDF
|
|
506
|
+
xs = numpy.linspace(x_min, x_max, size)
|
|
507
|
+
pdf = self.density(xs)
|
|
508
|
+
|
|
509
|
+
# CDF (using cumulative trapezoidal rule)
|
|
510
|
+
cdf = cumtrapz(pdf, xs, initial=0)
|
|
511
|
+
cdf /= cdf[-1] # normalize CDF to 1
|
|
512
|
+
|
|
513
|
+
# Inverse CDF interpolator
|
|
514
|
+
inv_cdf = interp1d(cdf, xs, bounds_error=False,
|
|
515
|
+
fill_value=(x_min, x_max))
|
|
516
|
+
|
|
517
|
+
# Draw from uniform distribution
|
|
518
|
+
if method == 'mc':
|
|
519
|
+
u = numpy.random.rand(size)
|
|
520
|
+
elif method == 'qmc':
|
|
521
|
+
engine = qmc.Halton(d=1)
|
|
522
|
+
u = engine.random(size)
|
|
523
|
+
else:
|
|
524
|
+
raise ValueError('"method" is invalid.')
|
|
525
|
+
|
|
526
|
+
# Draw from distribution by mapping from inverse CDF
|
|
527
|
+
samples = inv_cdf(u).ravel()
|
|
528
|
+
|
|
529
|
+
if plot:
|
|
530
|
+
radius = 0.5 * (self.lam_p - self.lam_m)
|
|
531
|
+
center = 0.5 * (self.lam_p + self.lam_m)
|
|
532
|
+
scale = 1.25
|
|
533
|
+
x_min = numpy.floor(center - radius * scale)
|
|
534
|
+
x_max = numpy.ceil(center + radius * scale)
|
|
535
|
+
x = numpy.linspace(x_min, x_max, 500)
|
|
536
|
+
rho = self.density(x)
|
|
537
|
+
plot_samples(x, rho, x_min, x_max, samples, latex=latex, save=save)
|
|
538
|
+
|
|
539
|
+
return samples
|
|
540
|
+
|
|
541
|
+
# ======
|
|
542
|
+
# matrix
|
|
543
|
+
# ======
|
|
544
|
+
|
|
545
|
+
def matrix(self, size):
|
|
546
|
+
"""
|
|
547
|
+
Generate matrix with the spectral density of the distribution.
|
|
548
|
+
|
|
549
|
+
Parameters
|
|
550
|
+
----------
|
|
551
|
+
|
|
552
|
+
size : int
|
|
553
|
+
Size :math:`n` of the matrix.
|
|
554
|
+
|
|
555
|
+
Returns
|
|
556
|
+
-------
|
|
557
|
+
|
|
558
|
+
Sx : numpy.ndarray
|
|
559
|
+
A matrix of the size :math:`n \\times n`.
|
|
560
|
+
|
|
561
|
+
Sy : numpy.ndarray
|
|
562
|
+
A matrix of the size :math:`n \\times n`.
|
|
563
|
+
|
|
564
|
+
Examples
|
|
565
|
+
--------
|
|
566
|
+
|
|
567
|
+
.. code-block::python
|
|
568
|
+
|
|
569
|
+
>>> from freealg.distributions import Meixner
|
|
570
|
+
>>> mx = Meixner(2, 3)
|
|
571
|
+
>>> A = mx.matrix(2000)
|
|
572
|
+
"""
|
|
573
|
+
|
|
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
|
freealg/distributions/wachter.py
CHANGED
|
@@ -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
|
freealg/distributions/wigner.py
CHANGED
|
@@ -55,7 +55,7 @@ class Wigner(object):
|
|
|
55
55
|
Notes
|
|
56
56
|
-----
|
|
57
57
|
|
|
58
|
-
The
|
|
58
|
+
The Wigner distribution has the absolutely-continuous density
|
|
59
59
|
|
|
60
60
|
.. math::
|
|
61
61
|
|
|
@@ -248,8 +248,8 @@ class Wigner(object):
|
|
|
248
248
|
|
|
249
249
|
def _m_mp_numeric_vectorized(self, z, alt_branch=False, tol=1e-8):
|
|
250
250
|
"""
|
|
251
|
-
Stieltjes transform (principal or secondary branch)
|
|
252
|
-
|
|
251
|
+
Stieltjes transform (principal or secondary branch) for Wigner
|
|
252
|
+
distribution on upper half-plane.
|
|
253
253
|
"""
|
|
254
254
|
|
|
255
255
|
m = numpy.empty_like(z, dtype=complex)
|
|
@@ -357,7 +357,7 @@ class Wigner(object):
|
|
|
357
357
|
|
|
358
358
|
.. code-block:: python
|
|
359
359
|
|
|
360
|
-
>>> m1, m2 =
|
|
360
|
+
>>> m1, m2 = wg.stieltjes(plot=True, on_disk=True)
|
|
361
361
|
|
|
362
362
|
.. image:: ../_static/images/plots/wg_stieltjes_disk.png
|
|
363
363
|
:align: center
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: freealg
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.7
|
|
4
4
|
Summary: Free probability for large matrices
|
|
5
5
|
Keywords: leaderboard bot chat
|
|
6
6
|
Platform: Linux
|
|
@@ -24,6 +24,7 @@ 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
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
freealg/__init__.py,sha256=K92neXJZ9VE1U_j_pj28Qyq1MzlMXhOuYK2ZihgwCaU,463
|
|
2
|
+
freealg/__version__.py,sha256=YpKDcdV7CqL8n45u267wKtyloM13FSVbOdrqgNZnSLM,22
|
|
3
|
+
freealg/_chebyshev.py,sha256=X6u5pKjR1HPZ-KbCfr7zT6HRwB6pZMADvVS3sT5LTkA,5638
|
|
4
|
+
freealg/_damp.py,sha256=k2vtBtWOxQBf4qXaWu_En81lQBXbEO4QbxxWpvuVhdE,1802
|
|
5
|
+
freealg/_decompress.py,sha256=7U2lL8F5z76aFuZJBsPj70jEVRuzvJHnIh5FSw-aLME,4680
|
|
6
|
+
freealg/_jacobi.py,sha256=AT4ONSHGGDxVKE3MGMLyMR8uDFiO-e9u3x5udYfdJJk,5635
|
|
7
|
+
freealg/_pade.py,sha256=mP96wEPfIzHLZ6PDB5OyhmSA8N1uVPVUkmJa3ebXXiU,13623
|
|
8
|
+
freealg/_plot_util.py,sha256=wVx99GRdIFu_wzmG8f5JSDZ65BJohnuSBm3mZ58wElg,18426
|
|
9
|
+
freealg/_sample.py,sha256=K1ZxKoiuPbEKyh-swL5X7gz1kYcQno6Mof0o1xF38tg,2323
|
|
10
|
+
freealg/_util.py,sha256=alJ9s1U_sHL7dXq7hI10fa8CF_AZ6Xmy_QsoyDYPSDQ,3677
|
|
11
|
+
freealg/freeform.py,sha256=kbh7UoOJkAVFKj2Zmddy803-asoslkqn-gWJ-HpLN7U,28750
|
|
12
|
+
freealg/distributions/__init__.py,sha256=ufiL5OG_Jyma3D2il0BedhGuilROilbmSjxqoiz45GE,574
|
|
13
|
+
freealg/distributions/kesten_mckay.py,sha256=nvCEPKVjZCYNt-MLlFSzTfj8PTlcLmmGW9AefvYJxuU,15977
|
|
14
|
+
freealg/distributions/marchenko_pastur.py,sha256=GwDTN-7au2h7H7PnZkQfs6bas8fNhgEnQ-hTWsBMZuE,16403
|
|
15
|
+
freealg/distributions/meixner.py,sha256=y_iLB5LqvkT9P_rGa46ZCmk_EFfPS_3VuFCZpeD2ooI,16172
|
|
16
|
+
freealg/distributions/wachter.py,sha256=IYKok4stONv0RrigSRDWGLEyBmg6j3hS8c9_fw31kZw,16276
|
|
17
|
+
freealg/distributions/wigner.py,sha256=LE-KDxFb8q7-ifWUv7_LrEpOGYYTMdVPFfzIhm4vJKg,15426
|
|
18
|
+
freealg-0.1.7.dist-info/licenses/AUTHORS.txt,sha256=0b67Nz4_JgIzUupHJTAZxu5QdSUM_HRM_X_w4xCb17o,30
|
|
19
|
+
freealg-0.1.7.dist-info/licenses/LICENSE.txt,sha256=J-EEYEtxb3VVf_Bn1TYfWnpY5lMFIM15iLDDcnaDTPA,1443
|
|
20
|
+
freealg-0.1.7.dist-info/METADATA,sha256=zCRGrnjGgjLlx9WToqSyBR_jqFsMzZIxPbWOM4fMgYE,2965
|
|
21
|
+
freealg-0.1.7.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
|
|
22
|
+
freealg-0.1.7.dist-info/top_level.txt,sha256=eR2wrgYwDdnnJ9Zf5PruPqe4kQav0GMvRsqct6y00Q8,8
|
|
23
|
+
freealg-0.1.7.dist-info/RECORD,,
|
freealg-0.1.5.dist-info/RECORD
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
freealg/__init__.py,sha256=K92neXJZ9VE1U_j_pj28Qyq1MzlMXhOuYK2ZihgwCaU,463
|
|
2
|
-
freealg/__version__.py,sha256=rPSfWgIeq2YWVPyESOAwCBt8vftsTpIkuLAGDEzyRQc,22
|
|
3
|
-
freealg/_chebyshev.py,sha256=X6u5pKjR1HPZ-KbCfr7zT6HRwB6pZMADvVS3sT5LTkA,5638
|
|
4
|
-
freealg/_damp.py,sha256=k2vtBtWOxQBf4qXaWu_En81lQBXbEO4QbxxWpvuVhdE,1802
|
|
5
|
-
freealg/_decompress.py,sha256=H7ocq09gQnCY-q_8xHi6qyYj3qp239MCgj406hn0yeE,3344
|
|
6
|
-
freealg/_jacobi.py,sha256=AT4ONSHGGDxVKE3MGMLyMR8uDFiO-e9u3x5udYfdJJk,5635
|
|
7
|
-
freealg/_pade.py,sha256=mP96wEPfIzHLZ6PDB5OyhmSA8N1uVPVUkmJa3ebXXiU,13623
|
|
8
|
-
freealg/_plot_util.py,sha256=wVx99GRdIFu_wzmG8f5JSDZ65BJohnuSBm3mZ58wElg,18426
|
|
9
|
-
freealg/_sample.py,sha256=K1ZxKoiuPbEKyh-swL5X7gz1kYcQno6Mof0o1xF38tg,2323
|
|
10
|
-
freealg/_util.py,sha256=alJ9s1U_sHL7dXq7hI10fa8CF_AZ6Xmy_QsoyDYPSDQ,3677
|
|
11
|
-
freealg/freeform.py,sha256=kbh7UoOJkAVFKj2Zmddy803-asoslkqn-gWJ-HpLN7U,28750
|
|
12
|
-
freealg/distributions/__init__.py,sha256=Hnk9bJi4Wy8I_1uuskRyrT2DUpPN1YmBY5uK7XI3U_o,644
|
|
13
|
-
freealg/distributions/kesten_mckay.py,sha256=Oq2FCX60seojy7LDn8nYPrbqinmXv4YC-93VxlmDQ6M,15913
|
|
14
|
-
freealg/distributions/marchenko_pastur.py,sha256=GwDTN-7au2h7H7PnZkQfs6bas8fNhgEnQ-hTWsBMZuE,16403
|
|
15
|
-
freealg/distributions/wachter.py,sha256=2eqbJY4S1MqLjgqO6qY06m3-_s-bKTuSiryS_ZH_xvI,16136
|
|
16
|
-
freealg/distributions/wigner.py,sha256=MSrB-HLMzOwnWDDzw5XPLsoL4LEIV35w5jWeL-qDn9Y,15448
|
|
17
|
-
freealg-0.1.5.dist-info/licenses/LICENSE.txt,sha256=J-EEYEtxb3VVf_Bn1TYfWnpY5lMFIM15iLDDcnaDTPA,1443
|
|
18
|
-
freealg-0.1.5.dist-info/METADATA,sha256=sG6aPyglynO4hXzXNnMPDliEtC5JLlrOMy84e_KMDzU,2939
|
|
19
|
-
freealg-0.1.5.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
|
|
20
|
-
freealg-0.1.5.dist-info/top_level.txt,sha256=eR2wrgYwDdnnJ9Zf5PruPqe4kQav0GMvRsqct6y00Q8,8
|
|
21
|
-
freealg-0.1.5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|