freealg 0.7.1__tar.gz → 0.7.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. {freealg-0.7.1 → freealg-0.7.4}/PKG-INFO +1 -1
  2. {freealg-0.7.1 → freealg-0.7.4}/freealg/__init__.py +1 -1
  3. freealg-0.7.4/freealg/__version__.py +1 -0
  4. {freealg-0.7.1 → freealg-0.7.4}/freealg/_algebraic_form/_continuation_algebraic.py +48 -5
  5. freealg-0.7.4/freealg/_algebraic_form/_decompress2.py +86 -0
  6. freealg-0.7.4/freealg/_algebraic_form/_homotopy.py +138 -0
  7. {freealg-0.7.1 → freealg-0.7.4}/freealg/_algebraic_form/algebraic_form.py +171 -114
  8. {freealg-0.7.1/freealg/_freeform → freealg-0.7.4/freealg/_free_form}/__init__.py +1 -1
  9. {freealg-0.7.1/freealg/_freeform → freealg-0.7.4/freealg/_free_form}/_linalg.py +1 -1
  10. {freealg-0.7.1/freealg/_freeform → freealg-0.7.4/freealg/_free_form}/_support.py +5 -5
  11. {freealg-0.7.1 → freealg-0.7.4}/freealg/distributions/_chiral_block.py +69 -15
  12. {freealg-0.7.1 → freealg-0.7.4}/freealg/distributions/_deformed_marchenko_pastur.py +71 -13
  13. {freealg-0.7.1 → freealg-0.7.4}/freealg/distributions/_deformed_wigner.py +46 -16
  14. {freealg-0.7.1 → freealg-0.7.4}/freealg/distributions/_kesten_mckay.py +1 -1
  15. {freealg-0.7.1 → freealg-0.7.4}/freealg/distributions/_marchenko_pastur.py +1 -1
  16. {freealg-0.7.1 → freealg-0.7.4}/freealg/distributions/_meixner.py +1 -1
  17. {freealg-0.7.1 → freealg-0.7.4}/freealg/distributions/_wachter.py +1 -1
  18. {freealg-0.7.1 → freealg-0.7.4}/freealg/distributions/_wigner.py +1 -1
  19. {freealg-0.7.1 → freealg-0.7.4}/freealg.egg-info/PKG-INFO +1 -1
  20. {freealg-0.7.1 → freealg-0.7.4}/freealg.egg-info/SOURCES.txt +15 -13
  21. freealg-0.7.1/freealg/__version__.py +0 -1
  22. {freealg-0.7.1 → freealg-0.7.4}/AUTHORS.txt +0 -0
  23. {freealg-0.7.1 → freealg-0.7.4}/CHANGELOG.rst +0 -0
  24. {freealg-0.7.1 → freealg-0.7.4}/LICENSE.txt +0 -0
  25. {freealg-0.7.1 → freealg-0.7.4}/MANIFEST.in +0 -0
  26. {freealg-0.7.1 → freealg-0.7.4}/README.rst +0 -0
  27. {freealg-0.7.1 → freealg-0.7.4}/freealg/_algebraic_form/__init__.py +0 -0
  28. {freealg-0.7.1 → freealg-0.7.4}/freealg/_algebraic_form/_decompress.py +0 -0
  29. {freealg-0.7.1 → freealg-0.7.4}/freealg/_algebraic_form/_edge.py +0 -0
  30. {freealg-0.7.1 → freealg-0.7.4}/freealg/_algebraic_form/_sheets_util.py +0 -0
  31. {freealg-0.7.1/freealg/_freeform → freealg-0.7.4/freealg/_free_form}/_chebyshev.py +0 -0
  32. {freealg-0.7.1/freealg/_freeform → freealg-0.7.4/freealg/_free_form}/_damp.py +0 -0
  33. {freealg-0.7.1/freealg/_freeform → freealg-0.7.4/freealg/_free_form}/_decompress.py +0 -0
  34. {freealg-0.7.1/freealg/_freeform → freealg-0.7.4/freealg/_free_form}/_density_util.py +0 -0
  35. {freealg-0.7.1/freealg/_freeform → freealg-0.7.4/freealg/_free_form}/_jacobi.py +0 -0
  36. {freealg-0.7.1/freealg/_freeform → freealg-0.7.4/freealg/_free_form}/_pade.py +0 -0
  37. {freealg-0.7.1/freealg/_freeform → freealg-0.7.4/freealg/_free_form}/_plot_util.py +0 -0
  38. {freealg-0.7.1/freealg/_freeform → freealg-0.7.4/freealg/_free_form}/_sample.py +0 -0
  39. {freealg-0.7.1/freealg/_freeform → freealg-0.7.4/freealg/_free_form}/_series.py +0 -0
  40. /freealg-0.7.1/freealg/_freeform/freeform.py → /freealg-0.7.4/freealg/_free_form/free_form.py +0 -0
  41. {freealg-0.7.1 → freealg-0.7.4}/freealg/_geometric_form/__init__.py +0 -0
  42. {freealg-0.7.1 → freealg-0.7.4}/freealg/_geometric_form/_continuation_genus0.py +0 -0
  43. {freealg-0.7.1 → freealg-0.7.4}/freealg/_geometric_form/_continuation_genus1.py +0 -0
  44. {freealg-0.7.1 → freealg-0.7.4}/freealg/_geometric_form/_elliptic_functions.py +0 -0
  45. {freealg-0.7.1 → freealg-0.7.4}/freealg/_geometric_form/_sphere_maps.py +0 -0
  46. {freealg-0.7.1 → freealg-0.7.4}/freealg/_geometric_form/_torus_maps.py +0 -0
  47. {freealg-0.7.1 → freealg-0.7.4}/freealg/_geometric_form/geometric_form.py +0 -0
  48. {freealg-0.7.1 → freealg-0.7.4}/freealg/_util.py +0 -0
  49. {freealg-0.7.1 → freealg-0.7.4}/freealg/distributions/__init__.py +0 -0
  50. {freealg-0.7.1 → freealg-0.7.4}/freealg/visualization/__init__.py +0 -0
  51. {freealg-0.7.1 → freealg-0.7.4}/freealg/visualization/_glue_util.py +0 -0
  52. {freealg-0.7.1 → freealg-0.7.4}/freealg/visualization/_rgb_hsv.py +0 -0
  53. {freealg-0.7.1 → freealg-0.7.4}/freealg.egg-info/dependency_links.txt +0 -0
  54. {freealg-0.7.1 → freealg-0.7.4}/freealg.egg-info/not-zip-safe +0 -0
  55. {freealg-0.7.1 → freealg-0.7.4}/freealg.egg-info/requires.txt +0 -0
  56. {freealg-0.7.1 → freealg-0.7.4}/freealg.egg-info/top_level.txt +0 -0
  57. {freealg-0.7.1 → freealg-0.7.4}/pyproject.toml +0 -0
  58. {freealg-0.7.1 → freealg-0.7.4}/requirements.txt +0 -0
  59. {freealg-0.7.1 → freealg-0.7.4}/setup.cfg +0 -0
  60. {freealg-0.7.1 → freealg-0.7.4}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: freealg
