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.
Files changed (48) hide show
  1. freealg/__init__.py +8 -7
  2. freealg/__version__.py +1 -1
  3. freealg/_algebraic_form/__init__.py +11 -0
  4. freealg/_algebraic_form/_continuation_algebraic.py +503 -0
  5. freealg/_algebraic_form/_decompress.py +648 -0
  6. freealg/_algebraic_form/_edge.py +352 -0
  7. freealg/_algebraic_form/_sheets_util.py +145 -0
  8. freealg/_algebraic_form/algebraic_form.py +987 -0
  9. freealg/_freeform/__init__.py +16 -0
  10. freealg/_freeform/_density_util.py +243 -0
  11. freealg/{_linalg.py → _freeform/_linalg.py} +1 -1
  12. freealg/{freeform.py → _freeform/freeform.py} +2 -1
  13. freealg/_geometric_form/__init__.py +13 -0
  14. freealg/_geometric_form/_continuation_genus0.py +175 -0
  15. freealg/_geometric_form/_continuation_genus1.py +275 -0
  16. freealg/_geometric_form/_elliptic_functions.py +174 -0
  17. freealg/_geometric_form/_sphere_maps.py +63 -0
  18. freealg/_geometric_form/_torus_maps.py +118 -0
  19. freealg/_geometric_form/geometric_form.py +1094 -0
  20. freealg/_util.py +1 -228
  21. freealg/distributions/__init__.py +5 -1
  22. freealg/distributions/_chiral_block.py +440 -0
  23. freealg/distributions/_deformed_marchenko_pastur.py +617 -0
  24. freealg/distributions/_deformed_wigner.py +312 -0
  25. freealg/distributions/_kesten_mckay.py +2 -2
  26. freealg/distributions/_marchenko_pastur.py +199 -82
  27. freealg/distributions/_meixner.py +2 -2
  28. freealg/distributions/_wachter.py +2 -2
  29. freealg/distributions/_wigner.py +2 -2
  30. freealg/visualization/__init__.py +12 -0
  31. freealg/visualization/_glue_util.py +32 -0
  32. freealg/visualization/_rgb_hsv.py +125 -0
  33. {freealg-0.6.3.dist-info → freealg-0.7.1.dist-info}/METADATA +1 -1
  34. freealg-0.7.1.dist-info/RECORD +47 -0
  35. freealg-0.6.3.dist-info/RECORD +0 -26
  36. /freealg/{_chebyshev.py → _freeform/_chebyshev.py} +0 -0
  37. /freealg/{_damp.py → _freeform/_damp.py} +0 -0
  38. /freealg/{_decompress.py → _freeform/_decompress.py} +0 -0
  39. /freealg/{_jacobi.py → _freeform/_jacobi.py} +0 -0
  40. /freealg/{_pade.py → _freeform/_pade.py} +0 -0
  41. /freealg/{_plot_util.py → _freeform/_plot_util.py} +0 -0
  42. /freealg/{_sample.py → _freeform/_sample.py} +0 -0
  43. /freealg/{_series.py → _freeform/_series.py} +0 -0
  44. /freealg/{_support.py → _freeform/_support.py} +0 -0
  45. {freealg-0.6.3.dist-info → freealg-0.7.1.dist-info}/WHEEL +0 -0
  46. {freealg-0.6.3.dist-info → freealg-0.7.1.dist-info}/licenses/AUTHORS.txt +0 -0
  47. {freealg-0.6.3.dist-info → freealg-0.7.1.dist-info}/licenses/LICENSE.txt +0 -0
  48. {freealg-0.6.3.dist-info → freealg-0.7.1.dist-info}/top_level.txt +0 -0
@@ -13,8 +13,9 @@
13
13
 
14
14
  import numpy
15
15
  from scipy.interpolate import interp1d
16
- from .._plot_util import plot_density, plot_hilbert, plot_stieltjes, \
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
+ 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.lam_p = (1 + numpy.sqrt(self.lam))**2
104
- self.lam_m = (1 - numpy.sqrt(self.lam))**2
105
- self.support = (self.lam_m, self.lam_p)
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 support interval of the spectral density
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
- assumed to the save filename (with the file extension). This option
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] = (1.0 / (2.0 * numpy.pi * x[mask] * self.lam)) * \
175
- numpy.sqrt((self.lam_p - x[mask]) * (x[mask] - self.lam_m))
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 support interval of 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.support, latex=latex, save=save)
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
- Stieltjes transform (principal or secondary branch)
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
- sigma = 1.0
275
- m = numpy.empty_like(z, dtype=complex)
340
+ sq = numpy.sqrt(z)
341
+ sq = numpy.where(sq.imag < 0, -sq, sq)
276
342
 
