freealg 0.1.12__tar.gz → 0.1.13__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 (36) hide show
  1. {freealg-0.1.12 → freealg-0.1.13}/PKG-INFO +1 -1
  2. freealg-0.1.13/freealg/__version__.py +1 -0
  3. freealg-0.1.13/freealg/_decompress.py +288 -0
  4. {freealg-0.1.12 → freealg-0.1.13}/freealg/freeform.py +4 -5
  5. {freealg-0.1.12 → freealg-0.1.13}/freealg.egg-info/PKG-INFO +1 -1
  6. freealg-0.1.12/freealg/__version__.py +0 -1
  7. freealg-0.1.12/freealg/_decompress.py +0 -180
  8. {freealg-0.1.12 → freealg-0.1.13}/AUTHORS.txt +0 -0
  9. {freealg-0.1.12 → freealg-0.1.13}/CHANGELOG.rst +0 -0
  10. {freealg-0.1.12 → freealg-0.1.13}/LICENSE.txt +0 -0
  11. {freealg-0.1.12 → freealg-0.1.13}/MANIFEST.in +0 -0
  12. {freealg-0.1.12 → freealg-0.1.13}/README.rst +0 -0
  13. {freealg-0.1.12 → freealg-0.1.13}/freealg/__init__.py +0 -0
  14. {freealg-0.1.12 → freealg-0.1.13}/freealg/_chebyshev.py +0 -0
  15. {freealg-0.1.12 → freealg-0.1.13}/freealg/_damp.py +0 -0
  16. {freealg-0.1.12 → freealg-0.1.13}/freealg/_jacobi.py +0 -0
  17. {freealg-0.1.12 → freealg-0.1.13}/freealg/_pade.py +0 -0
  18. {freealg-0.1.12 → freealg-0.1.13}/freealg/_plot_util.py +0 -0
  19. {freealg-0.1.12 → freealg-0.1.13}/freealg/_sample.py +0 -0
  20. {freealg-0.1.12 → freealg-0.1.13}/freealg/_support.py +0 -0
  21. {freealg-0.1.12 → freealg-0.1.13}/freealg/_util.py +0 -0
  22. {freealg-0.1.12 → freealg-0.1.13}/freealg/distributions/__init__.py +0 -0
  23. {freealg-0.1.12 → freealg-0.1.13}/freealg/distributions/_kesten_mckay.py +0 -0
  24. {freealg-0.1.12 → freealg-0.1.13}/freealg/distributions/_marchenko_pastur.py +0 -0
  25. {freealg-0.1.12 → freealg-0.1.13}/freealg/distributions/_meixner.py +0 -0
  26. {freealg-0.1.12 → freealg-0.1.13}/freealg/distributions/_wachter.py +0 -0
  27. {freealg-0.1.12 → freealg-0.1.13}/freealg/distributions/_wigner.py +0 -0
  28. {freealg-0.1.12 → freealg-0.1.13}/freealg.egg-info/SOURCES.txt +0 -0
  29. {freealg-0.1.12 → freealg-0.1.13}/freealg.egg-info/dependency_links.txt +0 -0
  30. {freealg-0.1.12 → freealg-0.1.13}/freealg.egg-info/not-zip-safe +0 -0
  31. {freealg-0.1.12 → freealg-0.1.13}/freealg.egg-info/requires.txt +0 -0
  32. {freealg-0.1.12 → freealg-0.1.13}/freealg.egg-info/top_level.txt +0 -0
  33. {freealg-0.1.12 → freealg-0.1.13}/pyproject.toml +0 -0
  34. {freealg-0.1.12 → freealg-0.1.13}/requirements.txt +0 -0
  35. {freealg-0.1.12 → freealg-0.1.13}/setup.cfg +0 -0
  36. {freealg-0.1.12 → freealg-0.1.13}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: freealg
3
- Version: 0.1.12
3
+ Version: 0.1.13
4
4
  Summary: Free probability for large matrices
5
5
  Keywords: leaderboard bot chat
6
6
  Platform: Linux
@@ -0,0 +1 @@
1
+ __version__ = "0.1.13"
@@ -0,0 +1,288 @@
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
+ def secant_complex(f, z0, z1, a=0+0j, tol=1e-12, max_iter=100,
19
+ alpha=0.5, max_bt=12, eps=1e-30, verbose=False):
20
+ """
21
+ Solves :math:``f(z) = a`` for many starting points simultaneously
22
+ using the secant method in the complex plane.
23
+
24
+ Parameters
25
+ ----------
26
+ f : callable
27
+ Function that accepts and returns complex `ndarray`s.
28
+
29
+ z0, z1 : array_like
30
+ Two initial guesses. ``z1`` may be broadcast to ``z0``.
31
+
32
+ a : complex or array_like, optional
33
+ Right‑hand‑side targets (broadcasted to ``z0``). Defaults to ``0+0j``.
34
+
35
+ tol : float, optional
36
+ Convergence criterion on ``|f(z) - a|``. Defaults to ``1e-12``.
37
+
38
+ max_iter : int, optional
39
+ Maximum number of secant iterations. Defaults to ``100``.
40
+
41
+ alpha : float, optional
42
+ Back‑tracking shrink factor (``0 < alpha < 1``). Defaults to ``0.5``.
43
+
44
+ max_bt : int, optional
45
+ Maximum back‑tracking trials per iteration. Defaults to ``12``.
46
+
47
+ eps : float, optional
48
+ Safeguard added to tiny denominators. Defaults to ``1e-30``.
49
+
50
+ verbose : bool, optional
51
+ If *True*, prints progress every 10 iterations.
52
+
53
+ Returns
54
+ -------
55
+ roots : ndarray
56
+ Estimated roots, shaped like the broadcast inputs.
57
+ residuals : ndarray
58
+ Final residuals ``|f(root) - a|``.
59
+ iterations : ndarray
60
+ Iteration count for each point.
61
+ """
62
+
63
+ # Broadcast inputs
64
+ z0, z1, a = numpy.broadcast_arrays(
65
+ numpy.asarray(z0, numpy.complex128),
66
+ numpy.asarray(z1, numpy.complex128),
67
+ numpy.asarray(a, numpy.complex128),
68
+ )
69
+ orig_shape = z0.shape
70
+ z0, z1, a = (x.ravel() for x in (z0, z1, a))
71
+
72
+ n_points = z0.size
73
+ roots = z1.copy()
74
+ iterations = numpy.zeros(n_points, dtype=int)
75
+
76
+ f0 = f(z0) - a
77
+ f1 = f(z1) - a
78
+ residuals = numpy.abs(f1)
79
+ converged = residuals < tol
80
+
81
+ # Entering main loop
82
+ for k in range(max_iter):
83
+ active = ~converged
84
+ if not active.any():
85
+ break
86
+
87
+ # Secant step
88
+ denom = f1 - f0
89
+ denom = numpy.where(numpy.abs(denom) < eps, denom + eps, denom)
90
+ dz = (z1 - z0) * f1 / denom
91
+ z2 = z1 - dz
92
+ f2 = f(z2) - a
93
+
94
+ # Line search by backtracking
95
+ worse = (numpy.abs(f2) >= numpy.abs(f1)) & active
96
+ if worse.any():
97
+ shrink = numpy.ones_like(dz)
98
+ for _ in range(max_bt):
99
+ shrink[worse] *= alpha
100
+ z_try = z1[worse] - shrink[worse] * dz[worse]
101
+ f_try = f(z_try) - a[worse]
102
+
103
+ improved = numpy.abs(f_try) < numpy.abs(f1[worse])
104
+ if not improved.any():
105
+ continue
106
+
107
+ idx = numpy.flatnonzero(worse)[improved]
108
+ z2[idx], f2[idx] = z_try[improved], f_try[improved]
109
+ worse[idx] = False
110
+ if not worse.any():
111
+ break
112
+
113
+ # Book‑keeping
114
+ newly_conv = (numpy.abs(f2) < tol) & active
115
+ converged[newly_conv] = True
116
+ iterations[newly_conv] = k + 1
117
+ roots[newly_conv] = z2[newly_conv]
118
+ residuals[newly_conv] = numpy.abs(f2[newly_conv])
119
+
120
+ still = active & ~newly_conv
121
+ z0[still], z1[still] = z1[still], z2[still]
122
+ f0[still], f1[still] = f1[still], f2[still]
123
+
124
+ if verbose and k % 10 == 0:
125
+ print(f"Iter {k}: {converged.sum()} / {n_points} converged")
126
+
127
+ # Non‑converged points
128
+ remaining = ~converged
129
+ roots[remaining] = z1[remaining]
130
+ residuals[remaining] = numpy.abs(f1[remaining])
131
+ iterations[remaining] = max_iter
132
+
133
+ return (
134
+ roots.reshape(orig_shape),
135
+ residuals.reshape(orig_shape),
136
+ iterations.reshape(orig_shape),
137
+ )
138
+
139
+ # ==========
140
+ # decompress
141
+ # ==========
142
+
143
+ def decompress(freeform, size, x=None, delta=1e-6, max_iter=500,
144
+ tolerance=1e-12):
145
+ """
146
+ Free decompression of spectral density.
147
+
148
+ Parameters
149
+ ----------
150
+
151
+ freeform : FreeForm
152
+ The initial freeform object of matrix to be decompressed
153
+
154
+ size : int
155
+ Size of the decompressed matrix.
156
+
157
+ x : numpy.array, default=None
158
+ Positions where density to be evaluated at. If `None`, an interval
159
+ slightly larger than the support interval will be used.
160
+
161
+ delta: float, default=1e-4
162
+ Size of the perturbation into the upper half plane for Plemelj's
163
+ formula.
164
+
165
+ max_iter: int, default=500
166
+ Maximum number of secant method iterations.
167
+
168
+ tolerance: float, default=1e-12
169
+ Tolerance for the solution obtained by the secant method solver.
170
+
171
+ Returns
172
+ -------
173
+
174
+ rho : numpy.array
175
+ Spectral density
176
+
177
+ See Also
178
+ --------
179
+
180
+ density
181
+ stieltjes
182
+
183
+ Notes
184
+ -----
185
+
186
+ Work in progress.
187
+
188
+ References
189
+ ----------
190
+
191
+ .. [1] tbd
192
+
193
+ Examples
194
+ --------
195
+
196
+ .. code-block:: python
197
+
198
+ >>> from freealg import FreeForm
199
+ """
200
+
201
+ alpha = size / freeform.n
202
+ m = freeform._eval_stieltjes
203
+ # Lower and upper bound on new support
204
+ hilb_lb = (1 / m(freeform.lam_m + delta * 1j)[1]).real
205
+ hilb_ub = (1 / m(freeform.lam_p + delta * 1j)[1]).real
206
+ lb = freeform.lam_m - (alpha - 1) * hilb_lb
207
+ ub = freeform.lam_p - (alpha - 1) * hilb_ub
208
+
209
+ # Create x if not given
210
+ if x is None:
211
+ radius = 0.5 * (ub - lb)
212
+ center = 0.5 * (ub + lb)
213
+ scale = 1.25
214
+ x_min = numpy.floor(center - radius * scale)
215
+ x_max = numpy.ceil(center + radius * scale)
216
+ x = numpy.linspace(x_min, x_max, 500)
217
+
218
+ # Ensure that input is an array
219
+ x = numpy.asarray(x)
220
+ target = x + delta * 1j
221
+ if numpy.isclose(alpha, 1.0):
222
+ return freeform.density(x), x, freeform.support
223
+
224
+ # Characteristic curve map
225
+ def _char_z(z):
226
+ return z + (1 / m(z)[1]) * (1 - alpha)
227
+
228
+ z0 = numpy.full(target.shape, numpy.mean(freeform.support) + delta*1j,
229
+ dtype=numpy.complex128)
230
+ z1 = z0 - numpy.log(alpha) * 1j
231
+
232
+ roots, _, _ = secant_complex(
233
+ _char_z, z0, z1,
234
+ a=target,
235
+ tol=tolerance,
236
+ max_iter=max_iter
237
+ )
238
+
239
+ # Plemelj's formula
240
+ z = roots
241
+ char_s = m(z)[1] / alpha
242
+ rho = numpy.maximum(0, char_s.imag / numpy.pi)
243
+ rho[numpy.isnan(rho) | numpy.isinf(rho)] = 0
244
+
245
+ return rho.reshape(*x.shape), x, (lb, ub)
246
+
247
+
248
+ # =======================
249
+ # reverse characteristics
250
+ # =======================
251
+
252
+ def reverse_characteristics(freeform, z_inits, T, iterations=500,
253
+ step_size=0.1, tolerance=1e-8):
254
+ """
255
+ """
256
+
257
+ t_span = (0, T)
258
+ t_eval = numpy.linspace(t_span[0], t_span[1], 50)
259
+
260
+ m = freeform._eval_stieltjes
261
+
262
+ def _char_z(z, t):
263
+ return z + (1 / m(z)[1]) * (1 - numpy.exp(t))
264
+
265
+ target_z, target_t = numpy.meshgrid(z_inits, t_eval)
266
+
267
+ z = numpy.full(target_z.shape, numpy.mean(freeform.support) - .1j,
268
+ dtype=numpy.complex128)
269
+
270
+ # Broken Newton steps can produce a lot of warnings. Removing them for now.
271
+ with numpy.errstate(all='ignore'):
272
+ for _ in range(iterations):
273
+ objective = _char_z(z, target_t) - target_z
274
+ mask = numpy.abs(objective) >= tolerance
275
+ if not numpy.any(mask):
276
+ break
277
+ z_m = z[mask]
278
+ t_m = target_t[mask]
279
+
280
+ # Perform finite difference approximation
281
+ dfdz = _char_z(z_m+tolerance, t_m) - _char_z(z_m-tolerance, t_m)
282
+ dfdz /= 2*tolerance
283
+ dfdz[dfdz == 0] = 1.0
284
+
285
+ # Perform Newton step
286
+ z[mask] = z_m - step_size * objective[mask] / dfdz
287
+
288
+ return z
@@ -783,7 +783,7 @@ class FreeForm(object):
783
783
  # decompress