3
- Version: 0.7.1
3
+ Version: 0.7.4
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
@@ -6,7 +6,7 @@
6
6
  # under the terms of the license found in the LICENSE.txt file in the root
7
7
  # directory of this source tree.
8
8
 
9
- from ._freeform import FreeForm, eigvalsh, cond, norm, trace, slogdet, supp, \
9
+ from ._free_form import FreeForm, eigvalsh, cond, norm, trace, slogdet, supp, \
10
10
  sample, kde
11
11
  from ._algebraic_form import AlgebraicForm
12
12
  from ._geometric_form import GeometricForm
@@ -0,0 +1 @@
1
+ __version__ = "0.7.4"
@@ -14,10 +14,50 @@
14
14
  import numpy
15
15
  from .._geometric_form._continuation_genus0 import joukowski_z
16
16
 
17
- __all__ = [
18
- 'sample_z_joukowski', 'filter_z_away_from_cuts', 'powers',
19
- 'fit_polynomial_relation', 'eval_P', 'eval_roots',
20
- 'build_sheets_from_roots']
17
+ __all__ = ['sample_z_joukowski', 'filter_z_away_from_cuts', 'powers',
18
+ 'fit_polynomial_relation', 'eval_P', 'eval_roots',
19
+ 'build_sheets_from_roots']
20
+
21
+
22
+ # ======================
23
+ # normalize coefficients
24
+ # ======================
25
+
26
+ def _normalize_coefficients(arr):
27
+ """
28
+ Trim rows and columns on the sides (equivalent to factorizing or reducing
29
+ degree) and normalize so that the sum of the first column is one.
30
+ """
31
+
32
+ a = numpy.asarray(arr).copy()
33
+
34
+ if a.size == 0:
35
+ return a
36
+
37
+ # --- Trim zero rows (top and bottom) ---
38
+ non_zero_rows = numpy.any(a != 0, axis=1)
39
+ if not numpy.any(non_zero_rows):
40
+ return a[:0, :0]
41
+
42
+ first_row = numpy.argmax(non_zero_rows)
43
+ last_row = len(non_zero_rows) - numpy.argmax(non_zero_rows[::-1])
44
+ a = a[first_row:last_row, :]
45
+
46
+ # --- Trim zero columns (left and right) ---
47
+ non_zero_cols = numpy.any(a != 0, axis=0)
48
+ if not numpy.any(non_zero_cols):
49
+ return a[:, :0]
50
+
51
+ first_col = numpy.argmax(non_zero_cols)
52
+ last_col = len(non_zero_cols) - numpy.argmax(non_zero_cols[::-1])
53
+ a = a[:, first_col:last_col]
54
+
55
+ # --- Normalize so first column sums to 1 ---
56
+ col_sum = numpy.sum(numpy.abs(a[:, 0]))
57
+ if col_sum != 0:
58
+ a = a / col_sum
59
+
60
+ return a
21
61
 
