freealg 0.7.3__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.3 → freealg-0.7.4}/PKG-INFO +1 -1
  2. freealg-0.7.4/freealg/__version__.py +1 -0
  3. {freealg-0.7.3 → freealg-0.7.4}/freealg/_algebraic_form/_continuation_algebraic.py +4 -2
  4. freealg-0.7.4/freealg/_algebraic_form/_decompress2.py +86 -0
  5. freealg-0.7.4/freealg/_algebraic_form/_homotopy.py +138 -0
  6. {freealg-0.7.3 → freealg-0.7.4}/freealg/_algebraic_form/algebraic_form.py +168 -113
  7. {freealg-0.7.3 → freealg-0.7.4}/freealg/distributions/_deformed_wigner.py +7 -19
  8. {freealg-0.7.3 → freealg-0.7.4}/freealg.egg-info/PKG-INFO +1 -1
  9. {freealg-0.7.3 → freealg-0.7.4}/freealg.egg-info/SOURCES.txt +2 -0
  10. freealg-0.7.3/freealg/__version__.py +0 -1
  11. {freealg-0.7.3 → freealg-0.7.4}/AUTHORS.txt +0 -0
  12. {freealg-0.7.3 → freealg-0.7.4}/CHANGELOG.rst +0 -0
  13. {freealg-0.7.3 → freealg-0.7.4}/LICENSE.txt +0 -0
  14. {freealg-0.7.3 → freealg-0.7.4}/MANIFEST.in +0 -0
  15. {freealg-0.7.3 → freealg-0.7.4}/README.rst +0 -0
  16. {freealg-0.7.3 → freealg-0.7.4}/freealg/__init__.py +0 -0
  17. {freealg-0.7.3 → freealg-0.7.4}/freealg/_algebraic_form/__init__.py +0 -0
  18. {freealg-0.7.3 → freealg-0.7.4}/freealg/_algebraic_form/_decompress.py +0 -0
  19. {freealg-0.7.3 → freealg-0.7.4}/freealg/_algebraic_form/_edge.py +0 -0
  20. {freealg-0.7.3 → freealg-0.7.4}/freealg/_algebraic_form/_sheets_util.py +0 -0
  21. {freealg-0.7.3 → freealg-0.7.4}/freealg/_free_form/__init__.py +0 -0
  22. {freealg-0.7.3 → freealg-0.7.4}/freealg/_free_form/_chebyshev.py +0 -0
  23. {freealg-0.7.3 → freealg-0.7.4}/freealg/_free_form/_damp.py +0 -0
  24. {freealg-0.7.3 → freealg-0.7.4}/freealg/_free_form/_decompress.py +0 -0
  25. {freealg-0.7.3 → freealg-0.7.4}/freealg/_free_form/_density_util.py +0 -0
  26. {freealg-0.7.3 → freealg-0.7.4}/freealg/_free_form/_jacobi.py +0 -0
  27. {freealg-0.7.3 → freealg-0.7.4}/freealg/_free_form/_linalg.py +0 -0
  28. {freealg-0.7.3 → freealg-0.7.4}/freealg/_free_form/_pade.py +0 -0
  29. {freealg-0.7.3 → freealg-0.7.4}/freealg/_free_form/_plot_util.py +0 -0
  30. {freealg-0.7.3 → freealg-0.7.4}/freealg/_free_form/_sample.py +0 -0
  31. {freealg-0.7.3 → freealg-0.7.4}/freealg/_free_form/_series.py +0 -0
  32. {freealg-0.7.3 → freealg-0.7.4}/freealg/_free_form/_support.py +0 -0
  33. {freealg-0.7.3 → freealg-0.7.4}/freealg/_free_form/free_form.py +0 -0
  34. {freealg-0.7.3 → freealg-0.7.4}/freealg/_geometric_form/__init__.py +0 -0
  35. {freealg-0.7.3 → freealg-0.7.4}/freealg/_geometric_form/_continuation_genus0.py +0 -0
  36. {freealg-0.7.3 → freealg-0.7.4}/freealg/_geometric_form/_continuation_genus1.py +0 -0
  37. {freealg-0.7.3 → freealg-0.7.4}/freealg/_geometric_form/_elliptic_functions.py +0 -0
  38. {freealg-0.7.3 → freealg-0.7.4}/freealg/_geometric_form/_sphere_maps.py +0 -0
  39. {freealg-0.7.3 → freealg-0.7.4}/freealg/_geometric_form/_torus_maps.py +0 -0
  40. {freealg-0.7.3 → freealg-0.7.4}/freealg/_geometric_form/geometric_form.py +0 -0
  41. {freealg-0.7.3 → freealg-0.7.4}/freealg/_util.py +0 -0
  42. {freealg-0.7.3 → freealg-0.7.4}/freealg/distributions/__init__.py +0 -0
  43. {freealg-0.7.3 → freealg-0.7.4}/freealg/distributions/_chiral_block.py +0 -0
  44. {freealg-0.7.3 → freealg-0.7.4}/freealg/distributions/_deformed_marchenko_pastur.py +0 -0
  45. {freealg-0.7.3 → freealg-0.7.4}/freealg/distributions/_kesten_mckay.py +0 -0
  46. {freealg-0.7.3 → freealg-0.7.4}/freealg/distributions/_marchenko_pastur.py +0 -0
  47. {freealg-0.7.3 → freealg-0.7.4}/freealg/distributions/_meixner.py +0 -0
  48. {freealg-0.7.3 → freealg-0.7.4}/freealg/distributions/_wachter.py +0 -0
  49. {freealg-0.7.3 → freealg-0.7.4}/freealg/distributions/_wigner.py +0 -0
  50. {freealg-0.7.3 → freealg-0.7.4}/freealg/visualization/__init__.py +0 -0
  51. {freealg-0.7.3 → freealg-0.7.4}/freealg/visualization/_glue_util.py +0 -0
  52. {freealg-0.7.3 → freealg-0.7.4}/freealg/visualization/_rgb_hsv.py +0 -0
  53. {freealg-0.7.3 → freealg-0.7.4}/freealg.egg-info/dependency_links.txt +0 -0
  54. {freealg-0.7.3 → freealg-0.7.4}/freealg.egg-info/not-zip-safe +0 -0
  55. {freealg-0.7.3 → freealg-0.7.4}/freealg.egg-info/requires.txt +0 -0
  56. {freealg-0.7.3 → freealg-0.7.4}/freealg.egg-info/top_level.txt +0 -0
  57. {freealg-0.7.3 → freealg-0.7.4}/pyproject.toml +0 -0
  58. {freealg-0.7.3 → freealg-0.7.4}/requirements.txt +0 -0
  59. {freealg-0.7.3 → freealg-0.7.4}/setup.cfg +0 -0
  60. {freealg-0.7.3 → 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.3
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
@@ -0,0 +1 @@
1
+ __version__ = "0.7.4"
@@ -23,12 +23,14 @@ __all__ = ['sample_z_joukowski', 'filter_z_away_from_cuts', 'powers',
23
23
  # normalize coefficients
24
24
  # ======================
25
25
 
26
- def _normalize_coefficients(a):
26
+ def _normalize_coefficients(arr):
27
27
  """
28
28
  Trim rows and columns on the sides (equivalent to factorizing or reducing
29
29
  degree) and normalize so that the sum of the first column is one.
30
30
  """
31
31
 
32
+ a = numpy.asarray(arr).copy()
33
+
32
34
  if a.size == 0:
33
35
  return a
34
36
 
@@ -51,7 +53,7 @@ def _normalize_coefficients(a):
51
53
  a = a[:, first_col:last_col]
52
54
 
53
55
  # --- Normalize so first column sums to 1 ---
54
- col_sum = numpy.sum(a[:, 0])
56
+ col_sum = numpy.sum(numpy.abs(a[:, 0]))
55
57
  if col_sum != 0:
56
58
  a = a / col_sum
57
59
 
@@ -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
@@ -11,13 +11,18 @@
11
11
  # Imports
12
12
  # =======
13
13
 
14
+ import inspect
14
15
  import numpy
15
- from .._util import resolve_complex_dtype
16
+ from .._util import resolve_complex_dtype, compute_eig
16
17
  # from .._util import compute_eig
17
18
  from ._continuation_algebraic import sample_z_joukowski, \
18
19
  filter_z_away_from_cuts, fit_polynomial_relation, eval_P
19
20
  from ._edge import evolve_edges, merge_edges
20
21
  from ._decompress import decompress_newton
22
+ from ._decompress2 import decompress_coeffs
23
+ from ._homotopy import stieltjes_poly
24
+ from .._free_form._support import supp
25
+ from .._free_form._plot_util import plot_density
21
26
 
22
27
  # Fallback to previous numpy API
23
28
  if not hasattr(numpy, 'trapezoid'):
@@ -129,46 +134,66 @@ class AlgebraicForm(object):
129
134
  # def __init__(self, A, support=None, delta=1e-6, dtype='complex128',
130
135
  # **kwargs):
131
136
 
132
- def __init__(self, stieltjes, support=None, delta=1e-5, dtype='complex128',
137
+ def __init__(self, A, support=None, delta=1e-5, dtype='complex128',
133
138
  **kwargs):
134
139
  """
135
140
  Initialization.
136
141
  """
137
142
 
138
- # self.A = None
139
- # self.eig = None
140
- self.stieltjes = stieltjes
143
+ self.A = None
144
+ self.eig = None
145
+ self.stieltjes = None
141
146
  self.support = support
142
147
  self.delta = delta # Offset above real axis to apply Plemelj formula
143
148
 
144
149
  # Data type for complex arrays
145
150
  self.dtype = resolve_complex_dtype(dtype)
146
151
 
147
- # # Eigenvalues
148
- # if A.ndim == 1:
149
- # # When A is a 1D array, it is assumed A is the eigenvalue array.
150
- # self.eig = A
151
- # self.n = len(A)
152
- # elif A.ndim == 2:
153
- # # When A is a 2D array, it is assumed A is the actual array,
154
- # # and its eigenvalues will be computed.
155
- # self.A = A
156
- # self.n = A.shape[0]
157
- # assert A.shape[0] == A.shape[1], \
158
- # 'Only square matrices are permitted.'
159
- # self.eig = compute_eig(A)
152
+ if inspect.isclass(A) and hasattr(A, "stieltjes") and \
153
+ callable(getattr(A, "stieltjes", None)):
154
+ # This is one of the distribution objects, like MarchenkoPastur
155
+ self.stieltjes = A.stieltjes
156
+ self.n = 1
157
+
158
+ elif callable(A):
159
+ # This is a custom function
160
+ self.stieltjes = A
161
+ self.n = 1
162
+
163
+ else:
164
+ # Eigenvalues
165
+ if A.ndim == 1:
166
+ # If A is a 1D array, it is assumed A is the eigenvalues array.
167
+ self.eig = A
168
+ self.n = len(A)
169
+ elif A.ndim == 2:
170
+ # When A is a 2D array, it is assumed A is the actual array,
171
+ # and its eigenvalues will be computed.
172
+ self.A = A
173
+ self.n = A.shape[0]
174
+ assert A.shape[0] == A.shape[1], \
175
+ 'Only square matrices are permitted.'
176
+ self.eig = compute_eig(A)
177
+
178
+ # Use empirical Stieltjes function
179
+ self.stieltjes = lambda z: \
180
+ numpy.mean(1.0/(self.eig-z[:, numpy.newaxis]), axis=-1)
160
181
 
161
182
  # Support
162
- # if support is None:
163
- # # Detect support
164
- # self.lam_m, self.lam_p = supp(self.eig, **kwargs)
165
- # else:
166
- # self.lam_m = float(support[0])
167
- # self.lam_p = float(support[1])
168
- # self.support = (self.lam_m, self.lam_p)
183
+ if support is None:
184
+ if self.eig is None:
185
+ raise RuntimeError("Support must be provided without data")
186
+ # Detect support
187
+ self.lam_m, self.lam_p = supp(self.eig, **kwargs)
188
+ self.support = [(self.lam_m, self.lam_p)]
189
+ self.broad_support = self.support[0]
190
+ else:
191
+ self.support = support
192
+ self.lam_m = min([s[0] for s in self.support])
193
+ self.lam_p = max([s[1] for s in self.support])
194
+ self.broad_support = (self.lam_m, self.lam_p)
169
195
 
170
196
  # Initialize
171
- # self.method = None # fitting rho: jacobi, chebyshev
172
197
  self.a_coeffs = None # Polynomial coefficients
173
198
  self.cache = {} # Cache inner-computations
174
199
 
@@ -193,8 +218,6 @@ class AlgebraicForm(object):
193
218
  # also empties all references holdign a cache copy.
194
219
  # self.cache.clear()
195
220
 
196
- # return self.a_coeffs
197
-
198
221
  z_fits = []
199
222
  for sup in self.support:
200
223
  a, b = sup
@@ -205,12 +228,10 @@ class AlgebraicForm(object):
205
228
 
206
229
  z_fit = numpy.concatenate(z_fits)
207
230
 
208
- # Remove points too close to ANY cut
231
+ # Remove points too close to any cut
209
232
  z_fit = filter_z_away_from_cuts(z_fit, self.support, y_eps=y_eps,
210
233
  x_pad=x_pad)
211
234
 
212
- # ---------
213
-
214
235
  m1_fit = self.stieltjes(z_fit)
215
236
  a_coeffs = fit_polynomial_relation(z_fit, m1_fit, s=deg_m, deg_z=deg_z,
216
237
  ridge_lambda=reg,
@@ -225,7 +246,7 @@ class AlgebraicForm(object):
225
246
  print("fit residual 99.9%:",
226
247
  numpy.quantile(P_res[numpy.isfinite(P_res)], 0.999))
227
248
 
228
- print('\nCoefficinets')
249
+ print('\nCoefficients')
229
250
  with numpy.printoptions(precision=4, suppress=True):
230
251
  for i in range(a_coeffs.shape[0]):
231
252
  for j in range(a_coeffs.shape[1]):
@@ -233,7 +254,7 @@ class AlgebraicForm(object):
233
254
  print(f"{v.real:>+0.4f}{v.imag:>+0.4f}j", end=" ")
234
255
  print('')
235
256
 
236
- print('\nCoefficients mangitudes')
257
+ print('\nCoefficient Magnitudes')
237
258
  with numpy.printoptions(precision=6, suppress=True):
238
259
  print(numpy.abs(a_coeffs))
239
260
 
@@ -243,22 +264,22 @@ class AlgebraicForm(object):
243
264
  # generate grid
244
265
  # =============
245
266
 
246
- # def _generate_grid(self, scale, extend=1.0, N=500):
247
- # """
248
- # Generate a grid of points to evaluate density / Hilbert / Stieltjes
249
- # transforms.
250
- # """
251
- #
252
- # radius = 0.5 * (self.lam_p - self.lam_m)
253
- # center = 0.5 * (self.lam_p + self.lam_m)
254
- #
255
- # x_min = numpy.floor(extend * (center - extend * radius * scale))
256
- # x_max = numpy.ceil(extend * (center + extend * radius * scale))
257
- #
258
- # x_min /= extend
259
- # x_max /= extend
260
- #
261
- # return numpy.linspace(x_min, x_max, N)
267
+ def _generate_grid(self, scale, extend=1.0, N=500):
268
+ """
269
+ Generate a grid of points to evaluate density / Hilbert / Stieltjes
270
+ transforms.
271
+ """
272
+
273
+ radius = 0.5 * (self.lam_p - self.lam_m)
274
+ center = 0.5 * (self.lam_p + self.lam_m)
275
+
276
+ x_min = numpy.floor(extend * (center - extend * radius * scale))
277
+ x_max = numpy.ceil(extend * (center + extend * radius * scale))
278
+
279
+ x_min /= extend
280
+ x_max /= extend
281
+
282
+ return numpy.linspace(x_min, x_max, N)
262
283
 
263
284
  # =======
264
285
  # density
@@ -312,16 +333,19 @@ class AlgebraicForm(object):
312
333
  raise RuntimeError('The model needs to be fit using the .fit() ' +
313
334
  'function.')
314
335
 
315
- # # Create x if not given
316
- # if x is None:
317
- # x = self._generate_grid(1.25)
318
- #
319
- # # Preallocate density to zero
320
- # rho = numpy.zeros_like(x)
321
- #
322
- # # Compute density only inside support
323
- # mask = numpy.logical_and(x >= self.lam_m, x <= self.lam_p)
324
- #
336
+ # Create x if not given
337
+ if x is None:
338
+ x = self._generate_grid(1.25)
339
+
340
+ # Preallocate density to zero
341
+ rho = numpy.zeros_like(x)
342
+
343
+ for idx, x_i in enumerate(x):
344
+ m_i = stieltjes_poly(x_i, self.a_coeffs)
345
+ rho[idx] = m_i.imag
346
+
347
+ rho = rho / numpy.pi
348
+
325
349
  # if self.method == 'jacobi':
326
350
  # rho[mask] = jacobi_density(x[mask], self.psi, self.support,
327
351
  # self.alpha, self.beta)
@@ -341,12 +365,12 @@ class AlgebraicForm(object):
341
365
  # if min_rho < 0.0 - 1e-3:
342
366
  # print(f'"rho" is not positive. min_rho: {min_rho:>0.3f}. Set ' +
343
367
  # r'"force=True".')
344
- #
345
- # if plot:
346
- # plot_density(x, rho, eig=self.eig, support=self.support,
347
- # label='Estimate', latex=latex, save=save)
348
- #
349
- # return rho
368
+
369
+ if plot:
370
+ plot_density(x, rho, eig=self.eig, support=self.broad_support,
371
+ label='Estimate', latex=latex, save=save)
372
+
373
+ return rho
350
374
 
351
375
  # =======
352
376
  # hilbert
@@ -568,68 +592,99 @@ class AlgebraicForm(object):
568
592
  # decompress
569
593
  # ==========
570
594
 
571
- def decompress(self, x, t,
572
- max_iter=50,
573
- tol=1e-12,
574
- armijo=1e-4,
575
- min_lam=1e-6,
576
- w_min=1e-14,
577
- sweep=True,
578
- verbose=False):
595
+ def decompress(self, size, x=None, method='one', plot=False, latex=False,
596
+ save=False, verbose=False, newton_opt={
597
+ 'max_iter': 50, 'tol': 1e-12, 'armijo': 1e-4,
598
+ 'min_lam': 1e-6, 'w_min': 1e-14, 'sweep': True}):
579
599
  """
580
600
  Free decompression of spectral density.
581
601
  """
582
602
 
583
603
  # Check size argument
584
- # if numpy.isscalar(size):
585
- # size = int(size)
586
- # else:
587
- # # Check monotonic increment (either all increasing or decreasing)
588
- # diff = numpy.diff(size)
589
- # if not (numpy.all(diff >= 0) or numpy.all(diff <= 0)):
590
- # raise ValueError('"size" increment should be monotonic.')
604
+ if numpy.isscalar(size):
605
+ size = int(size)
606
+ else:
607
+ # Check monotonic increment (either all increasing or decreasing)
608
+ diff = numpy.diff(size)
609
+ if not (numpy.all(diff >= 0) or numpy.all(diff <= 0)):
610
+ raise ValueError('"size" increment should be monotonic.')
591
611
 
592
612
  # Decompression ratio equal to e^{t}.
593
- # alpha = numpy.atleast_1d(size) / self.n
613
+ alpha = numpy.atleast_1d(size) / self.n
594
614
 
595
- # # If the input size was only a scalar, return a 1D rho, otherwise 2D.
596
- # if numpy.isscalar(size):
597
- # rho = numpy.squeeze(rho)
598
- #
599
- # # Plot only the last size
600
- # if plot:
601
- # if numpy.isscalar(size):
602
- # rho_last = rho
603
- # else:
604
- # rho_last = rho[-1, :]
605
- # plot_density(x, rho_last, support=(lb, ub),
606
- # label='Decompression', latex=latex, save=save)
607
- #
608
- # return rho, x
615
+ def m_fn(z):
616
+ return stieltjes_poly(z, self.a_coeffs)
609
617
 
610
- # Query grid on the real axis + a small imaginary buffer
611
- z_query = x + 1j * self.delta
618
+ # Lower and upper bound on new support
619
+ hilb_lb = (1.0 / m_fn(self.lam_m + self.delta * 1j).item()).real
620
+ hilb_ub = (1.0 / m_fn(self.lam_p + self.delta * 1j).item()).real
621
+ lb = self.lam_m - (numpy.max(alpha) - 1) * hilb_lb
622
+ ub = self.lam_p - (numpy.max(alpha) - 1) * hilb_ub
612
623
 
613
- # Initial condition at t=0 (physical branch)
614
- w0_list = self.stieltjes(z_query)
624
+ # Create x if not given
625
+ if x is None:
626
+ radius = 0.5 * (ub - lb)
627
+ center = 0.5 * (ub + lb)
628
+ scale = 1.25
629
+ x_min = numpy.floor(center - radius * scale)
630
+ x_max = numpy.ceil(center + radius * scale)
631
+ x = numpy.linspace(x_min, x_max, 200)
632
+ else:
633
+ x = numpy.asarray(x)
615
634
 
616
- # Evolve
617
- W, ok = decompress_newton(
618
- z_query, t, self.a_coeffs,
619
- w0_list=w0_list,
620
- max_iter=max_iter,
621
- tol=tol,
622
- armijo=armijo,
623
- min_lam=min_lam,
624
- w_min=w_min,
625
- sweep=sweep)
635
+ if method == 'one':
626
636
 
627
- rho = W.imag / numpy.pi
637
+ # Query grid on the real axis + a small imaginary buffer
638
+ z_query = x + 1j * self.delta
628
639
 
629
- if verbose:
630
- print("success rate per t:", ok.mean(axis=1))
640
+ # Initial condition at t=0 (physical branch)
641
+ w0_list = self.stieltjes(z_query)
631
642
 
632
- return rho
643
+ # Times
644
+ t = numpy.log(alpha)
645
+
646
+ # Evolve
647
+ W, ok = decompress_newton(
648
+ z_query, t, self.a_coeffs,
649
+ w0_list=w0_list, **newton_opt)
650
+
651
+ rho = W.imag / numpy.pi
652
+
653
+ if verbose:
654
+ print("success rate per t:", ok.mean(axis=1))
655
+
656
+ elif method == 'two':
657
+
658
+ # Preallocate density to zero
659
+ rho = numpy.zeros((alpha.size, x.size), dtype=float)
660
+
661
+ # Decompress to each alpha
662
+ for i in range(alpha.size):
663
+ coeffs_i = decompress_coeffs(self.a_coeffs,
664
+ numpy.log(alpha[i]))
665
+ for j, x_j in enumerate(x):
666
+ m_j = stieltjes_poly(x_j, coeffs_i)
667
+ rho[i, j] = m_j.imag
668
+
669
+ rho = rho / numpy.pi
670
+
671
+ else:
672
+ raise ValueError('"method" is invalid.')
673
+
674
+ # If the input size was only a scalar, return a 1D rho, otherwise 2D.
675
+ if numpy.isscalar(size):
676
+ rho = numpy.squeeze(rho)
677
+
678
+ # Plot only the last size
679
+ if plot:
680
+ if numpy.isscalar(size):
681
+ rho_last = rho
682
+ else:
683
+ rho_last = rho[-1, :]
684
+ plot_density(x, rho_last, support=(lb, ub),
685
+ label='Decompression', latex=latex, save=save)
686
+
687
+ return rho, x
633
688
 
634
689
  # ====
635
690
  # edge
@@ -290,26 +290,14 @@ class DeformedWigner(object):
290
290
  A : numpy.ndarray
291
291
  A matrix of the size :math:`n \\times n`.
292
292
 
293
- Parameters
294
- ----------
295
- size : int
296
- Size n of the matrix.
297
-
298
- seed : int, default=None
299
- Seed for random number generator.
300
-
301
- Returns
302
- -------
303
- A : numpy.ndarray
304
- Symmetric matrix of shape (n, n).
305
-
306
293
  Notes
307
294
  -----
308
295
 
309
- Generate an :math:`n x n` matrix :math:`\\mathbf{A} = \\mathbf{T} +
310
- \\sigma \\mathbf{W}` whose ESD converges to
311
- :math:`H \\boxplus SC_{\\sigma^2}`, where
312
- :math:`H = w_1 \\delta_{t_1} + (1-w_1) \\delta_{t_2}`.
296
+ Generate an :math:`n \\times n` matrix
297
+ :math:`\\mathbf{A} = \\mathbf{T} + \\sigma \\mathbf{W}`
298
+ whose ESD converges to
299
+ :math:`H \\boxplus \\mathrm{SC}_{\\sigma^2}`, where
300
+ :math:`H = w_1 \\delta_{t_1} + (1 - w_1) \\delta_{t_2}`.
313
301
 
314
302
  Examples
315
303
  --------
@@ -317,8 +305,8 @@ class DeformedWigner(object):
317
305
  .. code-block::python
318
306
 
319
307
  >>> from freealg.distributions import DeformedWigner
320
- >>> mp = DeformedWigner(1/50)
321
- >>> A = mp.matrix(2000)
308
+ >>> dwg = DeformedWigner(1/50)
309
+ >>> A = dwg.matrix(2000)
322
310
  """
323
311
 
324
312
  n = int(size)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: freealg
3
- Version: 0.7.3
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
@@ -19,7 +19,9 @@ freealg.egg-info/top_level.txt
19
19
  freealg/_algebraic_form/__init__.py
20
20
  freealg/_algebraic_form/_continuation_algebraic.py
21
21
  freealg/_algebraic_form/_decompress.py
22
+ freealg/_algebraic_form/_decompress2.py
22
23
  freealg/_algebraic_form/_edge.py
24
+ freealg/_algebraic_form/_homotopy.py
23
25
  freealg/_algebraic_form/_sheets_util.py
24
26
  freealg/_algebraic_form/algebraic_form.py
25
27
  freealg/_free_form/__init__.py
@@ -1 +0,0 @@
1
- __version__ = "0.7.3"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes