freealg 0.1.11__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.
- {freealg-0.1.11 → freealg-0.1.13}/PKG-INFO +3 -2
- {freealg-0.1.11 → freealg-0.1.13}/README.rst +1 -1
- freealg-0.1.13/freealg/__version__.py +1 -0
- freealg-0.1.13/freealg/_decompress.py +288 -0
- {freealg-0.1.11 → freealg-0.1.13}/freealg/freeform.py +9 -9
- {freealg-0.1.11 → freealg-0.1.13}/freealg.egg-info/PKG-INFO +3 -2
- {freealg-0.1.11 → freealg-0.1.13}/freealg.egg-info/requires.txt +1 -0
- {freealg-0.1.11 → freealg-0.1.13}/requirements.txt +1 -0
- freealg-0.1.11/freealg/__version__.py +0 -1
- freealg-0.1.11/freealg/_decompress.py +0 -180
- {freealg-0.1.11 → freealg-0.1.13}/AUTHORS.txt +0 -0
- {freealg-0.1.11 → freealg-0.1.13}/CHANGELOG.rst +0 -0
- {freealg-0.1.11 → freealg-0.1.13}/LICENSE.txt +0 -0
- {freealg-0.1.11 → freealg-0.1.13}/MANIFEST.in +0 -0
- {freealg-0.1.11 → freealg-0.1.13}/freealg/__init__.py +0 -0
- {freealg-0.1.11 → freealg-0.1.13}/freealg/_chebyshev.py +0 -0
- {freealg-0.1.11 → freealg-0.1.13}/freealg/_damp.py +0 -0
- {freealg-0.1.11 → freealg-0.1.13}/freealg/_jacobi.py +0 -0
- {freealg-0.1.11 → freealg-0.1.13}/freealg/_pade.py +0 -0
- {freealg-0.1.11 → freealg-0.1.13}/freealg/_plot_util.py +0 -0
- {freealg-0.1.11 → freealg-0.1.13}/freealg/_sample.py +0 -0
- {freealg-0.1.11 → freealg-0.1.13}/freealg/_support.py +0 -0
- {freealg-0.1.11 → freealg-0.1.13}/freealg/_util.py +0 -0
- {freealg-0.1.11 → freealg-0.1.13}/freealg/distributions/__init__.py +0 -0
- {freealg-0.1.11 → freealg-0.1.13}/freealg/distributions/_kesten_mckay.py +0 -0
- {freealg-0.1.11 → freealg-0.1.13}/freealg/distributions/_marchenko_pastur.py +0 -0
- {freealg-0.1.11 → freealg-0.1.13}/freealg/distributions/_meixner.py +0 -0
- {freealg-0.1.11 → freealg-0.1.13}/freealg/distributions/_wachter.py +0 -0
- {freealg-0.1.11 → freealg-0.1.13}/freealg/distributions/_wigner.py +0 -0
- {freealg-0.1.11 → freealg-0.1.13}/freealg.egg-info/SOURCES.txt +0 -0
- {freealg-0.1.11 → freealg-0.1.13}/freealg.egg-info/dependency_links.txt +0 -0
- {freealg-0.1.11 → freealg-0.1.13}/freealg.egg-info/not-zip-safe +0 -0
- {freealg-0.1.11 → freealg-0.1.13}/freealg.egg-info/top_level.txt +0 -0
- {freealg-0.1.11 → freealg-0.1.13}/pyproject.toml +0 -0
- {freealg-0.1.11 → freealg-0.1.13}/setup.cfg +0 -0
- {freealg-0.1.11 → 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.
|
|
3
|
+
Version: 0.1.13
|
|
4
4
|
Summary: Free probability for large matrices
|
|
5
5
|
Keywords: leaderboard bot chat
|
|
6
6
|
Platform: Linux
|
|
@@ -31,6 +31,7 @@ Requires-Dist: texplot
|
|
|
31
31
|
Requires-Dist: matplotlib
|
|
32
32
|
Requires-Dist: colorcet
|
|
33
33
|
Requires-Dist: statsmodels
|
|
34
|
+
Requires-Dist: numba
|
|
34
35
|
Provides-Extra: test
|
|
35
36
|
Requires-Dist: tox; extra == "test"
|
|
36
37
|
Requires-Dist: pytest-cov; extra == "test"
|
|
@@ -108,7 +109,7 @@ smaller Wishart matrix.
|
|
|
108
109
|
>>> A = mp.matrix(1000) # Sample a 1000 x 1000 Wishart matrix
|
|
109
110
|
>>> eigs = fa.eigfree(A, 100_000) # Estimate the eigenvalues of 100000 x 100000
|
|
110
111
|
|
|
111
|
-
For more details on how to interface with *freealg* check out the `Quick Start Guide <https://github.com/ameli/freealg/blob/main/notebooks/quick_start.ipynb
|
|
112
|
+
For more details on how to interface with *freealg* check out the `Quick Start Guide <https://github.com/ameli/freealg/blob/main/notebooks/quick_start.ipynb>`__.
|
|
112
113
|
|
|
113
114
|
|
|
114
115
|
Test
|
|
@@ -42,7 +42,7 @@ smaller Wishart matrix.
|
|
|
42
42
|
>>> A = mp.matrix(1000) # Sample a 1000 x 1000 Wishart matrix
|
|
43
43
|
>>> eigs = fa.eigfree(A, 100_000) # Estimate the eigenvalues of 100000 x 100000
|
|
44
44
|
|
|
45
|
-
For more details on how to interface with *freealg* check out the `Quick Start Guide <https://github.com/ameli/freealg/blob/main/notebooks/quick_start.ipynb
|
|
45
|
+
For more details on how to interface with *freealg* check out the `Quick Start Guide <https://github.com/ameli/freealg/blob/main/notebooks/quick_start.ipynb>`__.
|
|
46
46
|
|
|
47
47
|
|
|
48
48
|
Test
|
|
@@ -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,8 +783,8 @@ class FreeForm(object):
|
|
|
783
783
|
# decompress
|
|
784
784
|
# ==========
|
|
785
785
|
|
|
786
|
-
def decompress(self, size, x=None,
|
|
787
|
-
step_size=0.1, tolerance=1e-
|
|
786
|
+
def decompress(self, size, x=None, max_iter=500, eigvals=True,
|
|
787
|
+
step_size=0.1, tolerance=1e-9, seed=None, plot=False,
|
|
788
788
|
latex=False, save=False):
|
|
789
789
|
"""
|
|
790
790
|
Free decompression of spectral density.
|
|
@@ -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
|
-
|
|
803
|
-
Maximum number of
|
|
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.
|
|
@@ -808,7 +808,7 @@ class FreeForm(object):
|
|
|
808
808
|
step_size: float, default=0.1
|
|
809
809
|
Step size for Newton iterations.
|
|
810
810
|
|
|
811
|
-
tolerance: float, default=1e-
|
|
811
|
+
tolerance: float, default=1e-9
|
|
812
812
|
Tolerance for the solution obtained by the Newton solver. Also
|
|
813
813
|
used for the finite difference approximation to the derivative.
|
|
814
814
|
|
|
@@ -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
|
-
|
|
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:
|
|
@@ -956,12 +955,13 @@ def eigfree(A, N = None, psd = None):
|
|
|
956
955
|
ff.n = n_s
|
|
957
956
|
|
|
958
957
|
# Perform fit and estimate eigenvalues
|
|
959
|
-
order = 1 + int(len(samples)**.
|
|
958
|
+
order = 1 + int(len(samples)**.2)
|
|
960
959
|
ff.fit(method='chebyshev', K=order, projection='sample', damp='jackson',
|
|
961
|
-
force=True, plot=False, latex=False, save=False, reg=0.
|
|
960
|
+
force=True, plot=False, latex=False, save=False, reg=0.05)
|
|
962
961
|
_, _, eigs = ff.decompress(N)
|
|
963
962
|
|
|
964
963
|
if psd:
|
|
965
964
|
eigs = numpy.abs(eigs)
|
|
965
|
+
eigs.sort()
|
|
966
966
|
|
|
967
967
|
return eigs
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: freealg
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.13
|
|
4
4
|
Summary: Free probability for large matrices
|
|
5
5
|
Keywords: leaderboard bot chat
|
|
6
6
|
Platform: Linux
|
|
@@ -31,6 +31,7 @@ Requires-Dist: texplot
|
|
|
31
31
|
Requires-Dist: matplotlib
|
|
32
32
|
Requires-Dist: colorcet
|
|
33
33
|
Requires-Dist: statsmodels
|
|
34
|
+
Requires-Dist: numba
|
|
34
35
|
Provides-Extra: test
|
|
35
36
|
Requires-Dist: tox; extra == "test"
|
|
36
37
|
Requires-Dist: pytest-cov; extra == "test"
|
|
@@ -108,7 +109,7 @@ smaller Wishart matrix.
|
|
|
108
109
|
>>> A = mp.matrix(1000) # Sample a 1000 x 1000 Wishart matrix
|
|
109
110
|
>>> eigs = fa.eigfree(A, 100_000) # Estimate the eigenvalues of 100000 x 100000
|
|
110
111
|
|
|
111
|
-
For more details on how to interface with *freealg* check out the `Quick Start Guide <https://github.com/ameli/freealg/blob/main/notebooks/quick_start.ipynb
|
|
112
|
+
For more details on how to interface with *freealg* check out the `Quick Start Guide <https://github.com/ameli/freealg/blob/main/notebooks/quick_start.ipynb>`__.
|
|
112
113
|
|
|
113
114
|
|
|
114
115
|
Test
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.1.11"
|
|
@@ -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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|