22
62
 
23
63
  # ==================
@@ -91,7 +131,7 @@ def powers(x, deg):
91
131
  # =======================
92
132
 
93
133
  def fit_polynomial_relation(z, m, s, deg_z, ridge_lambda=0.0, weights=None,
94
- triangular=None):
134
+ triangular=None, normalize=False):
95
135
 
96
136
  z = numpy.asarray(z, dtype=complex).ravel()
97
137
  m = numpy.asarray(m, dtype=complex).ravel()
@@ -167,6 +207,9 @@ def fit_polynomial_relation(z, m, s, deg_z, ridge_lambda=0.0, weights=None,
167
207
  for k, (i, j) in enumerate(pairs):
168
208
  full[i, j] = coef[k]
169
209
 
210
+ if normalize:
211
+ full = _normalize_coefficients(full)
212
+
170
213
  return full
171
214
 
172
215
 
@@ -0,0 +1,86 @@
1
+ # =======
2
+ # Imports
3
+ # =======
4
+
5
+ import numpy
6
+ from scipy.special import comb
7
+ from ._continuation_algebraic import _normalize_coefficients
8
+
9
+ __all__ = ['decompress_coeffs']
10
+
11
+
12
+ # =================
13
+ # decompress_coeffs
14
+ # =================
15
+
16
+ def decompress_coeffs(a, t, normalize=True):
17
+ """
18
+ Compute the decompressed coefficients A[r, s](t) induced by
19
+ the transform Q_t(z, m) = m^L P(z + (1 - e^{-t}) / m, e^t m).
20
+
21
+ Parameters
22
+ ----------
23
+ a : array_like of float, shape (L+1, K+1)
24
+ Coefficients defining P(z, m) in the monomial basis:
25
+ P(z, m) = sum_{j=0..L} sum_{k=0..K} a[j, k] z^j m^k.
26
+ t : float
27
+ Time parameter.
28
+
29
+ Returns
30
+ -------
31
+ A : ndarray, shape (L+1, L+K+1)
32
+ Coefficients A[r, s](t) such that
33
+ sum_{r=0..L} sum_{s=0..L+K} A[r, s](t) z^r m^s = 0,
34
+ normalized by normalize_coefficients.
35
+ """
36
+ a = numpy.asarray(a)
37
+ if a.ndim != 2:
38
+ raise ValueError("a must be a 2D array-like of shape (L+1, K+1).")
39
+
40
+ l_degree = a.shape[0] - 1
41
+ k_degree = a.shape[1] - 1
42
+
43
+ c = 1.0 - numpy.exp(-t)
44
+
45
+ # Scale columns of a by e^{t k}: scaled[j, k] = a[j, k] e^{t k}.
46
+ exp_factors = numpy.exp(numpy.arange(k_degree + 1) * t)
47
+ scaled = a * exp_factors
48
+
49
+ # Output coefficients.
50
+ out_dtype = numpy.result_type(a, float)
51
+ a_out = numpy.zeros((l_degree + 1, l_degree + k_degree + 1),
52
+ dtype=out_dtype)
53
+
54
+ # Precompute binomial(j, r) * c^{j-r} for all j, r (lower-triangular).
55
+ j_inds = numpy.arange(l_degree + 1)[:, None]
56
+ r_inds = numpy.arange(l_degree + 1)[None, :]
57
+ mask = r_inds <= j_inds
58
+
59
+ binom_weights = numpy.zeros((l_degree + 1, l_degree + 1), dtype=float)
60
+ binom_weights[mask] = comb(j_inds, r_inds, exact=False)[mask]
61
+ binom_weights[mask] *= (c ** (j_inds - r_inds))[mask]
62
+
63
+ # Main accumulation:
64
+ # For fixed j and r, add:
65
+ # A[r, (L - j + r) + k] += binom_weights[j, r] * scaled[j, k],
66
+ # for k = 0..K.
67
+ for j in range(l_degree + 1):
68
+ row_scaled = scaled[j]
69
+ if numpy.all(row_scaled == 0):
70
+ continue
71
+
72
+ base0 = l_degree - j
73
+ row_b = binom_weights[j]
74
+
75
+ for r in range(j + 1):
76
+ coeff = row_b[r]
77
+ if coeff == 0:
78
+ continue
79
+
80
+ start = base0 + r
81
+ a_out[r, start:start + (k_degree + 1)] += coeff * row_scaled
82
+
83
+ if normalize:
84
+ return _normalize_coefficients(a_out)
85
+
86
+ return a_out
@@ -0,0 +1,138 @@
1
+ # =======
2
+ # Imports
3
+ # =======
4
+
5
+ import numpy
6
+
7
+ __all__ = ['stieltjes_poly']
8
+
9
+
10
+ # =====================
11
+ # stieltjes select root
12
+ # =====================
13
+
14
+ def stieltjes_select_root(roots, z, w_prev=None):
15
+ """
16
+ Select the Stieltjes-branch root among candidates at a given z.
17
+
18
+ Parameters
19
+ ----------
20
+ roots : array_like of complex
21
+ Candidate roots for m at the given z.
22
+ z : complex
23
+ Evaluation point. The Stieltjes/Herglotz branch satisfies
24
+ sign(Im(m)) = sign(Im(z)) away from the real axis.
25
+ w_prev : complex or None, optional
26
+ Previous continuation value used to enforce continuity. If None,
27
+ the asymptotic target -1/z is used.
28
+
29
+ Returns
30
+ -------
31
+ w : complex
32
+ Selected root corresponding to the Stieltjes branch.
33
+ """
34
+
35
+ z = complex(z)
36
+ roots = numpy.asarray(roots, dtype=numpy.complex128).ravel()
37
+
38
+ if roots.size == 0:
39
+ raise ValueError("roots must contain at least one candidate root.")
40
+
41
+ desired_sign = numpy.sign(z.imag)
42
+
43
+ if w_prev is None:
44
+ target = -1.0 / z
45
+ else:
46
+ target = complex(w_prev)
47
+
48
+ # Apply a soft Herglotz sign filter: prefer roots with Im(w) having the
49
+ # same sign as Im(z), allowing tiny numerical violations near the axis.
50
+ imag_roots = numpy.imag(roots)
51
+
52
+ good = roots[numpy.sign(imag_roots) == desired_sign]
53
+ if good.size == 0:
54
+ good = roots[(imag_roots * desired_sign) > -1e-12]
55
+
56
+ candidates = good if good.size > 0 else roots
57
+ idx = int(numpy.argmin(numpy.abs(candidates - target)))
58
+ return candidates[idx]
59
+
60
+
61
+ # ==============
62
+ # stieltjes poly
63
+ # ==============
64
+
65
+ def stieltjes_poly(z, a, eps=None, height=1e4, steps=100):
66
+ """
67
+ Evaluate the Stieltjes-branch solution m(z) of an algebraic equation.
68
+
69
+ The coefficients `a` define a polynomial relation
70
+ P(z, m) = 0,
71
+ where P is a polynomial in z and m with monomial-basis coefficients
72
+ arranged so that for fixed z, the coefficients of the polynomial in m
73
+ can be assembled from powers of z.
74
+
75
+ Parameters
76
+ ----------
77
+ z : complex
78
+ Evaluation point. Must be a single value.
79
+ a : ndarray, shape (L, K)
80
+ Coefficient matrix defining P(z, m) in the monomial basis.
81
+ eps : float or None, optional
82
+ If Im(z) == 0, use z + i*eps as the boundary evaluation point.
83
+ If None and Im(z) == 0, eps is set to 1e-8 * max(1, |z|).
84
+ height : float, optional
85
+ Imaginary height used for the starting point z0 in the same
86
+ half-plane as the evaluation point.
87
+ steps : int, optional
88
+ Number of continuation steps along the homotopy path.
89
+
90
+ Returns
91
+ -------
92
+ w : complex
93
+ Value of the Stieltjes-branch solution m(z) (or m(z+i*eps) if z is
94
+ real).
95
+ """
96
+
97
+ z = complex(z)
98
+ a = numpy.asarray(a)
99
+
100
+ if a.ndim != 2:
101
+ raise ValueError('a must be a 2D array.')
102
+
103
+ if steps < 1:
104
+ raise ValueError("steps must be a positive integer.")
105
+
106
+ a_l, _ = a.shape
107
+
108
+ def poly_coeffs_m(z_val):
109
+ z_powers = z_val ** numpy.arange(a_l)
110
+ return (z_powers @ a)[::-1]
111
+
112
+ def poly_roots(z_val):
113
+ coeffs = numpy.asarray(poly_coeffs_m(z_val), dtype=numpy.complex128)
114
+ return numpy.roots(coeffs)
115
+
116
+ # If user asked for a real-axis value, interpret as boundary value from C+.
117
+ if z.imag == 0.0:
118
+ if eps is None:
119
+ eps = 1e-8 * max(1.0, abs(z))
120
+ z_eval = z + 1j * float(eps)
121
+ else:
122
+ z_eval = z
123
+
124
+ half_sign = numpy.sign(z_eval.imag)
125
+ if half_sign == 0.0:
126
+ half_sign = 1.0
127
+
128
+ z0 = 1j * float(half_sign) * float(height)
129
+
130
+ # Initialize at z0 via asymptotic / Im-sign selection.
131
+ w_prev = stieltjes_select_root(poly_roots(z0), z0, w_prev=None)
132
+
133
+ # Straight-line homotopy from z0 to z_eval.
134
+ for tau in numpy.linspace(0.0, 1.0, int(steps) + 1)[1:]:
135
+ z_tau = z0 + tau * (z_eval - z0)
136
+ w_prev = stieltjes_select_root(poly_roots(z_tau), z_tau, w_prev=w_prev)
137
+
138
+ return w_prev