freealg 0.1.11__py3-none-any.whl → 0.7.12__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 (59) hide show
  1. freealg/__init__.py +8 -2
  2. freealg/__version__.py +1 -1
  3. freealg/_algebraic_form/__init__.py +12 -0
  4. freealg/_algebraic_form/_branch_points.py +288 -0
  5. freealg/_algebraic_form/_constraints.py +139 -0
  6. freealg/_algebraic_form/_continuation_algebraic.py +706 -0
  7. freealg/_algebraic_form/_decompress.py +641 -0
  8. freealg/_algebraic_form/_decompress2.py +204 -0
  9. freealg/_algebraic_form/_edge.py +330 -0
  10. freealg/_algebraic_form/_homotopy.py +323 -0
  11. freealg/_algebraic_form/_moments.py +448 -0
  12. freealg/_algebraic_form/_sheets_util.py +145 -0
  13. freealg/_algebraic_form/_support.py +309 -0
  14. freealg/_algebraic_form/algebraic_form.py +1232 -0
  15. freealg/_free_form/__init__.py +16 -0
  16. freealg/{_chebyshev.py → _free_form/_chebyshev.py} +75 -43
  17. freealg/_free_form/_decompress.py +993 -0
  18. freealg/_free_form/_density_util.py +243 -0
  19. freealg/_free_form/_jacobi.py +359 -0
  20. freealg/_free_form/_linalg.py +508 -0
  21. freealg/{_pade.py → _free_form/_pade.py} +42 -208
  22. freealg/{_plot_util.py → _free_form/_plot_util.py} +37 -22
  23. freealg/{_sample.py → _free_form/_sample.py} +58 -22
  24. freealg/_free_form/_series.py +454 -0
  25. freealg/_free_form/_support.py +214 -0
  26. freealg/_free_form/free_form.py +1362 -0
  27. freealg/_geometric_form/__init__.py +13 -0
  28. freealg/_geometric_form/_continuation_genus0.py +175 -0
  29. freealg/_geometric_form/_continuation_genus1.py +275 -0
  30. freealg/_geometric_form/_elliptic_functions.py +174 -0
  31. freealg/_geometric_form/_sphere_maps.py +63 -0
  32. freealg/_geometric_form/_torus_maps.py +118 -0
  33. freealg/_geometric_form/geometric_form.py +1094 -0
  34. freealg/_util.py +56 -110
  35. freealg/distributions/__init__.py +7 -1
  36. freealg/distributions/_chiral_block.py +494 -0
  37. freealg/distributions/_deformed_marchenko_pastur.py +726 -0
  38. freealg/distributions/_deformed_wigner.py +386 -0
  39. freealg/distributions/_kesten_mckay.py +29 -15
  40. freealg/distributions/_marchenko_pastur.py +224 -95
  41. freealg/distributions/_meixner.py +47 -37
  42. freealg/distributions/_wachter.py +29 -17
  43. freealg/distributions/_wigner.py +27 -14
  44. freealg/visualization/__init__.py +12 -0
  45. freealg/visualization/_glue_util.py +32 -0
  46. freealg/visualization/_rgb_hsv.py +125 -0
  47. freealg-0.7.12.dist-info/METADATA +172 -0
  48. freealg-0.7.12.dist-info/RECORD +53 -0
  49. {freealg-0.1.11.dist-info → freealg-0.7.12.dist-info}/WHEEL +1 -1
  50. freealg/_decompress.py +0 -180
  51. freealg/_jacobi.py +0 -218
  52. freealg/_support.py +0 -85
  53. freealg/freeform.py +0 -967
  54. freealg-0.1.11.dist-info/METADATA +0 -140
  55. freealg-0.1.11.dist-info/RECORD +0 -24
  56. /freealg/{_damp.py → _free_form/_damp.py} +0 -0
  57. {freealg-0.1.11.dist-info → freealg-0.7.12.dist-info}/licenses/AUTHORS.txt +0 -0
  58. {freealg-0.1.11.dist-info → freealg-0.7.12.dist-info}/licenses/LICENSE.txt +0 -0
  59. {freealg-0.1.11.dist-info → freealg-0.7.12.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,323 @@
1
+ # =======
2
+ # Imports
3
+ # =======
4
+
5
+ import numpy
6
+ from ._moments import AlgebraicStieltjesMoments
7
+ from tqdm import tqdm
8
+ from math import comb
9
+
10
+ __all__ = ['StieltjesPoly']
11
+
12
+
13
+ # ===========
14
+ # select root
15
+ # ===========
16
+
17
+ def select_root(roots, z, target):
18
+ """
19
+ Select the root among Herglotz candidates at a given z closest to a
20
+ given target
21
+
22
+ Parameters
23
+ ----------
24
+ roots : array_like of complex
25
+ Candidate roots for m at the given z.
26
+ z : complex
27
+ Evaluation point. The Stieltjes/Herglotz branch satisfies
28
+ sign(Im(m)) = sign(Im(z)) away from the real axis.
29
+ target : complex
30
+ Previous continuation value used to enforce continuity, or
31
+ target value.
32
+
33
+ Returns
34
+ -------
35
+ w : complex
36
+ Selected root corresponding to the Stieltjes branch.
37
+ """
38
+
39
+ z = complex(z)
40
+ roots = numpy.asarray(roots, dtype=numpy.complex128).ravel()
41
+
42
+ if roots.size == 0:
43
+ raise ValueError("roots must contain at least one candidate root.")
44
+
45
+ desired_sign = numpy.sign(z.imag)
46
+
47
+ # Apply a soft Herglotz sign filter: prefer roots with Im(w) having the
48
+ # same sign as Im(z), allowing tiny numerical violations near the axis.
49
+ imag_roots = numpy.imag(roots)
50
+
51
+ good = roots[numpy.sign(imag_roots) == desired_sign]
52
+ if good.size == 0:
53
+ good = roots[(imag_roots * desired_sign) > -1e-12]
54
+
55
+ candidates = good if good.size > 0 else roots
56
+ idx = int(numpy.argmin(numpy.abs(candidates - target)))
57
+ return candidates[idx]
58
+
59
+
60
+ # ==============
61
+ # stieltjes poly
62
+ # ==============
63
+
64
+ class StieltjesPoly(object):
65
+ """
66
+ Stieltjes-branch evaluator for an algebraic equation P(z, m) = 0.
67
+
68
+ This class represents the Stieltjes-branch solution m(z) of an algebraic
69
+ equation defined by a polynomial relation
70
+
71
+ P(z, m) = 0,
72
+
73
+ where P is a polynomial in z and m with monomial-basis coefficients.
74
+ The coefficient matrix ``a`` is fixed at construction time, and all
75
+ quantities depending only on ``a`` are precomputed. Evaluation at a
76
+ complex point ``z`` is performed via :meth:`evaluate`. The instance is
77
+ also callable; :meth:`__call__` supports scalar or vector inputs and
78
+ applies :meth:`evaluate` elementwise.
79
+
80
+ The Stieltjes branch is selected by initializing in the appropriate
81
+ half-plane using an asymptotic Stieltjes estimate and then performing
82
+ homotopy continuation along a straight-line path in the complex plane.
83
+
84
+ Parameters
85
+ ----------
86
+ a : ndarray, shape (L, K)
87
+ Coefficient matrix defining P(z, m) in the monomial basis. For fixed
88
+ z, the coefficients of the polynomial in m are assembled from powers
89
+ of z.
90
+ mom : callable, optional
91
+ A callable providing raw moments ``m_k = mom(k)``
92
+ eps : float or None, optional
93
+ If Im(z) == 0, use z + i*eps as the boundary evaluation point.
94
+ If None and Im(z) == 0, eps is set to 1e-8 * max(1, |z|).
95
+ height : float, default = 2.0
96
+ Imaginary height used for the starting point z0 in the same
97
+ half-plane as the evaluation point.
98
+ steps : int, default = 100
99
+ Number of continuation steps along the homotopy path.
100
+ order : int, default = 15
101
+ Number of moments in Stieltjes estimate
102
+
103
+ Methods
104
+ -------
105
+ evaluate(z)
106
+ Evaluate the Stieltjes-branch solution m(z) at a single complex point.
107
+
108
+ __call__(z)
109
+ If ``z`` is scalar, returns ``evaluate(z, ...)``.
110
+ If ``z`` is array-like, returns an array of the same shape, where each
111
+ entry is computed by calling ``evaluate`` on the corresponding element.
112
+
113
+ Notes
114
+ -----
115
+ If an input ``z`` value is real (Im(z) == 0), the evaluation is interpreted
116
+ as a boundary value by replacing that element with z + i*eps. If ``eps`` is
117
+ None, eps is chosen per element as 1e-8 * max(1, |z|).
118
+ """
119
+
120
+ def __init__(self, a, mom=None, eps=None, height=2.0, steps=100, order=15):
121
+ a = numpy.asarray(a)
122
+ if a.ndim != 2:
123
+ raise ValueError("a must be a 2D array.")
124
+
125
+ self.a = a
126
+ self.a_l, _ = a.shape
127
+ self.eps = eps
128
+ self.height = height
129
+ self.steps = steps
130
+ self.order = order
131
+ if order < 3:
132
+ raise RuntimeError("order is too small, choose a larger value.")
133
+
134
+ if mom is None:
135
+ self.mom = AlgebraicStieltjesMoments(a)
136
+ else:
137
+ self.mom = mom
138
+ self.mu = numpy.array([self.mom(j) for j in range(self.order+1)])
139
+ self.rad = max([numpy.abs(self.mu[j] / self.mu[j-1])
140
+ for j in range(2, self.order+1)])
141
+ self.rad = 1.0 + self.height * self.rad
142
+ self.z0_p = 1j * self.rad
143
+ self.m0_p = self._moment_est(self.z0_p)
144
+ self.z0_m = -1j * self.rad
145
+ self.m0_m = self._moment_est(self.z0_m)
146
+
147
+ def _moment_est(self, z):
148
+ # Estimate Stieltjes transform (root) using moment
149
+ # expansion
150
+ z = numpy.asarray(z)
151
+ pows = z[..., numpy.newaxis]**(-numpy.arange(self.order+1)-1)
152
+ return -numpy.sum(pows * self.mu, axis=-1)
153
+
154
+ def _poly_coeffs_m(self, z_val):
155
+ z_powers = z_val ** numpy.arange(self.a_l)
156
+ return (z_powers @ self.a)[::-1]
157
+
158
+ def _poly_roots(self, z_val):
159
+ coeffs = numpy.asarray(self._poly_coeffs_m(z_val),
160
+ dtype=numpy.complex128)
161
+ return numpy.roots(coeffs)
162
+
163
+ def evaluate(self, z, eps=None, height=2.0, steps=100, order=15, extrap=2,
164
+ num_angles=1):
165
+ """
166
+ Evaluate the Stieltjes-branch solution m(z) at a single point.
167
+
168
+ Parameters are as in the original function, except ``a`` is fixed at
169
+ construction time.
170
+ """
171
+ z = complex(z)
172
+
173
+ if steps < 1:
174
+ raise ValueError("steps must be a positive integer.")
175
+
176
+ # Boundary-value interpretation on the real axis
177
+ if z.imag == 0.0:
178
+ if self.eps is None:
179
+ eps_loc = 1e-8 * max(1.0, abs(z))
180
+ else:
181
+ eps_loc = float(self.eps)
182
+ z_eval = z + 1j * eps_loc
183
+ else:
184
+ z_eval = z
185
+
186
+ half_sign = numpy.sign(z_eval.imag)
187
+ if half_sign == 0.0:
188
+ half_sign = 1.0
189
+
190
+ # If z is outside radius of convergence, no homotopy
191
+ # necessary
192
+ if numpy.abs(z) > self.rad:
193
+ target = self._moment_est(z)
194
+ return select_root(self._poly_roots(z), z, target)
195
+
196
+ # z0 = z.real
197
+ # z0 = z0 + 1j*numpy.sqrt(self.rad**2 - z0**2)
198
+ # target = self._moment_est(z0)
199
+ # if half_sign > 0.0:
200
+ # z0 = self.z0_p
201
+ # target = self.m0_p
202
+ # else:
203
+ # z0 = self.z0_m
204
+ # target = self.m0_m
205
+
206
+ # Initialize at z0
207
+ res = 0
208
+ for theta in numpy.linspace(0, numpy.pi, num_angles+2)[1:-1]:
209
+ z0 = self.rad * numpy.exp(1j * theta) * half_sign
210
+ target = self._moment_est(z0)
211
+ coeffs = numpy.array([(-1)**k * comb(extrap, k + 1)
212
+ for k in range(extrap)])
213
+ w_prev = numpy.ones(extrap) * \
214
+ select_root(self._poly_roots(z0), z0, target)
215
+
216
+ # Straight-line homotopy continuation
217
+ for tau in numpy.linspace(0.0, 1.0, int(self.steps) + 1)[1:]:
218
+ z_tau = z0 + tau * (z_eval - z0)
219
+ target = numpy.dot(coeffs, w_prev)
220
+ w_prev[1:] = w_prev[0:-1]
221
+ w_prev[0] = select_root(self._poly_roots(z_tau), z_tau, target)
222
+ res += w_prev[0]
223
+
224
+ return res / num_angles
225
+
226
+ def __call__(self, z, progress=False, num_angles=1):
227
+ # Scalar fast-path
228
+ if numpy.isscalar(z):
229
+ return self.evaluate(z, num_angles=num_angles)
230
+
231
+ # Array-like: evaluate elementwise, preserving shape
232
+ z_arr = numpy.asarray(z)
233
+ out = numpy.empty(z_arr.shape, dtype=numpy.complex128)
234
+
235
+ # Iterate over indices so we can pass Python scalars into evaluate()
236
+ if progress:
237
+ indices = tqdm(numpy.ndindex(z_arr.shape), total=z_arr.size)
238
+ else:
239
+ indices = numpy.ndindex(z_arr.shape)
240
+ for idx in indices:
241
+ out[idx] = self.evaluate(z_arr[idx], num_angles=num_angles)
242
+
243
+ return out
244
+
245
+
246
+ # def stieltjes_poly(z, a, eps=None, height=2., steps=100, order=15):
247
+ # """
248
+ # Evaluate the Stieltjes-branch solution m(z) of an algebraic equation.
249
+
250
+ # The coefficients `a` define a polynomial relation
251
+ # P(z, m) = 0,
252
+ # where P is a polynomial in z and m with monomial-basis coefficients
253
+ # arranged so that for fixed z, the coefficients of the polynomial in m
254
+ # can be assembled from powers of z.
255
+
256
+ # Parameters
257
+ # ----------
258
+ # z : complex
259
+ # Evaluation point. Must be a single value.
260
+ # a : ndarray, shape (L, K)
261
+ # Coefficient matrix defining P(z, m) in the monomial basis.
262
+ # eps : float or None, optional
263
+ # If Im(z) == 0, use z + i*eps as the boundary evaluation point.
264
+ # If None and Im(z) == 0, eps is set to 1e-8 * max(1, |z|).
265
+ # height : float, default = 2.0
266
+ # Imaginary height used for the starting point z0 in the same
267
+ # half-plane as the evaluation point.
268
+ # steps : int, default = 100
269
+ # Number of continuation steps along the homotopy path.
270
+ # order : int, default = 15
271
+ # Number of moments in Stieltjes estimate
272
+
273
+ # Returns
274
+ # -------
275
+ # w : complex
276
+ # Value of the Stieltjes-branch solution m(z) (or m(z+i*eps) if z is
277
+ # real).
278
+ # """
279
+
280
+ # z = complex(z)
281
+ # a = numpy.asarray(a)
282
+
283
+ # if a.ndim != 2:
284
+ # raise ValueError('a must be a 2D array.')
285
+
286
+ # if steps < 1:
287
+ # raise ValueError("steps must be a positive integer.")
288
+
289
+ # a_l, _ = a.shape
290
+ # mom = AlgebraicStieltjesMoments(a)
291
+
292
+ # def poly_coeffs_m(z_val):
293
+ # z_powers = z_val ** numpy.arange(a_l)
294
+ # return (z_powers @ a)[::-1]
295
+
296
+ # def poly_roots(z_val):
297
+ # coeffs = numpy.asarray(poly_coeffs_m(z_val), dtype=numpy.complex128)
298
+ # return numpy.roots(coeffs)
299
+
300
+ # # If user asked a real-axis value, interpret as boundary value from C+.
301
+ # if z.imag == 0.0:
302
+ # if eps is None:
303
+ # eps = 1e-8 * max(1.0, abs(z))
304
+ # z_eval = z + 1j * float(eps)
305
+ # else:
306
+ # z_eval = z
307
+
308
+ # half_sign = numpy.sign(z_eval.imag)
309
+ # if half_sign == 0.0:
310
+ # half_sign = 1.0
311
+
312
+ # z0 = 1j * float(half_sign) * (1. + height * mom.radius(order))
313
+ # target = mom.stieltjes(z0, order)
314
+
315
+ # # Initialize at z0 via asymptotic / Im-sign selection.
316
+ # w_prev = select_root(poly_roots(z0), z0, target)
317
+
318
+ # # Straight-line homotopy from z0 to z_eval.
319
+ # for tau in numpy.linspace(0.0, 1.0, int(steps) + 1)[1:]:
320
+ # z_tau = z0 + tau * (z_eval - z0)
321
+ # w_prev = select_root(poly_roots(z_tau), z_tau, w_prev)
322
+
323
+ # return w_prev