784
784
  # ==========
785
785
 
786
- def decompress(self, size, x=None, iterations=500, eigvals=True,
786
+ def decompress(self, size, x=None, max_iter=500, eigvals=True,
787
787
  step_size=0.1, tolerance=1e-9, seed=None, plot=False,
788
788
  latex=False, save=False):
789
789
  """
@@ -799,8 +799,8 @@ class FreeForm(object):
799
799
  Positions where density to be evaluated at. If `None`, an interval
800
800
  slightly larger than the support interval will be used.
801
801
 
802
- iterations: int, default=500
803
- Maximum number of Newton iterations.
802
+ max_iter: int, default=500
803
+ Maximum number of secant method iterations.
804
804
 
805
805
  eigvals: bool, default=True
806
806
  Return estimated (sampled) eigenvalues as well as the density.
@@ -867,8 +867,7 @@ class FreeForm(object):
867
867
  size = int(size)
868
868
 
869
869
  rho, x, (lb, ub) = decompress(self, size, x=x, delta=self.delta,
870
- iterations=iterations,
871
- step_size=step_size, tolerance=tolerance)
870
+ max_iter=max_iter, tolerance=tolerance)
872
871
  x, rho = x.ravel(), rho.ravel()
873
872
 
874
873
  if plot:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: freealg
3
- Version: 0.1.12
3
+ Version: 0.1.13
4
4
  Summary: Free probability for large matrices
5
5
  Keywords: leaderboard bot chat
6
6
  Platform: Linux
@@ -1 +0,0 @@
1
- __version__ = "0.1.12"
@@ -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-6, iterations=500,
24
- step_size=0.1, tolerance=1e-9):
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
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
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes