freealg 0.6.3__py3-none-any.whl → 0.7.1__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/__init__.py +8 -7
- freealg/__version__.py +1 -1
- freealg/_algebraic_form/__init__.py +11 -0
- freealg/_algebraic_form/_continuation_algebraic.py +503 -0
- freealg/_algebraic_form/_decompress.py +648 -0
- freealg/_algebraic_form/_edge.py +352 -0
- freealg/_algebraic_form/_sheets_util.py +145 -0
- freealg/_algebraic_form/algebraic_form.py +987 -0
- freealg/_freeform/__init__.py +16 -0
- freealg/_freeform/_density_util.py +243 -0
- freealg/{_linalg.py → _freeform/_linalg.py} +1 -1
- freealg/{freeform.py → _freeform/freeform.py} +2 -1
- freealg/_geometric_form/__init__.py +13 -0
- freealg/_geometric_form/_continuation_genus0.py +175 -0
- freealg/_geometric_form/_continuation_genus1.py +275 -0
- freealg/_geometric_form/_elliptic_functions.py +174 -0
- freealg/_geometric_form/_sphere_maps.py +63 -0
- freealg/_geometric_form/_torus_maps.py +118 -0
- freealg/_geometric_form/geometric_form.py +1094 -0
- freealg/_util.py +1 -228
- freealg/distributions/__init__.py +5 -1
- freealg/distributions/_chiral_block.py +440 -0
- freealg/distributions/_deformed_marchenko_pastur.py +617 -0
- freealg/distributions/_deformed_wigner.py +312 -0
- freealg/distributions/_kesten_mckay.py +2 -2
- freealg/distributions/_marchenko_pastur.py +199 -82
- freealg/distributions/_meixner.py +2 -2
- freealg/distributions/_wachter.py +2 -2
- freealg/distributions/_wigner.py +2 -2
- freealg/visualization/__init__.py +12 -0
- freealg/visualization/_glue_util.py +32 -0
- freealg/visualization/_rgb_hsv.py +125 -0
- {freealg-0.6.3.dist-info → freealg-0.7.1.dist-info}/METADATA +1 -1
- freealg-0.7.1.dist-info/RECORD +47 -0
- freealg-0.6.3.dist-info/RECORD +0 -26
- /freealg/{_chebyshev.py → _freeform/_chebyshev.py} +0 -0
- /freealg/{_damp.py → _freeform/_damp.py} +0 -0
- /freealg/{_decompress.py → _freeform/_decompress.py} +0 -0
- /freealg/{_jacobi.py → _freeform/_jacobi.py} +0 -0
- /freealg/{_pade.py → _freeform/_pade.py} +0 -0
- /freealg/{_plot_util.py → _freeform/_plot_util.py} +0 -0
- /freealg/{_sample.py → _freeform/_sample.py} +0 -0
- /freealg/{_series.py → _freeform/_series.py} +0 -0
- /freealg/{_support.py → _freeform/_support.py} +0 -0
- {freealg-0.6.3.dist-info → freealg-0.7.1.dist-info}/WHEEL +0 -0
- {freealg-0.6.3.dist-info → freealg-0.7.1.dist-info}/licenses/AUTHORS.txt +0 -0
- {freealg-0.6.3.dist-info → freealg-0.7.1.dist-info}/licenses/LICENSE.txt +0 -0
- {freealg-0.6.3.dist-info → freealg-0.7.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright 2026, 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 .._algebraic_form._sheets_util import _pick_physical_root_scalar
|
|
16
|
+
|
|
17
|
+
__all__ = ['DeformedWigner']
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# ===============
|
|
21
|
+
# Deformed Wigner
|
|
22
|
+
# ===============
|
|
23
|
+
|
|
24
|
+
class DeformedWigner(object):
|
|
25
|
+
"""
|
|
26
|
+
Deformed Wiger
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
# ====
|
|
30
|
+
# init
|
|
31
|
+
# ====
|
|
32
|
+
|
|
33
|
+
def __init__(self, t1, t2, w1, sigma=1.0):
|
|
34
|
+
"""
|
|
35
|
+
Initialization.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
self.t1 = t1
|
|
39
|
+
self.t2 = t2
|
|
40
|
+
self.w1 = w1
|
|
41
|
+
self.sigma = sigma
|
|
42
|
+
|
|
43
|
+
# ==================
|
|
44
|
+
# roots cubic scalar
|
|
45
|
+
# ==================
|
|
46
|
+
|
|
47
|
+
def _roots_cubic_scalar(self, z):
|
|
48
|
+
"""
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
# Unpack parameters
|
|
52
|
+
t1 = self.t1
|
|
53
|
+
t2 = self.t2
|
|
54
|
+
w1 = self.w1
|
|
55
|
+
sigma = self.sigma
|
|
56
|
+
|
|
57
|
+
w2 = 1.0 - w1
|
|
58
|
+
s2 = sigma * sigma
|
|
59
|
+
a1 = t1 - z
|
|
60
|
+
a2 = t2 - z
|
|
61
|
+
|
|
62
|
+
c3 = s2 * s2
|
|
63
|
+
c2 = -s2 * (a1 + a2)
|
|
64
|
+
c1 = (a1 * a2) + s2
|
|
65
|
+
c0 = -(w1 * a2 + w2 * a1)
|
|
66
|
+
|
|
67
|
+
return numpy.roots([c3, c2, c1, c0])
|
|
68
|
+
|
|
69
|
+
# =========
|
|
70
|
+
# stieltjes
|
|
71
|
+
# =========
|
|
72
|
+
|
|
73
|
+
def stieltjes(self, z, max_iter=100, tol=1e-12):
|
|
74
|
+
"""
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
# Unpack parameters
|
|
78
|
+
t1 = self.t1
|
|
79
|
+
t2 = self.t2
|
|
80
|
+
w1 = self.w1
|
|
81
|
+
sigma = self.sigma
|
|
82
|
+
|
|
83
|
+
w2 = 1.0 - w1
|
|
84
|
+
s2 = sigma * sigma
|
|
85
|
+
|
|
86
|
+
z = numpy.asarray(z, dtype=numpy.complex128)
|
|
87
|
+
scalar = (z.ndim == 0)
|
|
88
|
+
if scalar:
|
|
89
|
+
z = z.reshape((1,))
|
|
90
|
+
|
|
91
|
+
m = -1.0 / z
|
|
92
|
+
active = numpy.isfinite(m)
|
|
93
|
+
|
|
94
|
+
for _ in range(int(max_iter)):
|
|
95
|
+
if not numpy.any(active):
|
|
96
|
+
break
|
|
97
|
+
|
|
98
|
+
ma = m[active]
|
|
99
|
+
za = z[active]
|
|
100
|
+
|
|
101
|
+
d1 = (t1 - za - s2 * ma)
|
|
102
|
+
d2 = (t2 - za - s2 * ma)
|
|
103
|
+
|
|
104
|
+
f = ma - (w1 / d1 + w2 / d2)
|
|
105
|
+
fp = 1.0 - (w1 * s2 / (d1 * d1) + w2 * s2 / (d2 * d2))
|
|
106
|
+
|
|
107
|
+
step = f / fp
|
|
108
|
+
ma2 = ma - step
|
|
109
|
+
m[active] = ma2
|
|
110
|
+
|
|
111
|
+
conv = numpy.abs(step) < tol * (1.0 + numpy.abs(ma2))
|
|
112
|
+
idx = numpy.where(active)[0]
|
|
113
|
+
active[idx[conv]] = False
|
|
114
|
+
|
|
115
|
+
sign = numpy.where(numpy.imag(z) >= 0.0, 1.0, -1.0)
|
|
116
|
+
bad = (sign * numpy.imag(m) <= 0.0) | (~numpy.isfinite(m))
|
|
117
|
+
|
|
118
|
+
if numpy.any(bad):
|
|
119
|
+
zf = z.ravel()
|
|
120
|
+
mf = m.ravel()
|
|
121
|
+
bad_idx = numpy.where(bad.ravel())[0]
|
|
122
|
+
for i in bad_idx:
|
|
123
|
+
r = self._roots_cubic_scalar(zf[i])
|
|
124
|
+
mf[i] = _pick_physical_root_scalar(zf[i], r)
|
|
125
|
+
m = mf.reshape(z.shape)
|
|
126
|
+
|
|
127
|
+
if scalar:
|
|
128
|
+
return m.reshape(())
|
|
129
|
+
return m
|
|
130
|
+
|
|
131
|
+
# =======
|
|
132
|
+
# density
|
|
133
|
+
# =======
|
|
134
|
+
|
|
135
|
+
def density(self, x, eta=1e-3):
|
|
136
|
+
"""
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
# Unpack parameters
|
|
140
|
+
t1 = self.t1
|
|
141
|
+
t2 = self.t2
|
|
142
|
+
w1 = self.w1
|
|
143
|
+
sigma = self.sigma
|
|
144
|
+
|
|
145
|
+
x = numpy.asarray(x, dtype=numpy.float64)
|
|
146
|
+
z = x + 1j * float(eta)
|
|
147
|
+
|
|
148
|
+
zf = z.ravel()
|
|
149
|
+
m = numpy.empty_like(zf, dtype=numpy.complex128)
|
|
150
|
+
|
|
151
|
+
m_prev = None
|
|
152
|
+
for i in range(zf.size):
|
|
153
|
+
zi = zf[i]
|
|
154
|
+
if m_prev is None:
|
|
155
|
+
mi = -1.0 / zi
|
|
156
|
+
else:
|
|
157
|
+
mi = complex(m_prev)
|
|
158
|
+
|
|
159
|
+
for _ in range(80):
|
|
160
|
+
d1 = (t1 - zi - (sigma * sigma) * mi)
|
|
161
|
+
d2 = (t2 - zi - (sigma * sigma) * mi)
|
|
162
|
+
|
|
163
|
+
f = mi - (w1 / d1 + (1.0 - w1) / d2)
|
|
164
|
+
fp = 1.0 - (
|
|
165
|
+
w1 * (sigma * sigma) / (d1 * d1) +
|
|
166
|
+
(1.0 - w1) * (sigma * sigma) / (d2 * d2)
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
step = f / fp
|
|
170
|
+
mi2 = mi - step
|
|
171
|
+
if abs(step) < 1e-12 * (1.0 + abs(mi2)):
|
|
172
|
+
mi = mi2
|
|
173
|
+
break
|
|
174
|
+
mi = mi2
|
|
175
|
+
|
|
176
|
+
m[i] = mi
|
|
177
|
+
m_prev = mi
|
|
178
|
+
|
|
179
|
+
m = m.reshape(z.shape)
|
|
180
|
+
rho = numpy.imag(m) / numpy.pi
|
|
181
|
+
rho = numpy.maximum(rho, 0.0)
|
|
182
|
+
return rho
|
|
183
|
+
|
|
184
|
+
# =====
|
|
185
|
+
# roots
|
|
186
|
+
# =====
|
|
187
|
+
|
|
188
|
+
def roots(self, z):
|
|
189
|
+
"""
|
|
190
|
+
"""
|
|
191
|
+
|
|
192
|
+
z = numpy.asarray(z, dtype=numpy.complex128)
|
|
193
|
+
scalar = (z.ndim == 0)
|
|
194
|
+
if scalar:
|
|
195
|
+
z = z.reshape((1,))
|
|
196
|
+
|
|
197
|
+
zf = z.ravel()
|
|
198
|
+
out = numpy.empty((zf.size, 3), dtype=numpy.complex128)
|
|
199
|
+
for i in range(zf.size):
|
|
200
|
+
out[i, :] = self._roots_cubic_scalar(zf[i])
|
|
201
|
+
|
|
202
|
+
out = out.reshape(z.shape + (3,))
|
|
203
|
+
if scalar:
|
|
204
|
+
return out.reshape((3,))
|
|
205
|
+
return out
|
|
206
|
+
|
|
207
|
+
# =======
|
|
208
|
+
# support
|
|
209
|
+
# =======
|
|
210
|
+
|
|
211
|
+
def support(self, y_probe=1e-6):
|
|
212
|
+
"""
|
|
213
|
+
"""
|
|
214
|
+
|
|
215
|
+
# Unpack parameters
|
|
216
|
+
t1 = self.t1
|
|
217
|
+
t2 = self.t2
|
|
218
|
+
w1 = self.w1
|
|
219
|
+
sigma = self.sigma
|
|
220
|
+
|
|
221
|
+
w2 = 1.0 - w1
|
|
222
|
+
|
|
223
|
+
p_a = numpy.poly1d([-1.0, t1])
|
|
224
|
+
p_b = numpy.poly1d([-1.0, t2])
|
|
225
|
+
|
|
226
|
+
pa2 = p_a * p_a
|
|
227
|
+
pb2 = p_b * p_b
|
|
228
|
+
|
|
229
|
+
eq = pa2 * pb2 - (sigma * sigma) * (w1 * pb2 + w2 * pa2)
|
|
230
|
+
u_roots = numpy.roots(eq.coeffs)
|
|
231
|
+
|
|
232
|
+
ucrit = []
|
|
233
|
+
for r in u_roots:
|
|
234
|
+
if numpy.isfinite(r) and abs(r.imag) < 1e-10:
|
|
235
|
+
ucrit.append(float(r.real))
|
|
236
|
+
ucrit.sort()
|
|
237
|
+
|
|
238
|
+
def G(u):
|
|
239
|
+
return w1 / (t1 - u) + w2 / (t2 - u)
|
|
240
|
+
|
|
241
|
+
def z_of_u(u):
|
|
242
|
+
return u - (sigma * sigma) * G(u)
|
|
243
|
+
|
|
244
|
+
edges = []
|
|
245
|
+
for u in ucrit:
|
|
246
|
+
x = z_of_u(u)
|
|
247
|
+
if numpy.isfinite(x):
|
|
248
|
+
x = float(numpy.real(x))
|
|
249
|
+
if (len(edges) == 0) or (abs(x - edges[-1]) > 1e-8):
|
|
250
|
+
edges.append(x)
|
|
251
|
+
|
|
252
|
+
if len(edges) < 2:
|
|
253
|
+
return []
|
|
254
|
+
|
|
255
|
+
thr = 100.0 * float(y_probe)
|
|
256
|
+
cuts = []
|
|
257
|
+
for i in range(len(edges) - 1):
|
|
258
|
+
xm = 0.5 * (edges[i] + edges[i + 1])
|
|
259
|
+
z = xm + 1j * float(y_probe)
|
|
260
|
+
r = self._roots_cubic_scalar(z)
|
|
261
|
+
m = _pick_physical_root_scalar(z, r)
|
|
262
|
+
if numpy.imag(m) > thr:
|
|
263
|
+
cuts.append((edges[i], edges[i + 1]))
|
|
264
|
+
|
|
265
|
+
return cuts
|
|
266
|
+
|
|
267
|
+
# ======
|
|
268
|
+
# matrix
|
|
269
|
+
# ======
|
|
270
|
+
|
|
271
|
+
def matrix(self, size, seed=None):
|
|
272
|
+
"""
|
|
273
|
+
Generate matrix with the spectral density of the distribution.
|
|
274
|
+
|
|
275
|
+
Parameters
|
|
276
|
+
----------
|
|
277
|
+
|
|
278
|
+
size : int
|
|
279
|
+
Size :math:`n` of the matrix.
|
|
280
|
+
|
|
281
|
+
seed : int, default=None
|
|
282
|
+
Seed for random number generator.
|
|
283
|
+
|
|
284
|
+
Returns
|
|
285
|
+
-------
|
|
286
|
+
|
|
287
|
+
A : numpy.ndarray
|
|
288
|
+
A matrix of the size :math:`n \\times n`.
|
|
289
|
+
|
|
290
|
+
Examples
|
|
291
|
+
--------
|
|
292
|
+
|
|
293
|
+
.. code-block::python
|
|
294
|
+
|
|
295
|
+
>>> from freealg.distributions import MarchenkoPastur
|
|
296
|
+
>>> mp = MarchenkoPastur(1/50)
|
|
297
|
+
>>> A = mp.matrix(2000)
|
|
298
|
+
"""
|
|
299
|
+
|
|
300
|
+
# Parameters
|
|
301
|
+
# m = int(size / self.lam)
|
|
302
|
+
#
|
|
303
|
+
# # Generate random matrix X (n x m) with i.i.d.
|
|
304
|
+
# rng = numpy.random.default_rng(seed)
|
|
305
|
+
# X = rng.standard_normal((size, m))
|
|
306
|
+
#
|
|
307
|
+
# # Form the sample covariance matrix A = (1/m)*XX^T.
|
|
308
|
+
# A = X @ X.T / m
|
|
309
|
+
#
|
|
310
|
+
# return A
|
|
311
|
+
|
|
312
|
+
pass
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
|
|
14
14
|
import numpy
|
|
15
15
|
from scipy.interpolate import interp1d
|
|
16
|
-
from .._plot_util import plot_density, plot_hilbert,
|
|
17
|
-
plot_stieltjes_on_disk, plot_samples
|
|
16
|
+
from .._freeform._plot_util import plot_density, plot_hilbert, \
|
|
17
|
+
plot_stieltjes, plot_stieltjes_on_disk, plot_samples
|
|
18
18
|
|
|
19
19
|
try:
|
|
20
20
|
from scipy.integrate import cumtrapz
|