freealg 0.6.2__py3-none-any.whl → 0.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- freealg/__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/{_decompress.py → _freeform/_decompress.py} +0 -10
- freealg/_freeform/_density_util.py +243 -0
- freealg/{_linalg.py → _freeform/_linalg.py} +1 -1
- freealg/{_pade.py → _freeform/_pade.py} +0 -1
- freealg/{freeform.py → _freeform/freeform.py} +2 -31
- 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 -217
- 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/_marchenko_pastur.py +197 -80
- freealg/visualization/__init__.py +12 -0
- freealg/visualization/_glue_util.py +32 -0
- freealg/visualization/_rgb_hsv.py +125 -0
- {freealg-0.6.2.dist-info → freealg-0.7.0.dist-info}/METADATA +9 -11
- freealg-0.7.0.dist-info/RECORD +47 -0
- freealg-0.6.2.dist-info/RECORD +0 -26
- /freealg/{_chebyshev.py → _freeform/_chebyshev.py} +0 -0
- /freealg/{_damp.py → _freeform/_damp.py} +0 -0
- /freealg/{_jacobi.py → _freeform/_jacobi.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.2.dist-info → freealg-0.7.0.dist-info}/WHEEL +0 -0
- {freealg-0.6.2.dist-info → freealg-0.7.0.dist-info}/licenses/AUTHORS.txt +0 -0
- {freealg-0.6.2.dist-info → freealg-0.7.0.dist-info}/licenses/LICENSE.txt +0 -0
- {freealg-0.6.2.dist-info → freealg-0.7.0.dist-info}/top_level.txt +0 -0
|
@@ -15,6 +15,7 @@ import numpy
|
|
|
15
15
|
from scipy.interpolate import interp1d
|
|
16
16
|
from .._plot_util import plot_density, plot_hilbert, plot_stieltjes, \
|
|
17
17
|
plot_stieltjes_on_disk, plot_samples
|
|
18
|
+
from ..visualization import glue_branches
|
|
18
19
|
|
|
19
20
|
try:
|
|
20
21
|
from scipy.integrate import cumtrapz
|
|
@@ -94,15 +95,20 @@ class MarchenkoPastur(object):
|
|
|
94
95
|
# init
|
|
95
96
|
# ====
|
|
96
97
|
|
|
97
|
-
def __init__(self, lam):
|
|
98
|
+
def __init__(self, lam, sigma=1.0):
|
|
98
99
|
"""
|
|
99
100
|
Initialization.
|
|
100
101
|
"""
|
|
101
102
|
|
|
102
103
|
self.lam = lam
|
|
103
|
-
self.
|
|
104
|
-
|
|
105
|
-
self.
|
|
104
|
+
self.sigma = sigma
|
|
105
|
+
|
|
106
|
+
# self.lam_p = (1 + numpy.sqrt(self.lam))**2
|
|
107
|
+
# self.lam_m = (1 - numpy.sqrt(self.lam))**2
|
|
108
|
+
self.lam_p = sigma**2 * (1.0 + numpy.sqrt(lam))**2
|
|
109
|
+
self.lam_m = sigma**2 * (1.0 - numpy.sqrt(lam))**2
|
|
110
|
+
|
|
111
|
+
self.supp = (self.lam_m, self.lam_p)
|
|
106
112
|
|
|
107
113
|
# =======
|
|
108
114
|
# density
|
|
@@ -117,7 +123,7 @@ class MarchenkoPastur(object):
|
|
|
117
123
|
|
|
118
124
|
x : numpy.array, default=None
|
|
119
125
|
The locations where density is evaluated at. If `None`, an interval
|
|
120
|
-
slightly larger than the
|
|
126
|
+
slightly larger than the supp interval of the spectral density
|
|
121
127
|
is used.
|
|
122
128
|
|
|
123
129
|
rho : numpy.array, default=None
|
|
@@ -132,7 +138,7 @@ class MarchenkoPastur(object):
|
|
|
132
138
|
|
|
133
139
|
save : bool, default=False
|
|
134
140
|
If not `False`, the plot is saved. If a string is given, it is
|
|
135
|
-
|
|
141
|
+
assumed to the save filename (with the file extension). This option
|
|
136
142
|
is relevant only if ``plot=True``.
|
|
137
143
|
|
|
138
144
|
eig : numpy.array, default=None
|
|
@@ -168,11 +174,21 @@ class MarchenkoPastur(object):
|
|
|
168
174
|
x_max = numpy.ceil(center + radius * scale)
|
|
169
175
|
x = numpy.linspace(x_min, x_max, 500)
|
|
170
176
|
|
|
177
|
+
# Unpack parameters
|
|
178
|
+
lam = self.lam
|
|
179
|
+
lam_p = self.lam_p
|
|
180
|
+
lam_m = self.lam_m
|
|
181
|
+
sigma = self.sigma
|
|
182
|
+
|
|
171
183
|
rho = numpy.zeros_like(x)
|
|
172
|
-
mask = numpy.logical_and(x >= self.lam_m, x <= self.lam_p)
|
|
184
|
+
# mask = numpy.logical_and(x >= self.lam_m, x <= self.lam_p)
|
|
185
|
+
mask = (x > lam_m) & (x < lam_p)
|
|
186
|
+
|
|
187
|
+
# rho[mask] = (1.0 / (2.0 * numpy.pi * x[mask] * self.lam)) * \
|
|
188
|
+
# numpy.sqrt((self.lam_p - x[mask]) * (x[mask] - self.lam_m))
|
|
173
189
|
|
|
174
|
-
rho[mask] =
|
|
175
|
-
|
|
190
|
+
rho[mask] = numpy.sqrt((lam_p - x[mask]) * (x[mask] - lam_m)) / \
|
|
191
|
+
(lam * x[mask] * 2 * numpy.pi * sigma**2)
|
|
176
192
|
|
|
177
193
|
if plot:
|
|
178
194
|
if eig is not None:
|
|
@@ -183,6 +199,17 @@ class MarchenkoPastur(object):
|
|
|
183
199
|
|
|
184
200
|
return rho
|
|
185
201
|
|
|
202
|
+
# =======
|
|
203
|
+
# support
|
|
204
|
+
# =======
|
|
205
|
+
|
|
206
|
+
def support(self):
|
|
207
|
+
"""
|
|
208
|
+
supp
|
|
209
|
+
"""
|
|
210
|
+
|
|
211
|
+
return [self.supp]
|
|
212
|
+
|
|
186
213
|
# =======
|
|
187
214
|
# hilbert
|
|
188
215
|
# =======
|
|
@@ -196,7 +223,7 @@ class MarchenkoPastur(object):
|
|
|
196
223
|
|
|
197
224
|
x : numpy.array, default=None
|
|
198
225
|
The locations where Hilbert transform is evaluated at. If `None`,
|
|
199
|
-
an interval slightly larger than the
|
|
226
|
+
an interval slightly larger than the supp interval of the
|
|
200
227
|
spectral density is used.
|
|
201
228
|
|
|
202
229
|
plot : bool, default=False
|
|
@@ -257,7 +284,7 @@ class MarchenkoPastur(object):
|
|
|
257
284
|
hilb = -hilb
|
|
258
285
|
|
|
259
286
|
if plot:
|
|
260
|
-
plot_hilbert(x, hilb, support=self.
|
|
287
|
+
plot_hilbert(x, hilb, support=self.supp, latex=latex, save=save)
|
|
261
288
|
|
|
262
289
|
return hilb
|
|
263
290
|
|
|
@@ -265,59 +292,126 @@ class MarchenkoPastur(object):
|
|
|
265
292
|
# m mp numeric vectorized
|
|
266
293
|
# =======================
|
|
267
294
|
|
|
268
|
-
def _m_mp_numeric_vectorized(self, z, alt_branch=False, tol=1e-8):
|
|
295
|
+
# def _m_mp_numeric_vectorized(self, z, alt_branch=False, tol=1e-8):
|
|
296
|
+
# """
|
|
297
|
+
# Stieltjes transform (principal or secondary branch)
|
|
298
|
+
# for Marchenko-Pastur distribution on upper half-plane.
|
|
299
|
+
# """
|
|
300
|
+
#
|
|
301
|
+
# sigma = 1.0
|
|
302
|
+
# m = numpy.empty_like(z, dtype=complex)
|
|
303
|
+
#
|
|
304
|
+
# # When z is too small, do not use quadratic form.
|
|
305
|
+
# mask = numpy.abs(z) < tol
|
|
306
|
+
# m[mask] = 1 / (sigma**2 * (1 - self.lam))
|
|
307
|
+
#
|
|
308
|
+
# # Use quadratic form
|
|
309
|
+
# not_mask = ~mask
|
|
310
|
+
# if numpy.any(not_mask):
|
|
311
|
+
#
|
|
312
|
+
# sign = -1 if alt_branch else 1
|
|
313
|
+
# A = self.lam * sigma**2 * z[not_mask]
|
|
314
|
+
# B = z[not_mask] - sigma**2 * (1 - self.lam)
|
|
315
|
+
# D = B**2 - 4 * A
|
|
316
|
+
# sqrtD = numpy.sqrt(D)
|
|
317
|
+
# m1 = (-B + sqrtD) / (2 * A)
|
|
318
|
+
# m2 = (-B - sqrtD) / (2 * A)
|
|
319
|
+
#
|
|
320
|
+
# # pick correct branch only for non-masked entries
|
|
321
|
+
# upper = z[not_mask].imag >= 0
|
|
322
|
+
# branch = numpy.empty_like(m1)
|
|
323
|
+
# branch[upper] = numpy.where(sign*m1[upper].imag > 0, m1[upper],
|
|
324
|
+
# m2[upper])
|
|
325
|
+
# branch[~upper] = numpy.where(sign*m1[~upper].imag < 0,
|
|
326
|
+
# m1[~upper], m2[~upper])
|
|
327
|
+
# m[not_mask] = branch
|
|
328
|
+
#
|
|
329
|
+
# return m
|
|
330
|
+
|
|
331
|
+
# =============
|
|
332
|
+
# sqrt pos imag
|
|
333
|
+
# =============
|
|
334
|
+
|
|
335
|
+
def _sqrt_pos_imag(self, z):
|
|
269
336
|
"""
|
|
270
|
-
|
|
271
|
-
for Marchenko-Pastur distribution on upper half-plane.
|
|
337
|
+
Square root on a branch cut with always positive imaginary part.
|
|
272
338
|
"""
|
|
273
339
|
|
|
274
|
-
|
|
275
|
-
|
|
340
|
+
sq = numpy.sqrt(z)
|
|
341
|
+
sq = numpy.where(sq.imag < 0, -sq, sq)
|
|
276
342
|
|
|
277
|
-
|
|
278
|
-
mask = numpy.abs(z) < tol
|
|
279
|
-
m[mask] = 1 / (sigma**2 * (1 - self.lam))
|
|
280
|
-
|
|
281
|
-
# Use quadratic form
|
|
282
|
-
not_mask = ~mask
|
|
283
|
-
if numpy.any(not_mask):
|
|
284
|
-
|
|
285
|
-
sign = -1 if alt_branch else 1
|
|
286
|
-
A = self.lam * sigma**2 * z
|
|
287
|
-
B = z - sigma**2 * (1 - self.lam)
|
|
288
|
-
D = B**2 - 4 * A
|
|
289
|
-
sqrtD = numpy.sqrt(D)
|
|
290
|
-
m1 = (-B + sqrtD) / (2 * A)
|
|
291
|
-
m2 = (-B - sqrtD) / (2 * A)
|
|
292
|
-
|
|
293
|
-
# pick correct branch only for non-masked entries
|
|
294
|
-
upper = z[not_mask].imag >= 0
|
|
295
|
-
branch = numpy.empty_like(m1)
|
|
296
|
-
branch[upper] = numpy.where(sign*m1[upper].imag > 0, m1[upper],
|
|
297
|
-
m2[upper])
|
|
298
|
-
branch[~upper] = numpy.where(sign*m1[~upper].imag < 0, m1[~upper],
|
|
299
|
-
m2[~upper])
|
|
300
|
-
m[not_mask] = branch
|
|
301
|
-
|
|
302
|
-
return m
|
|
343
|
+
return sq
|
|
303
344
|
|
|
304
345
|
# ============
|
|
305
346
|
# m mp reflect
|
|
306
347
|
# ============
|
|
307
348
|
|
|
308
|
-
def _m_mp_reflect(self, z, alt_branch=False):
|
|
349
|
+
# def _m_mp_reflect(self, z, alt_branch=False):
|
|
350
|
+
# """
|
|
351
|
+
# Analytic continuation using Schwarz reflection.
|
|
352
|
+
# """
|
|
353
|
+
#
|
|
354
|
+
# mask_p = z.imag >= 0.0
|
|
355
|
+
# mask_n = z.imag < 0.0
|
|
356
|
+
#
|
|
357
|
+
# m = numpy.zeros_like(z)
|
|
358
|
+
#
|
|
359
|
+
# f = self._m_mp_numeric_vectorized
|
|
360
|
+
# m[mask_p] = f(z[mask_p], alt_branch=False)
|
|
361
|
+
# m[mask_n] = f(z[mask_n], alt_branch=alt_branch)
|
|
362
|
+
#
|
|
363
|
+
# return m
|
|
364
|
+
|
|
365
|
+
# ================
|
|
366
|
+
# stieltjes branch
|
|
367
|
+
# ================
|
|
368
|
+
|
|
369
|
+
def _stieltjes_branch(self, z, alt_branch=False, tol=1e-8):
|
|
309
370
|
"""
|
|
310
|
-
Analytic continuation using Schwarz reflection.
|
|
311
371
|
"""
|
|
312
372
|
|
|
313
|
-
|
|
314
|
-
|
|
373
|
+
# Unpack parameters
|
|
374
|
+
lam = self.lam
|
|
375
|
+
sigma = self.sigma
|
|
376
|
+
|
|
377
|
+
z = numpy.asarray(z, dtype=complex)
|
|
378
|
+
m = numpy.empty_like(z, dtype=complex)
|
|
379
|
+
|
|
380
|
+
def _eval_upper(zu):
|
|
381
|
+
mu = numpy.empty_like(zu, dtype=complex)
|
|
382
|
+
|
|
383
|
+
mask = numpy.abs(zu) < tol
|
|
384
|
+
if numpy.any(mask):
|
|
385
|
+
if alt_branch:
|
|
386
|
+
mu[mask] = numpy.inf + 0.0j
|
|
387
|
+
else:
|
|
388
|
+
mu[mask] = 1.0 / (sigma**2 * (1.0 - lam))
|
|
315
389
|
|
|
316
|
-
|
|
390
|
+
not_mask = ~mask
|
|
391
|
+
if numpy.any(not_mask):
|
|
392
|
+
sign = -1 if alt_branch else 1
|
|
317
393
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
394
|
+
A = lam * sigma**2 * zu[not_mask]
|
|
395
|
+
B = zu[not_mask] - sigma**2 * (1.0 - lam)
|
|
396
|
+
D = B**2 - 4.0 * A
|
|
397
|
+
|
|
398
|
+
sqrtD = self._sqrt_pos_imag(D)
|
|
399
|
+
|
|
400
|
+
r1 = (-B + sqrtD) / (2.0 * A)
|
|
401
|
+
r2 = (-B - sqrtD) / (2.0 * A)
|
|
402
|
+
|
|
403
|
+
mu[not_mask] = numpy.where(sign * r1.imag > 0.0, r1, r2)
|
|
404
|
+
|
|
405
|
+
return mu
|
|
406
|
+
|
|
407
|
+
mask_p = numpy.imag(z) >= 0.0
|
|
408
|
+
if numpy.any(mask_p):
|
|
409
|
+
m[mask_p] = _eval_upper(z[mask_p])
|
|
410
|
+
|
|
411
|
+
mask_n = ~mask_p
|
|
412
|
+
if numpy.any(mask_n):
|
|
413
|
+
z_ref = numpy.conjugate(z[mask_n])
|
|
414
|
+
m[mask_n] = numpy.conjugate(_eval_upper(z_ref))
|
|
321
415
|
|
|
322
416
|
return m
|
|
323
417
|
|
|
@@ -325,8 +419,8 @@ class MarchenkoPastur(object):
|
|
|
325
419
|
# stieltjes
|
|
326
420
|
# =========
|
|
327
421
|
|
|
328
|
-
def stieltjes(self,
|
|
329
|
-
save=False):
|
|
422
|
+
def stieltjes(self, z=None, x=None, y=None, alt_branch='both', plot=False,
|
|
423
|
+
on_disk=False, latex=False, save=False):
|
|
330
424
|
"""
|
|
331
425
|
Stieltjes transform of distribution.
|
|
332
426
|
|
|
@@ -335,13 +429,17 @@ class MarchenkoPastur(object):
|
|
|
335
429
|
|
|
336
430
|
x : numpy.array, default=None
|
|
337
431
|
The x axis of the grid where the Stieltjes transform is evaluated.
|
|
338
|
-
If `None`, an interval slightly larger than the
|
|
432
|
+
If `None`, an interval slightly larger than the supp interval of
|
|
339
433
|
the spectral density is used.
|
|
340
434
|
|
|
341
435
|
y : numpy.array, default=None
|
|
342
436
|
The y axis of the grid where the Stieltjes transform is evaluated.
|
|
343
437
|
If `None`, a grid on the interval ``[-1, 1]`` is used.
|
|
344
438
|
|
|
439
|
+
alt_branch : {``True``, ``False``, ``'both'``} default=``'both'``
|
|
440
|
+
If `True`, returns non-physical branch. If `False`, returns
|
|
441
|
+
physical branch. If ``'both'``, returns both.
|
|
442
|
+
|
|
345
443
|
plot : bool, default=False
|
|
346
444
|
If `True`, Stieltjes transform is plotted.
|
|
347
445
|
|
|
@@ -407,38 +505,57 @@ class MarchenkoPastur(object):
|
|
|
407
505
|
# Cayley transform mapping zeta on D to z on H
|
|
408
506
|
z_H = 1j * (1 + zeta) / (1 - zeta)
|
|
409
507
|
|
|
410
|
-
m1_D = self._m_mp_reflect(z_H, alt_branch=False)
|
|
411
|
-
m2_D = self._m_mp_reflect(z_H, alt_branch=True)
|
|
508
|
+
# m1_D = self._m_mp_reflect(z_H, alt_branch=False)
|
|
509
|
+
# m2_D = self._m_mp_reflect(z_H, alt_branch=True)
|
|
510
|
+
m1_D = self._stieltjes_branch(z_H, alt_branch=False)
|
|
511
|
+
m2_D = self._stieltjes_branch(z_H, alt_branch=True)
|
|
512
|
+
m12_D = glue_branches(z_H, m1_D, m2_D)
|
|
412
513
|
|
|
413
|
-
plot_stieltjes_on_disk(r, t, m1_D,
|
|
514
|
+
plot_stieltjes_on_disk(r, t, m1_D, m12_D, support=self.supp,
|
|
414
515
|
latex=latex, save=save)
|
|
415
516
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
x
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
517
|
+
if alt_branch == 'both':
|
|
518
|
+
return m1_D, m2_D
|
|
519
|
+
elif alt_branch is True:
|
|
520
|
+
return m2_D
|
|
521
|
+
else:
|
|
522
|
+
return m1_D
|
|
523
|
+
|
|
524
|
+
if z is None:
|
|
525
|
+
# Create x if not given
|
|
526
|
+
if x is None:
|
|
527
|
+
radius = 0.5 * (self.lam_p - self.lam_m)
|
|
528
|
+
center = 0.5 * (self.lam_p + self.lam_m)
|
|
529
|
+
scale = 2.0
|
|
530
|
+
x_min = numpy.floor(
|
|
531
|
+
2.0 * (center - 2.0 * radius * scale)) / 2.0
|
|
532
|
+
x_max = numpy.ceil(
|
|
533
|
+
2.0 * (center + 2.0 * radius * scale)) / 2.0
|
|
534
|
+
x = numpy.linspace(x_min, x_max, 500)
|
|
535
|
+
|
|
536
|
+
# Create y if not given
|
|
537
|
+
if y is None:
|
|
538
|
+
y = numpy.linspace(-1, 1, 400)
|
|
539
|
+
|
|
540
|
+
x_grid, y_grid = numpy.meshgrid(x, y)
|
|
541
|
+
z = x_grid + 1j * y_grid # shape (Ny, Nx)
|
|
542
|
+
|
|
543
|
+
# m1 = self._m_mp_reflect(z, alt_branch=False)
|
|
544
|
+
# m2 = self._m_mp_reflect(z, alt_branch=True)
|
|
545
|
+
m1 = self._stieltjes_branch(z, alt_branch=False)
|
|
546
|
+
m2 = self._stieltjes_branch(z, alt_branch=True)
|
|
436
547
|
|
|
437
548
|
if plot:
|
|
438
|
-
|
|
549
|
+
m12 = glue_branches(z, m1, m2)
|
|
550
|
+
plot_stieltjes(x, y, m1, m12, support=self.supp, latex=latex,
|
|
439
551
|
save=save)
|
|
440
552
|
|
|
441
|
-
|
|
553
|
+
if alt_branch == 'both':
|
|
554
|
+
return m1, m2
|
|
555
|
+
elif alt_branch is True:
|
|
556
|
+
return m2
|
|
557
|
+
else:
|
|
558
|
+
return m1
|
|
442
559
|
|
|
443
560
|
# ======
|
|
444
561
|
# sample
|
|
@@ -456,11 +573,11 @@ class MarchenkoPastur(object):
|
|
|
456
573
|
Size of sample.
|
|
457
574
|
|
|
458
575
|
x_min : float, default=None
|
|
459
|
-
Minimum of sample values. If `None`, the left edge of the
|
|
576
|
+
Minimum of sample values. If `None`, the left edge of the supp
|
|
460
577
|
is used.
|
|
461
578
|
|
|
462
579
|
x_max : float, default=None
|
|
463
|
-
Maximum of sample values. If `None`, the right edge of the
|
|
580
|
+
Maximum of sample values. If `None`, the right edge of the supp
|
|
464
581
|
is used.
|
|
465
582
|
|
|
466
583
|
method : {``'mc'``, ``'qmc'``}, default= ``'qmc'``
|
|
@@ -0,0 +1,12 @@
|
|
|
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
|
+
from ._rgb_hsv import rgb_hsv
|
|
10
|
+
from ._glue_util import glue_branches
|
|
11
|
+
|
|
12
|
+
__all__ = ['rgb_hsv', 'glue_branches']
|
|
@@ -0,0 +1,32 @@
|
|
|
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
|
+
|
|
16
|
+
__all__ = ['glue_branches']
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# =============
|
|
20
|
+
# glue branches
|
|
21
|
+
# =============
|
|
22
|
+
|
|
23
|
+
def glue_branches(z, m1, m2):
|
|
24
|
+
"""
|
|
25
|
+
m12 is the mixing of m1 and m2 where it contains m1 on C^+ and m2 on C^-.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
m12 = numpy.array(m2, copy=True)
|
|
29
|
+
mask_p = numpy.imag(z) >= 0.0
|
|
30
|
+
m12[mask_p] = m1[mask_p]
|
|
31
|
+
|
|
32
|
+
return m12
|
|
@@ -0,0 +1,125 @@
|
|
|
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 under
|
|
6
|
+
# the terms of the license found in the LICENSE.txt file in the root directory
|
|
7
|
+
# of this source tree.
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# =======
|
|
11
|
+
# Imports
|
|
12
|
+
# =======
|
|
13
|
+
|
|
14
|
+
import numpy
|
|
15
|
+
import matplotlib
|
|
16
|
+
|
|
17
|
+
__all__ = ['rgb_hsv']
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# =======
|
|
21
|
+
# rgb hsv
|
|
22
|
+
# =======
|
|
23
|
+
|
|
24
|
+
def rgb_hsv(c, shift=0.0, thresh=numpy.inf, n_mod=12.0, n_ph=12.0, vmin=0.35,
|
|
25
|
+
vmax=1.0, tile_gamma=1.0, tile_mix=1.0):
|
|
26
|
+
"""
|
|
27
|
+
Convert complex field c to RGB via HSV domain coloring.
|
|
28
|
+
|
|
29
|
+
Parameters
|
|
30
|
+
----------
|
|
31
|
+
c : array_like of complex
|
|
32
|
+
Complex field.
|
|
33
|
+
|
|
34
|
+
shift : float, default 0.0
|
|
35
|
+
Phase offset in turns (1.0 = full 2*pi rotation). Applied to hue.
|
|
36
|
+
|
|
37
|
+
thresh : float, default numpy.inf
|
|
38
|
+
Optional cap on |c| used for magnitude-related terms. Use to prevent
|
|
39
|
+
very large magnitudes from dominating the encoding.
|
|
40
|
+
|
|
41
|
+
n_mod : float, default 12.0
|
|
42
|
+
Number of modulus steps per 2*pi in log(|c|). Higher -> more concentric
|
|
43
|
+
rings. Set to 0.0 to disable modulus stepping.
|
|
44
|
+
|
|
45
|
+
n_ph : float, default 12.0
|
|
46
|
+
Number of phase steps per 2*pi in arg(c). Higher -> more angular
|
|
47
|
+
sectors. Set to 0.0 to disable phase stepping.
|
|
48
|
+
|
|
49
|
+
vmin : float, default 0.35
|
|
50
|
+
Minimum brightness for the tiling shading (darkest parts of tiles).
|
|
51
|
+
|
|
52
|
+
vmax : float, default 1.0
|
|
53
|
+
Maximum brightness for the tiling shading (brightest parts of tiles).
|
|
54
|
+
Lowering vmax (e.g. 0.8-0.9) can reduce the "neon" look.
|
|
55
|
+
|
|
56
|
+
tile_gamma : float, default 1.0
|
|
57
|
+
Shapes the within-tile ramp. 1.0 = linear sawtooth. >1.0 makes tiles
|
|
58
|
+
stay darker longer and brighten sharply near boundaries. <1.0 brightens
|
|
59
|
+
earlier.
|
|
60
|
+
|
|
61
|
+
tile_mix : float in [0, 1], default 1.0
|
|
62
|
+
Mix between original magnitude brightness and tiling shading:
|
|
63
|
+
0.0 -> value = 1 - exp(-|c|) (your original, no tiling influence)
|
|
64
|
+
1.0 -> value = tiling shading only (Wegert-style tiling look)
|
|
65
|
+
Intermediate values overlay tiling onto the original magnitude shading.
|
|
66
|
+
|
|
67
|
+
Notes
|
|
68
|
+
-----
|
|
69
|
+
|
|
70
|
+
The coloring technique is inspired from [1]_.
|
|
71
|
+
|
|
72
|
+
References
|
|
73
|
+
----------
|
|
74
|
+
|
|
75
|
+
[1] Wegert, E. (2015) "Visual Complex Functions: An Introduction +with
|
|
76
|
+
Phase Portraits", Springer.
|
|
77
|
+
doi: https://doi.org/10.1007/978-3-0348-0180-5
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
hue = (numpy.angle(c) + numpy.pi) / (2.0 * numpy.pi)
|
|
81
|
+
|
|
82
|
+
hue = (hue + shift) % 1.0
|
|
83
|
+
|
|
84
|
+
r = numpy.abs(c)
|
|
85
|
+
if numpy.isfinite(thresh):
|
|
86
|
+
r = numpy.minimum(r, thresh)
|
|
87
|
+
|
|
88
|
+
value0 = 1.0 - numpy.exp(-r)
|
|
89
|
+
|
|
90
|
+
eps = 1e-300
|
|
91
|
+
tau = 2.0 * numpy.pi
|
|
92
|
+
|
|
93
|
+
g = numpy.ones_like(hue)
|
|
94
|
+
|
|
95
|
+
if n_mod and n_mod > 0.0:
|
|
96
|
+
x_mod = (n_mod / tau) * numpy.log(r + eps)
|
|
97
|
+
g_mod = numpy.ceil(x_mod) - x_mod
|
|
98
|
+
g = g * g_mod
|
|
99
|
+
|
|
100
|
+
if n_ph and n_ph > 0.0:
|
|
101
|
+
theta = (numpy.angle(c) + numpy.pi) % tau
|
|
102
|
+
|
|
103
|
+
x_ph = (n_ph / tau) * theta
|
|
104
|
+
g_ph = numpy.ceil(x_ph) - x_ph
|
|
105
|
+
g = g * g_ph
|
|
106
|
+
|
|
107
|
+
g = numpy.clip(g, 0.0, 1.0)
|
|
108
|
+
if tile_gamma and tile_gamma != 1.0:
|
|
109
|
+
g = g ** float(tile_gamma)
|
|
110
|
+
|
|
111
|
+
vmin = float(numpy.clip(vmin, 0.0, 1.0))
|
|
112
|
+
vmax = float(numpy.clip(vmax, 0.0, 1.0))
|
|
113
|
+
if vmax < vmin:
|
|
114
|
+
vmin, vmax = vmax, vmin
|
|
115
|
+
|
|
116
|
+
value_tile = vmin + (vmax - vmin) * g
|
|
117
|
+
|
|
118
|
+
tile_mix = float(numpy.clip(tile_mix, 0.0, 1.0))
|
|
119
|
+
value = (1.0 - tile_mix) * value0 + tile_mix * value_tile
|
|
120
|
+
|
|
121
|
+
saturation = numpy.ones_like(hue)
|
|
122
|
+
hsv = numpy.stack((hue, saturation, numpy.clip(value, 0.0, 1.0)), axis=-1)
|
|
123
|
+
rgb = matplotlib.colors.hsv_to_rgb(hsv)
|
|
124
|
+
|
|
125
|
+
return rgb
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: freealg
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: Free probability for large matrices
|
|
5
5
|
Home-page: https://github.com/ameli/freealg
|
|
6
6
|
Download-URL: https://github.com/ameli/freealg/archive/main.zip
|
|
@@ -142,19 +142,17 @@ code, we also welcome feature requests and bug reports.
|
|
|
142
142
|
How to Cite
|
|
143
143
|
===========
|
|
144
144
|
|
|
145
|
-
If you use this work, please cite our `
|
|
145
|
+
If you use this work, please cite our `paper <https://openreview.net/pdf?id=2CeGVUpOd7>`__.
|
|
146
146
|
|
|
147
147
|
.. code::
|
|
148
148
|
|
|
149
|
-
@
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
url={https://arxiv.org/abs/2506.11994},
|
|
157
|
-
journal={arXiv preprint arXiv:2506.11994},
|
|
149
|
+
@inproceedings{
|
|
150
|
+
AMELI-2025,
|
|
151
|
+
title={Spectral Estimation with Free Decompression},
|
|
152
|
+
author={Siavash Ameli and Chris van der Heide and Liam Hodgkinson and Michael W. Mahoney},
|
|
153
|
+
booktitle={The Thirty-ninth Annual Conference on Neural Information Processing Systems},
|
|
154
|
+
year={2025},
|
|
155
|
+
url={https://openreview.net/forum?id=2CeGVUpOd7}
|
|
158
156
|
}
|
|
159
157
|
|
|
160
158
|
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
freealg/__init__.py,sha256=ApA8Dl6Sm29NN_hrT-YRmLFLhrXWEb5N1mc0e0_tDDE,832
|
|
2
|
+
freealg/__version__.py,sha256=RaANGbRu5e-vehwXI1-Qe2ggPPfs1TQaZj072JdbLk4,22
|
|
3
|
+
freealg/_util.py,sha256=E254DRTCeST7h1QHrfLeyg9jQntRaOyui1oXD7nMaWQ,1890
|
|
4
|
+
freealg/_algebraic_form/__init__.py,sha256=MIB_jVgw2qI-JW_ypqaFSeNAB6c4GvpjNySnap_a6hg,398
|
|
5
|
+
freealg/_algebraic_form/_continuation_algebraic.py,sha256=y2ZKppTM41SBmhD3AsPN02c-MIuJWwrH1EqG385aOdY,13036
|
|
6
|
+
freealg/_algebraic_form/_decompress.py,sha256=gGtixLOVxlMy5S-NsXgoA7lIrB7u7nUZImQk1mIDo3s,21101
|
|
7
|
+
freealg/_algebraic_form/_edge.py,sha256=7l9QyLJDxaEY4WB6MCUFtfEZSf04wyHwH7YPHFJXSbM,10690
|
|
8
|
+
freealg/_algebraic_form/_sheets_util.py,sha256=6OLzWQKu-gN8rxM2rbpbN8TjNZFmD8UJ-8t9kcZdkCo,4174
|
|
9
|
+
freealg/_algebraic_form/algebraic_form.py,sha256=MFWS-zxacFprUWgFWDNdygIk4GFU6Bp-Ed_ns4wVBmk,28298
|
|
10
|
+
freealg/_freeform/__init__.py,sha256=fxHAqlCBK0AqLrK9VhA3FMeid8nutCy5UwsUSgkIhrI,610
|
|
11
|
+
freealg/_freeform/_chebyshev.py,sha256=zkyVA8NLf7uUKlJdLz4ijd_SurdsqUgkA5nHGWSybaE,6916
|
|
12
|
+
freealg/_freeform/_damp.py,sha256=k2vtBtWOxQBf4qXaWu_En81lQBXbEO4QbxxWpvuVhdE,1802
|
|
13
|
+
freealg/_freeform/_decompress.py,sha256=_i37IToZ6oN9DdLXOM8r4y92EbazzcylhcnWwUOpaj0,32108
|
|
14
|
+
freealg/_freeform/_density_util.py,sha256=C_0lKA5QzKAdxPRLJvajNO9zaw2QliZ-n7SI8g2a53M,6745
|
|
15
|
+
freealg/_freeform/_jacobi.py,sha256=z0X6Ws_BEo_h8EQBzDNHGFhLF9F2PUmnGeBVs0bNL7w,10709
|
|
16
|
+
freealg/_freeform/_linalg.py,sha256=U7b_FJwfjU1itO4R4K831Zm2rnUB5eNOdOzvjPSeH98,13142
|
|
17
|
+
freealg/_freeform/_pade.py,sha256=_y89r7rVc2E5lgiN_ZjnxzW2IaVevWwpq5ISor2NVOo,10310
|
|
18
|
+
freealg/_freeform/_plot_util.py,sha256=GKvmc1wjVGeqoomrULPbzBEt6P86FdoR2idBLYh5EDY,20068
|
|
19
|
+
freealg/_freeform/_sample.py,sha256=rhfd_83TCTvvJh8cG8TzEYO4OR8VbtND2YCNtWEhMa8,3205
|
|
20
|
+
freealg/_freeform/_series.py,sha256=33LLCUe4svmV0eWyzhP_XClfDzccQHTW9WBJlYlLfHY,11475
|
|
21
|
+
freealg/_freeform/_support.py,sha256=nxDa2OFlWBgjD0_1qoSMWG7kub6-GIuxIA04n5bdaYw,6614
|
|
22
|
+
freealg/_freeform/freeform.py,sha256=UaRLYSvA0ib5wy1xnLy3_9ndCJ6gUdhNswSm0m9-7go,43571
|
|
23
|
+
freealg/_geometric_form/__init__.py,sha256=mWsXP0nXs3pY8RfUDhPRTgIfhOigKqw_VmoWnJOw2a0,485
|
|
24
|
+
freealg/_geometric_form/_continuation_genus0.py,sha256=4jiXfQaA6w3IhVkJgtKVVjqqtBmavq78FY_YTGUQyY0,4026
|
|
25
|
+
freealg/_geometric_form/_continuation_genus1.py,sha256=X8NZ1_6PxhJJLXZk5ASeGwxej_KwH3-ftuXkBrOFmgU,6021
|
|
26
|
+
freealg/_geometric_form/_elliptic_functions.py,sha256=Rr_pb1A_FjrJlraYQj2G5shdO6f77aVQN2eQzrvIygI,4109
|
|
27
|
+
freealg/_geometric_form/_sphere_maps.py,sha256=NlhTgWXKWXKdyR2dQxMmePsIwHp7IWyYh6uoxgW5Osc,1465
|
|
28
|
+
freealg/_geometric_form/_torus_maps.py,sha256=7m5QsbmnXTWHJE5rWjKG3_TnErHEEQ41vW-3hsOc3lo,3338
|
|
29
|
+
freealg/_geometric_form/geometric_form.py,sha256=whHKYQdakqShtR-jCEugevnje72JEr9M0HSvZ2BYoKs,33379
|
|
30
|
+
freealg/distributions/__init__.py,sha256=POoSAPVazz83y79pfdKeevPtKqpSATNb8S3ocYMdEzI,798
|
|
31
|
+
freealg/distributions/_chiral_block.py,sha256=NxwD-2qx7-ewg5TwMALnPoesUNIWPDktXhyH8RSKi2w,12350
|
|
32
|
+
freealg/distributions/_deformed_marchenko_pastur.py,sha256=cZN3XnAxK4L2zkteE4XL5rlslRz34ArUlInc9TzQIZA,17568
|
|
33
|
+
freealg/distributions/_deformed_wigner.py,sha256=ZC7dj-ViSxaYdHRs45Yx6vf9Yw0WaTm4qZ7FumE0EEo,7239
|
|
34
|
+
freealg/distributions/_kesten_mckay.py,sha256=FB7UtMxkn3Olg6XmjUKrh5njhdttMo6HwW78fDBTDJk,20078
|
|
35
|
+
freealg/distributions/_marchenko_pastur.py,sha256=VBcM0ZnUYkoCPyVwqlxaIvnVWcbEzMe1HynfEH4cW-s,20635
|
|
36
|
+
freealg/distributions/_meixner.py,sha256=-gDYBShAXxtfe3vY4-2bSvp7QM8uU9eeDC9v_YVNxrk,17579
|
|
37
|
+
freealg/distributions/_wachter.py,sha256=Kv1qQF1MOFCli5-IDT5lZYRhqRXh4xCugEq7KSYbE4g,17079
|
|
38
|
+
freealg/distributions/_wigner.py,sha256=FBGhSijbex5oFztnV6laEGAiSWGdn1z9g9lSfhgQ_sQ,16058
|
|
39
|
+
freealg/visualization/__init__.py,sha256=NLq_zwueF7ytZ8sl8zLPqm-AODxxXNvfMozHGmmklcE,435
|
|
40
|
+
freealg/visualization/_glue_util.py,sha256=2oKnEYjUOS4OZfivmciVLauVr53kyHMwi6c2zRKilTQ,693
|
|
41
|
+
freealg/visualization/_rgb_hsv.py,sha256=rEskxXxSlKKxIrHRslVkgxHtD010L3ge9YtcVsOPl8E,3650
|
|
42
|
+
freealg-0.7.0.dist-info/licenses/AUTHORS.txt,sha256=0b67Nz4_JgIzUupHJTAZxu5QdSUM_HRM_X_w4xCb17o,30
|
|
43
|
+
freealg-0.7.0.dist-info/licenses/LICENSE.txt,sha256=J-EEYEtxb3VVf_Bn1TYfWnpY5lMFIM15iLDDcnaDTPA,1443
|
|
44
|
+
freealg-0.7.0.dist-info/METADATA,sha256=CysldpASDqA_l29no4cFs1td2_pi86TVRV2Hdkbe4p0,5516
|
|
45
|
+
freealg-0.7.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
46
|
+
freealg-0.7.0.dist-info/top_level.txt,sha256=eR2wrgYwDdnnJ9Zf5PruPqe4kQav0GMvRsqct6y00Q8,8
|
|
47
|
+
freealg-0.7.0.dist-info/RECORD,,
|