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
freealg/_decompress.py DELETED
@@ -1,180 +0,0 @@
1
- # SPDX-License-Identifier: BSD-3-Clause
2
- # SPDX-FileType: SOURCE
3
- #
4
- # This program is free software: you can redistribute it and/or modify it under
5
- # the terms of the license found in the LICENSE.txt file in the root directory
6
- # of this source tree.
7
-
8
-
9
- # =======
10
- # Imports
11
- # =======
12
-
13
- import numpy
14
- # from scipy.integrate import solve_ivp
15
-
16
- __all__ = ['decompress', 'reverse_characteristics']
17
-
18
-
19
- # ==========
20
- # decompress
21
- # ==========
22
-
23
- def decompress(freeform, size, x=None, delta=1e-4, iterations=500,
24
- step_size=0.1, tolerance=1e-4):
25
- """
26
- Free decompression of spectral density.
27
-
28
- Parameters
29
- ----------
30
-
31
- freeform : FreeForm
32
- The initial freeform object of matrix to be decompressed
33
-
34
- size : int
35
- Size of the decompressed matrix.
36
-
37
- x : numpy.array, default=None
38
- Positions where density to be evaluated at. If `None`, an interval
39
- slightly larger than the support interval will be used.
40
-
41
- delta: float, default=1e-4
42
- Size of the perturbation into the upper half plane for Plemelj's
43
- formula.
44
-
45
- iterations: int, default=500
46
- Maximum number of Newton iterations.
47
-
48
- step_size: float, default=0.1
49
- Step size for Newton iterations.
50
-
51
- tolerance: float, default=1e-4
52
- Tolerance for the solution obtained by the Newton solver. Also
53
- used for the finite difference approximation to the derivative.
54
-
55
- Returns
56
- -------
57
-
58
- rho : numpy.array
59
- Spectral density
60
-
61
- See Also
62
- --------
63
-
64
- density
65
- stieltjes
66
-
67
- Notes
68
- -----
69
-
70
- Work in progress.
71
-
72
- References
73
- ----------
74
-
75
- .. [1] tbd
76
-
77
- Examples
78
- --------
79
-
80
- .. code-block:: python
81
-
82
- >>> from freealg import FreeForm
83
- """
84
-
85
- alpha = size / freeform.n
86
- m = freeform._eval_stieltjes
87
- # Lower and upper bound on new support
88
- hilb_lb = (1 / m(freeform.lam_m + delta * 1j)[1]).real
89
- hilb_ub = (1 / m(freeform.lam_p + delta * 1j)[1]).real
90
- lb = freeform.lam_m - (alpha - 1) * hilb_lb
91
- ub = freeform.lam_p - (alpha - 1) * hilb_ub
92
-
93
- # Create x if not given
94
- if x is None:
95
- radius = 0.5 * (ub - lb)
96
- center = 0.5 * (ub + lb)
97
- scale = 1.25
98
- x_min = numpy.floor(center - radius * scale)
99
- x_max = numpy.ceil(center + radius * scale)
100
- x = numpy.linspace(x_min, x_max, 500)
101
-
102
- def _char_z(z):
103
- return z + (1 / m(z)[1]) * (1 - alpha)
104
-
105
- # Ensure that input is an array
106
- x = numpy.asarray(x)
107
-
108
- target = x + delta * 1j
109
-
110
- z = numpy.full(target.shape, numpy.mean(freeform.support) - .1j,
111
- dtype=numpy.complex128)
112
-
113
- # Broken Newton steps can produce a lot of warnings. Removing them
114
- # for now.
115
- with numpy.errstate(all='ignore'):
116
- for _ in range(iterations):
117
- objective = _char_z(z) - target
118
- mask = numpy.abs(objective) >= tolerance
119
- if not numpy.any(mask):
120
- break
121
- z_m = z[mask]
122
-
123
- # Perform finite difference approximation
124
- dfdz = _char_z(z_m+tolerance) - _char_z(z_m-tolerance)
125
- dfdz /= 2*tolerance
126
- dfdz[dfdz == 0] = 1.0
127
-
128
- # Perform Newton step
129
- z[mask] = z_m - step_size * objective[mask] / dfdz
130
-
131
- # Plemelj's formula
132
- char_s = m(z)[1] / alpha
133
- rho = numpy.maximum(0, char_s.imag / numpy.pi)
134
- rho[numpy.isnan(rho) | numpy.isinf(rho)] = 0
135
- rho = rho.reshape(*x.shape)
136
-
137
- return rho, x, (lb, ub)
138
-
139
-
140
- # =======================
141
- # reverse characteristics
142
- # =======================
143
-
144
- def reverse_characteristics(freeform, z_inits, T, iterations=500,
145
- step_size=0.1, tolerance=1e-8):
146
- """
147
- """
148
-
149
- t_span = (0, T)
150
- t_eval = numpy.linspace(t_span[0], t_span[1], 50)
151
-
152
- m = freeform._eval_stieltjes
153
-
154
- def _char_z(z, t):
155
- return z + (1 / m(z)[1]) * (1 - numpy.exp(t))
156
-
157
- target_z, target_t = numpy.meshgrid(z_inits, t_eval)
158
-
159
- z = numpy.full(target_z.shape, numpy.mean(freeform.support) - .1j,
160
- dtype=numpy.complex128)
161
-
162
- # Broken Newton steps can produce a lot of warnings. Removing them for now.
163
- with numpy.errstate(all='ignore'):
164
- for _ in range(iterations):
165
- objective = _char_z(z, target_t) - target_z
166
- mask = numpy.abs(objective) >= tolerance
167
- if not numpy.any(mask):
168
- break
169
- z_m = z[mask]
170
- t_m = target_t[mask]
171
-
172
- # Perform finite difference approximation
173
- dfdz = _char_z(z_m+tolerance, t_m) - _char_z(z_m-tolerance, t_m)
174
- dfdz /= 2*tolerance
175
- dfdz[dfdz == 0] = 1.0
176
-
177
- # Perform Newton step
178
- z[mask] = z_m - step_size * objective[mask] / dfdz
179
-
180
- return z
freealg/_jacobi.py DELETED
@@ -1,218 +0,0 @@
1
- # SPDX-FileCopyrightText: Copyright 2025, 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
- from scipy.special import eval_jacobi, roots_jacobi
16
- from scipy.special import gammaln, beta as Beta
17
-
18
- __all__ = ['jacobi_sample_proj', 'jacobi_kernel_proj', 'jacobi_approx',
19
- 'jacobi_stieltjes']
20
-
21
-
22
- # ==============
23
- # jacobi sq norm
24
- # ==============
25
-
26
- def jacobi_sq_norm(k, alpha, beta):
27
- """
28
- Norm of P_k
29
- Special-case k = 0 to avoid gamma(0) issues when alpha + beta + 1 = 0.
30
- """
31
-
32
- if k == 0:
33
- return 2.0**(alpha + beta + 1) * Beta(alpha + 1, beta + 1)
34
-
35
- # Use logs instead to avoid overflow in gamma function.
36
- lg_num = (alpha + beta + 1) * numpy.log(2.0) \
37
- + gammaln(k + alpha + 1) \
38
- + gammaln(k + beta + 1)
39
-
40
- lg_den = numpy.log(2*k + alpha + beta + 1) \
41
- + gammaln(k + 1) \
42
- + gammaln(k + alpha + beta + 1)
43
-
44
- return numpy.exp(lg_num - lg_den)
45
-
46
-
47
- # ==================
48
- # jacobi sample proj
49
- # ==================
50
-
51
- def jacobi_sample_proj(eig, support, K=10, alpha=0.0, beta=0.0, reg=0.0):
52
- """
53
- """
54
-
55
- lam_m, lam_p = support
56
-
57
- # Convert to [-1, 1] interval
58
- x = (2.0 * eig - (lam_p + lam_m)) / (lam_p - lam_m)
59
-
60
- psi = numpy.empty(K + 1)
61
-
62
- # Empirical moments and coefficients
63
- for k in range(K + 1):
64
- moment = numpy.mean(eval_jacobi(k, alpha, beta, x))
65
- N_k = jacobi_sq_norm(k, alpha, beta) # normalization
66
-
67
- if k == 0:
68
- # Do not penalize at k=0, as this keeps unit mass.
69
- # k=0 has unit mass, while k>0 has zero mass by orthogonality.
70
- penalty = 0
71
- else:
72
- penalty = reg * (k / (K + 1))**2
73
-
74
- # Add regularization on the diagonal
75
- psi[k] = moment / (N_k + penalty)
76
-
77
- return psi
78
-
79
-
80
- # ==================
81
- # jacobi kernel proj
82
- # ==================
83
-
84
- def jacobi_kernel_proj(xs, pdf, support, K=10, alpha=0.0, beta=0.0, reg=0.0):
85
- """
86
- Same moments as `jacobi_proj`, but the target is a *continuous* density
87
- given on a grid (xs, pdf).
88
- """
89
-
90
- lam_m, lam_p = support
91
- t = (2.0 * xs - (lam_p + lam_m)) / (lam_p - lam_m) # map to [-1,1]
92
- psi = numpy.empty(K + 1)
93
-
94
- for k in range(K + 1):
95
- Pk = eval_jacobi(k, alpha, beta, t)
96
- N_k = jacobi_sq_norm(k, alpha, beta)
97
-
98
- # \int P_k(t) w(t) \rho(t) dt. w(t) cancels with pdf already being rho
99
- moment = numpy.trapz(Pk * pdf, xs)
100
-
101
- if k == 0:
102
- penalty = 0
103
- else:
104
- penalty = reg * (k / (K + 1))**2
105
-
106
- psi[k] = moment / (N_k + penalty)
107
-
108
- return psi
109
-
110
-
111
- # =============
112
- # jacobi approx
113
- # =============
114
-
115
- def jacobi_approx(x, psi, support, alpha=0.0, beta=0.0):
116
- """
117
- Reconstruct Jacobi approximation.
118
-
119
- Parameters
120
- ----------
121
-
122
- psi : array_like, shape (K+1, )
123
- Jacobi expansion coefficients.
124
-
125
- x : array_like
126
- Points (in original eigenvalue scale) to evaluate at.
127
-
128
- support : tuple (lam_m, lam_p)
129
-
130
- alpha : float
131
- Jacobi parameter.
132
-
133
- beta : float
134
- Jacobi parameter.
135
-
136
- Returns
137
- -------
138
-
139
- rho : ndarray
140
- """
141
-
142
- lam_m, lam_p = support
143
- t = (2 * x - (lam_p + lam_m)) / (lam_p - lam_m)
144
- w = (1 - t)**alpha * (1 + t)**beta
145
- P = numpy.vstack([eval_jacobi(k, alpha, beta, t) for k in range(len(psi))])
146
-
147
- rho_t = w * (psi @ P) # density in t–variable
148
- rho_x = rho_t * (2.0 / (lam_p - lam_m)) # back to x–variable
149
-
150
- return rho_x
151
-
152
-
153
- # ================
154
- # jacobi stieltjes
155
- # ================
156
-
157
- def jacobi_stieltjes(z, psi, support, alpha=0.0, beta=0.0, n_base=40):
158
- """
159
- Compute m(z) = sum_k psi_k * m_k(z) where
160
-
161
- m_k(z) = \\int w^{(alpha, beta)}(t) P_k^{(alpha, beta)}(t) / (u(z)-t) dt
162
-
163
- Each m_k is evaluated *separately* with a Gauss–Jacobi rule sized
164
- for that k. This follows the user's request: 1 quadrature rule per P_k.
165
-
166
- Parameters
167
- ----------
168
-
169
- z : complex or ndarray
170
-
171
- psi : (K+1,) array_like
172
-
173
- support : (lambda_minus, lambda_plus)
174
-
175
- alpha, beta : float
176
-
177
- n_base : int
178
- Minimum quadrature size. For degree-k polynomial we use
179
- n_quad = max(n_base, k+1).
180
-
181
- Returns
182
- -------
183
-
184
- m1 : ndarray (same shape as z)
185
-
186
- m12 : ndarray (same shape as z)
187
- """
188
-
189
- z = numpy.asarray(z, dtype=numpy.complex128)
190
- lam_minus, lam_plus = support
191
- span = lam_plus - lam_minus
192
- centre = 0.5 * (lam_plus + lam_minus)
193
- u_z = (2.0 / span) * (z - centre) # map z -> u
194
-
195
- m_total = numpy.zeros_like(z, dtype=numpy.complex128)
196
-
197
- for k, psi_k in enumerate(psi):
198
- # Select quadrature size tailored to this P_k
199
- n_quad = max(n_base, k + 1)
200
- t_nodes, w_nodes = roots_jacobi(n_quad, alpha, beta) # (n_quad,)
201
-
202
- # Evaluate P_k at the quadrature nodes
203
- P_k_nodes = eval_jacobi(k, alpha, beta, t_nodes) # (n_quad,)
204
-
205
- # Integrand values at nodes: w_nodes already include the weight
206
- integrand = w_nodes * P_k_nodes # (n_quad,)
207
-
208
- # Broadcast over z: shape (n_quad, ...) / ...
209
- diff = u_z[None, ...] - t_nodes[:, None, None] # (n_quad, Ny, Nx)
210
- m_k = (integrand[:, None, None] / diff).sum(axis=0)
211
-
212
- # Accumulate with factor 2/span
213
- m_total += psi_k * (2.0 / span) * m_k
214
-
215
- # We use a negative sign convention
216
- m_total = -m_total
217
-
218
- return m_total
freealg/_support.py DELETED
@@ -1,85 +0,0 @@
1
- import numpy
2
- from scipy.stats import gaussian_kde
3
-
4
- def detect_support(eigs, method='interior_smooth', k = None, p = 0.001, **kwargs):
5
- """
6
- Estimates the support of the eigenvalue density.
7
-
8
- Parameters
9
- ----------
10
- method : {``'range'``, ``'jackknife'``, ``'regression'``, ``'interior'``,
11
- ``'interior_smooth'``}, \
12
- default= ``'jackknife'``
13
- The method of support estimation:
14
-
15
- * ``'range'``: no estimation; the support is the range of the eigenvalues
16
- * ``'jackknife'``: estimates the support using Quenouille's [1]
17
- jackknife estimator. Fast and simple, more accurate than the range.
18
- * ``'regression'``: estimates the support by performing a regression under
19
- the assumption that the edge behavior is of square-root type. Often
20
- most accurate.
21
- * ``'interior'``: estimates a support assuming the range overestimates;
22
- uses quantiles (p, 1-p).
23
- * ``'interior_smooth'``: same as ``'interior'`` but using kernel density
24
- estimation.
25
-
26
- k : int, default = None
27
- Number of extreme order statistics to use for ``method='regression'``.
28
-
29
- p : float, default=0.001
30
- The edges of the support of the distribution is detected by the
31
- :math:`p`-quantile on the left and :math:`(1-p)`-quantile on the right
32
- where ``method='interior'`` or ``method='interior_smooth'``.
33
- This value should be between 0 and 1, ideally a small number close to
34
- zero.
35
-
36
- References
37
- ----------
38
-
39
- .. [1] Quenouille, M. H. (1949, July). Approximate tests of correlation in time-series.
40
- In Mathematical Proceedings of the Cambridge Philosophical Society (Vol. 45, No. 3,
41
- pp. 483-484). Cambridge University Press.
42
- """
43
-
44
- if method=='range':
45
- lam_m = eigs.min()
46
- lam_p = eigs.max()
47
-
48
- elif method=='jackknife':
49
- x, n = numpy.sort(eigs), len(eigs)
50
- lam_m = x[0] - (n - 1)/n * (x[1] - x[0])
51
- lam_p = x[-1] + (n - 1)/n * (x[-1] - x[-2])
52
-
53
- elif method=='regression':
54
- x, n = numpy.sort(eigs), len(eigs)
55
- if k is None:
56
- k = int(round(n ** (2/3)))
57
- k = max(5, min(k, n // 2))
58
-
59
- # The theoretical cdf near the edge behaves like const*(x - a)^{3/2},
60
- # so (i/n) ≈ (x - a)^{3/2} ⇒ x ≈ a + const*(i/n)^{2/3}.
61
- y = ((numpy.arange(1, k + 1) - 0.5) / n) ** (2 / 3)
62
-
63
- # Left edge: regress x_{(i)} on y
64
- _, lam_m = numpy.polyfit(y, x[:k], 1)
65
-
66
- # Right edge: regress x_{(n-i+1)} on y
67
- _, lam_p = numpy.polyfit(y, x[-k:][::-1], 1)
68
-
69
- elif method=='interior':
70
- lam_m, lam_p = numpy.quantile(eigs, [p, 1-p])
71
-
72
- elif method=='interior_smooth':
73
- kde = gaussian_kde(eigs)
74
- xs = numpy.linspace(eigs.min(), eigs.max(), 1000)
75
- fs = kde(xs)
76
-
77
- cdf = numpy.cumsum(fs)
78
- cdf /= cdf[-1]
79
-
80
- lam_m = numpy.interp(p, cdf, xs)
81
- lam_p = numpy.interp(1-p, cdf, xs)
82
- else:
83
- raise NotImplementedError("Unknown method")
84
-
85
- return lam_m, lam_p