277
- # When z is too small, do not use quadratic form.
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[not_mask]
287
- B = z[not_mask] - 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
- mask_p = z.imag >= 0.0
314
- mask_n = z.imag < 0.0
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
- m = numpy.zeros_like(z)
390
+ not_mask = ~mask
391
+ if numpy.any(not_mask):
392
+ sign = -1 if alt_branch else 1
317
393
 
318
- f = self._m_mp_numeric_vectorized
319
- m[mask_p] = f(z[mask_p], alt_branch=False)
320
- m[mask_n] = f(z[mask_n], alt_branch=alt_branch)
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, x=None, y=None, plot=False, on_disk=False, latex=False,
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 support interval of
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, m2_D, support=self.support,
514
+ plot_stieltjes_on_disk(r, t, m1_D, m12_D, support=self.supp,
414
515
  latex=latex, save=save)
415
516
 
416
- return m1_D, m2_D
417
-
418
- # Create x if not given
419
- if x is None:
420
- radius = 0.5 * (self.lam_p - self.lam_m)
421
- center = 0.5 * (self.lam_p + self.lam_m)
422
- scale = 2.0
423
- x_min = numpy.floor(2.0 * (center - 2.0 * radius * scale)) / 2.0
424
- x_max = numpy.ceil(2.0 * (center + 2.0 * radius * scale)) / 2.0
425
- x = numpy.linspace(x_min, x_max, 500)
426
-
427
- # Create y if not given
428
- if y is None:
429
- y = numpy.linspace(-1, 1, 400)
430
-
431
- x_grid, y_grid = numpy.meshgrid(x, y)
432
- z = x_grid + 1j * y_grid # shape (Ny, Nx)
433
-
434
- m1 = self._m_mp_reflect(z, alt_branch=False)
435
- m2 = self._m_mp_reflect(z, alt_branch=True)
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
- plot_stieltjes(x, y, m1, m2, support=self.support, latex=latex,
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
- return m1, m2
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 support
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 support
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'``
@@ -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, plot_stieltjes, \
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
@@ -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, plot_stieltjes, \
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
@@ -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, plot_stieltjes, \
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
@@ -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.6.3
3
+ Version: 0.7.1
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
@@ -0,0 +1,47 @@
1
+ freealg/__init__.py,sha256=ApA8Dl6Sm29NN_hrT-YRmLFLhrXWEb5N1mc0e0_tDDE,832
2
+ freealg/__version__.py,sha256=2KJZDSMOG7KS82AxYOrZ4ZihYxX0wjfUjDsIZh3L024,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=Abnpz7cRUPvReqbkfXvOh7onrjjvuLDdUUx3wFpu1yA,20088
35
+ freealg/distributions/_marchenko_pastur.py,sha256=G8nzyeZ2kfNIHfv4D2p0uN_9_0Z98JqKvr9PvV9k4o8,20645
36
+ freealg/distributions/_meixner.py,sha256=oxtXpFhEnehPuGHp8d0XEdGCGwmP_KoNJABZRV5GgcQ,17589
37
+ freealg/distributions/_wachter.py,sha256=qn8-_vBoCcpOtsuxXZK6vGd8XABqAgT5wO5FmoSvrc8,17089
38
+ freealg/distributions/_wigner.py,sha256=C8pTM0LMrU79TYIVRvcgPUACG9mwRzMRajS-98YNHPw,16068
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.1.dist-info/licenses/AUTHORS.txt,sha256=0b67Nz4_JgIzUupHJTAZxu5QdSUM_HRM_X_w4xCb17o,30
43
+ freealg-0.7.1.dist-info/licenses/LICENSE.txt,sha256=J-EEYEtxb3VVf_Bn1TYfWnpY5lMFIM15iLDDcnaDTPA,1443
44
+ freealg-0.7.1.dist-info/METADATA,sha256=xd4C9NmSiyjjLBVQjxFqzMx8VVpQ2rCK-x4tD55afEs,5516
45
+ freealg-0.7.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
46
+ freealg-0.7.1.dist-info/top_level.txt,sha256=eR2wrgYwDdnnJ9Zf5PruPqe4kQav0GMvRsqct6y00Q8,8
47
+ freealg-0.7.1.dist-info/RECORD,,