freealg 0.5.2__tar.gz → 0.5.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 (38) hide show
  1. {freealg-0.5.2 → freealg-0.5.4}/PKG-INFO +1 -1
  2. freealg-0.5.4/freealg/__version__.py +1 -0
  3. {freealg-0.5.2 → freealg-0.5.4}/freealg/_chebyshev.py +26 -12
  4. {freealg-0.5.2 → freealg-0.5.4}/freealg/_jacobi.py +21 -9
  5. {freealg-0.5.2 → freealg-0.5.4}/freealg/_linalg.py +1 -1
  6. freealg-0.5.4/freealg/_series.py +454 -0
  7. {freealg-0.5.2 → freealg-0.5.4}/freealg/freeform.py +13 -12
  8. {freealg-0.5.2 → freealg-0.5.4}/freealg.egg-info/PKG-INFO +1 -1
  9. freealg-0.5.2/freealg/__version__.py +0 -1
  10. freealg-0.5.2/freealg/_series.py +0 -145
  11. {freealg-0.5.2 → freealg-0.5.4}/AUTHORS.txt +0 -0
  12. {freealg-0.5.2 → freealg-0.5.4}/CHANGELOG.rst +0 -0
  13. {freealg-0.5.2 → freealg-0.5.4}/LICENSE.txt +0 -0
  14. {freealg-0.5.2 → freealg-0.5.4}/MANIFEST.in +0 -0
  15. {freealg-0.5.2 → freealg-0.5.4}/README.rst +0 -0
  16. {freealg-0.5.2 → freealg-0.5.4}/freealg/__init__.py +0 -0
  17. {freealg-0.5.2 → freealg-0.5.4}/freealg/_damp.py +0 -0
  18. {freealg-0.5.2 → freealg-0.5.4}/freealg/_decompress.py +0 -0
  19. {freealg-0.5.2 → freealg-0.5.4}/freealg/_pade.py +0 -0
  20. {freealg-0.5.2 → freealg-0.5.4}/freealg/_plot_util.py +0 -0
  21. {freealg-0.5.2 → freealg-0.5.4}/freealg/_sample.py +0 -0
  22. {freealg-0.5.2 → freealg-0.5.4}/freealg/_support.py +0 -0
  23. {freealg-0.5.2 → freealg-0.5.4}/freealg/_util.py +0 -0
  24. {freealg-0.5.2 → freealg-0.5.4}/freealg/distributions/__init__.py +0 -0
  25. {freealg-0.5.2 → freealg-0.5.4}/freealg/distributions/_kesten_mckay.py +0 -0
  26. {freealg-0.5.2 → freealg-0.5.4}/freealg/distributions/_marchenko_pastur.py +0 -0
  27. {freealg-0.5.2 → freealg-0.5.4}/freealg/distributions/_meixner.py +0 -0
  28. {freealg-0.5.2 → freealg-0.5.4}/freealg/distributions/_wachter.py +0 -0
  29. {freealg-0.5.2 → freealg-0.5.4}/freealg/distributions/_wigner.py +0 -0
  30. {freealg-0.5.2 → freealg-0.5.4}/freealg.egg-info/SOURCES.txt +0 -0
  31. {freealg-0.5.2 → freealg-0.5.4}/freealg.egg-info/dependency_links.txt +0 -0
  32. {freealg-0.5.2 → freealg-0.5.4}/freealg.egg-info/not-zip-safe +0 -0
  33. {freealg-0.5.2 → freealg-0.5.4}/freealg.egg-info/requires.txt +0 -0
  34. {freealg-0.5.2 → freealg-0.5.4}/freealg.egg-info/top_level.txt +0 -0
  35. {freealg-0.5.2 → freealg-0.5.4}/pyproject.toml +0 -0
  36. {freealg-0.5.2 → freealg-0.5.4}/requirements.txt +0 -0
  37. {freealg-0.5.2 → freealg-0.5.4}/setup.cfg +0 -0
  38. {freealg-0.5.2 → freealg-0.5.4}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: freealg
3
- Version: 0.5.2
3
+ Version: 0.5.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.5.4"
@@ -13,7 +13,8 @@
13
13
 
14
14
  import numpy
15
15
  from scipy.special import eval_chebyu
16
- from ._series import partial_sum, wynn_epsilon
16
+ from ._series import partial_sum, wynn_epsilon, wynn_rho, levin_u, \
17
+ weniger_delta, brezinski_theta
17
18
 
18
19
  __all__ = ['chebyshev_sample_proj', 'chebyshev_kernel_proj',
19
20
  'chebyshev_density', 'chebyshev_stieltjes']
@@ -164,7 +165,7 @@ def chebyshev_density(x, psi, support):
164
165
  # chebushev stieltjes
165
166
  # ===================
166
167
 
167
- def chebyshev_stieltjes(z, psi, support, use_wynn_epsilon=False):
168
+ def chebyshev_stieltjes(z, psi, support, continuation='pade'):
168
169
  """
169
170
  Compute the Stieltjes transform m(z) for a Chebyshev‐II expansion
170
171
 
@@ -197,8 +198,8 @@ def chebyshev_stieltjes(z, psi, support, use_wynn_epsilon=False):
197
198
  support : tuple
198
199
  The support interval of the original density.
199
200
 
200
- use_wynn_epsilon : bool, default=False
201
- Use Wynn epsilon, otherwise assumes Pade is used.
201
+ continuation : str, default= ``'pade'``
202
+ Methof of analytiv continuation.
202
203
 
203
204
  Returns
204
205
  -------
@@ -225,14 +226,7 @@ def chebyshev_stieltjes(z, psi, support, use_wynn_epsilon=False):
225
226
  J = numpy.where(Jp.imag > 0, Jm, Jp)
226
227
 
227
228
  # This depends on the method of analytic continuation
228
- if use_wynn_epsilon:
229
- # Flatten J before passing to Wynn method.
230
- psi_zero = numpy.concatenate([[0], psi])
231
- Sn = partial_sum(psi_zero, J.ravel())
232
- S = wynn_epsilon(Sn)
233
- S = S.reshape(J.shape)
234
-
235
- else:
229
+ if continuation == 'pade':
236
230
  # Build powers J^(k+1) for k = 0, ..., K
237
231
  K = len(psi) - 1
238
232
  Jpow = J[..., None] ** numpy.arange(1, K+2) # shape: (..., K+1)
@@ -240,6 +234,26 @@ def chebyshev_stieltjes(z, psi, support, use_wynn_epsilon=False):
240
234
  # Summing psi_k * J^(k+1)
241
235
  S = numpy.sum(psi * Jpow, axis=-1)
242
236
 
237
+ else:
238
+ # Flatten J before passing to Wynn method.
239
+ psi_zero = numpy.concatenate([[0], psi])
240
+ Sn = partial_sum(psi_zero, J.ravel(), p=0)
241
+
242
+ if continuation == 'wynn-eps':
243
+ S = wynn_epsilon(Sn)
244
+ elif continuation == 'wynn-rho':
245
+ S = wynn_rho(Sn)
246
+ elif continuation == 'levin':
247
+ S = levin_u(Sn)
248
+ elif continuation == 'weniger':
249
+ S = weniger_delta(Sn)
250
+ elif continuation == 'brezinski':
251
+ S = brezinski_theta(Sn)
252
+ else:
253
+ raise NotImplementedError('"continuation" is invalid.')
254
+
255
+ S = S.reshape(J.shape)
256
+
243
257
  # Assemble m(z)
244
258
  m_z = -(2.0 / span) * numpy.pi * S
245
259
 
@@ -14,7 +14,8 @@
14
14
  import numpy
15
15
  from scipy.special import eval_jacobi, roots_jacobi
16
16
  from scipy.special import gammaln, beta as Beta
17
- from ._series import wynn_epsilon
17
+ from ._series import wynn_epsilon, wynn_rho, levin_u, weniger_delta, \
18
+ brezinski_theta
18
19
 
19
20
  __all__ = ['jacobi_sample_proj', 'jacobi_kernel_proj', 'jacobi_density',
20
21
  'jacobi_stieltjes']
@@ -156,7 +157,7 @@ def jacobi_density(x, psi, support, alpha=0.0, beta=0.0):
156
157
  # ================
157
158
 
158
159
  def jacobi_stieltjes(z, psi, support, alpha=0.0, beta=0.0, n_base=40,
159
- use_wynn_epsilon=False):
160
+ continuation='pade'):
160
161
  """
161
162
  Compute m(z) = sum_k psi_k * m_k(z) where
162
163
 
@@ -180,8 +181,8 @@ def jacobi_stieltjes(z, psi, support, alpha=0.0, beta=0.0, n_base=40,
180
181
  Minimum quadrature size. For degree-k polynomial we use
181
182
  n_quad = max(n_base, k+1).
182
183
 
183
- use_wynn_epsilon : bool, default=False
184
- Use Wynn epsilon, otherwise assumes Pade is used.
184
+ continuation : str, default= ``'pade'``
185
+ Methof of analytiv continuation.
185
186
 
186
187
  Returns
187
188
  -------
@@ -203,7 +204,7 @@ def jacobi_stieltjes(z, psi, support, alpha=0.0, beta=0.0, n_base=40,
203
204
 
204
205
  m_total = numpy.zeros_like(z, dtype=numpy.complex128)
205
206
 
206
- if use_wynn_epsilon:
207
+ if continuation != 'pade':
207
208
  # Stores m with the ravel size of z.
208
209
  m_partial = numpy.zeros((psi.size, z.size), dtype=numpy.complex128)
209
210
 
@@ -226,7 +227,7 @@ def jacobi_stieltjes(z, psi, support, alpha=0.0, beta=0.0, n_base=40,
226
227
  m_k = (2.0 / span) * Q_k
227
228
 
228
229
  # Compute secondary branch from the principal branch
229
- if use_wynn_epsilon:
230
+ if continuation != 'pade':
230
231
 
231
232
  # Compute analytic extension of rho(z) to lower-half plane for
232
233
  # when rho is just the k-th Jacobi basis: w(z) P_k(z). FOr this,
@@ -250,11 +251,22 @@ def jacobi_stieltjes(z, psi, support, alpha=0.0, beta=0.0, n_base=40,
250
251
  # Accumulate with factor 2/span
251
252
  m_total += psi_k * m_k
252
253
 
253
- if use_wynn_epsilon:
254
+ if continuation != 'pade':
254
255
  m_partial[k, :] = m_total.ravel()
255
256
 
256
- if use_wynn_epsilon:
257
- S = wynn_epsilon(m_partial)
257
+ if continuation != 'pade':
258
+
259
+ if continuation == 'wynn-eps':
260
+ S = wynn_epsilon(m_partial)
261
+ elif continuation == 'wynn-rho':
262
+ S = wynn_rho(m_partial)
263
+ elif continuation == 'levin':
264
+ S = levin_u(m_partial)
265
+ elif continuation == 'weniger':
266
+ S = weniger_delta(m_partial)
267
+ elif continuation == 'brezinski':
268
+ S = brezinski_theta(m_partial)
269
+
258
270
  m_total = S.reshape(z.shape)
259
271
 
260
272
  return m_total
@@ -151,7 +151,7 @@ def eigvalsh(A, size=None, psd=None, seed=None, plot=False, **kwargs):
151
151
  # Perform fit and estimate eigenvalues
152
152
  order = 1 + int(len(samples)**0.2)
153
153
  ff.fit(method='chebyshev', K=order, projection='sample',
154
- continuation='wynn', force=True, plot=False, latex=False,
154
+ continuation='wynn-eps', force=True, plot=False, latex=False,
155
155
  save=False)
156
156
 
157
157
  if plot:
@@ -0,0 +1,454 @@
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
+
15
+ __all__ = ['partial_sum', 'wynn_epsilon', 'wynn_rho', 'levin_u',
16
+ 'weniger_delta', 'brezinski_theta']
17
+
18
+
19
+ # ===========
20
+ # partial sum
21
+ # ===========
22
+
23
+ def partial_sum(coeffs, x, p=0.0):
24
+ """
25
+ Compute partial sum:
26
+
27
+ .. math::
28
+
29
+ S_n(x) = \\sum_{n=0}^{N-1} coeffs[n] * x^{n+p}.
30
+
31
+ Parameters
32
+ ----------
33
+
34
+ coeffs : array_like
35
+ Coefficients [a_0, a_1, a_2, ..., a_{N-1}] of the power series of the
36
+ size N.
37
+
38
+ x : numpy.array
39
+ A flattened array of the size d.
40
+
41
+ d : float, default=0.0
42
+ Offset power.
43
+
44
+ Returns
45
+ -------
46
+
47
+ Sn : numpy.ndarray
48
+ Partial sums of the size (N, d), where the n-th row is the n-th
49
+ partial sum.
50
+ """
51
+
52
+ x_ = x.ravel()
53
+ N = len(coeffs)
54
+ d = x_.size
55
+
56
+ # Forming partial sum via Horner method
57
+ Sn = numpy.zeros((N, d), dtype=x.dtype)
58
+ sum_ = numpy.zeros((d,), dtype=x.dtype)
59
+ pow_x = numpy.ones((d,), dtype=x.dtype)
60
+
61
+ if p == 1:
62
+ pow_x *= x_
63
+ elif p != 0:
64
+ pow_x *= x_**p
65
+
66
+ for n in range(N):
67
+ sum_ += coeffs[n] * pow_x
68
+ Sn[n, :] = sum_
69
+
70
+ if n < N-1:
71
+ pow_x *= x_
72
+
73
+ return Sn
74
+
75
+
76
+ # ============
77
+ # wynn epsilon
78
+ # ============
79
+
80
+ def wynn_epsilon(Sn):
81
+ """
82
+ Accelerate conversion of a series using Wynn's epsilon algorithm.
83
+
84
+ Parameters
85
+ ----------
86
+
87
+ Sn : numpy.ndarray
88
+ A 2D array of the size (N, d), where N is the number of partial sums
89
+ and d is the vector size.
90
+
91
+ Returns
92
+ -------
93
+
94
+ S : numpy.array
95
+ A 1D array of the size (d,) which is the accelerated value of the
96
+ series at each vector element.
97
+
98
+ Notes
99
+ -----
100
+
101
+ Given a series of vectors:
102
+
103
+ .. math::
104
+
105
+ (S_n)_{n=1}^N = (S1, \\dots, S_n)
106
+
107
+ this function finds the limit S = \\lim_{n \\to infty} S_n.
108
+
109
+ Each :math:`S_i \\in \\mathbb{C}^d` is a vector. However, instead of using
110
+ the vector version of the Wynn's epsilon algorithm, we use the scalar
111
+ version on each component of the vector. The reason for this is that in our
112
+ dataset, each component has its own convergence rate. The convergence rate
113
+ of vector version of the algorithm is bounded by the worse point, and this
114
+ potentially stall convergence for all points. As such, vector version is
115
+ avoided.
116
+
117
+ In our dataset, the series is indeed divergent. The Wynn's accelerated
118
+ method computes the principal value of the convergence series.
119
+ """
120
+
121
+ # N: number of partial sums, d: vector size
122
+ N, d = Sn.shape
123
+
124
+ # Epsilons of stage k-1 and k-2
125
+ eps_prev = Sn.copy() # row k-1
126
+ eps_pprev = None # row k-2
127
+
128
+ tol = numpy.finfo(Sn.dtype).eps
129
+
130
+ # Wynn's epsilon triangle table
131
+ for k in range(1, N):
132
+ Nk = N - k
133
+
134
+ delta = eps_prev[1:Nk+1, :] - eps_prev[:Nk, :]
135
+ small = numpy.abs(delta) <= \
136
+ tol * numpy.maximum(1.0, numpy.abs(eps_prev[1:Nk+1, :]))
137
+
138
+ # Reciprocal of delta
139
+ rec_delta = numpy.empty_like(delta)
140
+ rec_delta[small] = 0.0j
141
+ rec_delta[~small] = 1.0 / delta[~small]
142
+
143
+ # Current epsilon of row k
144
+ eps_curr = rec_delta
145
+ if k > 1:
146
+ eps_curr += eps_pprev[1:Nk+1, :]
147
+
148
+ # Roll rows
149
+ eps_pprev = eps_prev
150
+ eps_prev = eps_curr
151
+
152
+ # Last even row
153
+ if (N - 1) % 2 == 0:
154
+ # N is odd, so use step k-1
155
+ S = eps_prev[0, :]
156
+ else:
157
+ # N is even, so use k-2
158
+ S = eps_pprev[0, :]
159
+
160
+ return S
161
+
162
+
163
+ # ========
164
+ # wynn rho
165
+ # ========
166
+
167
+ def wynn_rho(Sn, beta=0.0):
168
+ """
169
+ Accelerate convergence of a series using Wynn's rho algorithm.
170
+
171
+ Parameters
172
+ ----------
173
+
174
+ Sn : numpy.ndarray
175
+ A 2D array of shape ``(N, d)``, where *N* is the number of partial
176
+ sums and *d* is the vector size.
177
+
178
+ beta : float, default=0.0
179
+ Shift parameter in the rho recursion, usually chosen in the range
180
+ ``0 < beta <= 1``.
181
+
182
+ Returns
183
+ -------
184
+
185
+ S : numpy.ndarray
186
+ A 1D array of shape ``(d,)`` giving the rho‑accelerated estimate
187
+ of the series limit for each component.
188
+
189
+ Notes
190
+ -----
191
+
192
+ Let ``S_n`` be the *n*‑th partial sum of the (possibly divergent)
193
+ sequence. Wynn's rho algorithm builds a triangular table
194
+ ``rho[k, n]`` (row *k*, column *n*) as follows:
195
+
196
+ rho[-1, n] = 0
197
+ rho[ 0, n] = S_n
198
+
199
+ rho[k, n] = rho[k-2, n+1] +
200
+ (n + beta + k - 1) / (rho[k-1, n+1] - rho[k-1, n])
201
+
202
+ Only even rows (k even) provide improved approximants. As with
203
+ ``wynn_epsilon``, we apply the scalar recursion component‑wise so that a
204
+ slowly converging component does not stall the others.
205
+ """
206
+
207
+ # N: number of partial sums, d: vector size
208
+ N, d = Sn.shape
209
+
210
+ # Rho of stage k-1 and k-2
211
+ rho_prev = Sn.copy() # row k-1
212
+ rho_pprev = None # row k-2
213
+
214
+ tol = numpy.finfo(Sn.dtype).eps
215
+
216
+ # Wynn's rho triangule table
217
+ for k in range(1, N):
218
+ Nk = N - k
219
+
220
+ delta = rho_prev[1:Nk+1, :] - rho_prev[:Nk, :]
221
+ small = numpy.abs(delta) <= \
222
+ tol * numpy.maximum(1.0, numpy.abs(rho_prev[1:Nk+1, :]))
223
+
224
+ coef = (beta + (k - 1) + numpy.arange(Nk))[:, None] # (Nk, 1)
225
+ coef = numpy.repeat(coef, d, axis=1) # (Nk, d)
226
+
227
+ # Current rho of row k
228
+ rho_curr = numpy.empty_like(delta)
229
+ rho_curr[small] = 0.0j # treat near-zero denominator
230
+
231
+ if k == 1:
232
+ rho_curr[~small] = coef[~small] / delta[~small]
233
+ else:
234
+ rho_curr[~small] = rho_pprev[1:Nk+1][~small] + \
235
+ coef[~small] / delta[~small]
236
+
237
+ # Roll rows
238
+ rho_pprev = rho_prev
239
+ rho_prev = rho_curr
240
+
241
+ # Last even row
242
+ if (N - 1) % 2 == 0:
243
+ # N is odd, so use step k-1
244
+ S = rho_prev[0, :]
245
+ else:
246
+ # N is even, so use k-2
247
+ S = rho_pprev[0, :]
248
+
249
+ return S
250
+
251
+
252
+ # ========
253
+ # levin u
254
+ # ========
255
+
256
+ def levin_u(Sn, omega=None, beta=0.0):
257
+ """
258
+ Levin u‑transform (vector form).
259
+
260
+ Parameters
261
+ ----------
262
+ Sn : ndarray, shape (N, d)
263
+ First N partial sums of a vector series.
264
+ omega : None or ndarray, shape (N-1, d), optional
265
+ Remainder estimate. If None, uses omega_n = S_{n+1} - S_n.
266
+ beta : float, optional
267
+ Levin shift parameter (default 0.0).
268
+
269
+ Returns
270
+ -------
271
+ S : ndarray, shape (d,)
272
+ Accelerated sum / antilimit.
273
+ """
274
+
275
+ Sn = numpy.asarray(Sn)
276
+ N, d = Sn.shape
277
+ if N < 3:
278
+ raise ValueError("Need at least 3 partial sums for Levin u.")
279
+
280
+ # default omega_n = forward difference
281
+ if omega is None:
282
+ omega = Sn[1:, :] - Sn[:-1, :]
283
+ else:
284
+ omega = numpy.asarray(omega)
285
+ if omega.shape != (N - 1, d):
286
+ raise ValueError("omega must have shape (N-1, d).")
287
+
288
+ tol = numpy.finfo(Sn.dtype).eps
289
+ m = N - 2 # highest possible order
290
+
291
+ # binomial coefficients with alternating sign
292
+ Cmk = numpy.empty(m + 1, dtype=Sn.dtype)
293
+ Cmk[0] = 1.0
294
+ for k in range(1, m + 1):
295
+ Cmk[k] = Cmk[k - 1] * (m - k + 1) / k
296
+ Cmk *= (-1.0) ** numpy.arange(m + 1)
297
+
298
+ # powers (k + beta)^(m-1)
299
+ if m == 1:
300
+ Pk = numpy.ones(m + 1, dtype=Sn.dtype)
301
+ else:
302
+ Pk = (numpy.arange(m + 1, dtype=Sn.dtype) + beta) ** (m - 1)
303
+
304
+ numer = numpy.zeros(d, dtype=Sn.dtype)
305
+ denom = numpy.zeros(d, dtype=Sn.dtype)
306
+
307
+ for k in range(m + 1):
308
+ idx = k
309
+ w = omega[idx, :]
310
+
311
+ inv_w = numpy.empty_like(w)
312
+ mask = numpy.abs(w) < tol
313
+ inv_w[mask] = 0.0
314
+ inv_w[~mask] = 1.0 / w[~mask]
315
+
316
+ coeff = Cmk[k] * Pk[k]
317
+ numer += coeff * Sn[idx, :] * inv_w
318
+ denom += coeff * inv_w
319
+
320
+ return numer / denom
321
+
322
+
323
+ # =============
324
+ # weniger delta
325
+ # =============
326
+
327
+ def weniger_delta(Sn):
328
+ """
329
+ Weniger's nonlinear delta^2 sequence transformation.
330
+
331
+ Parameters
332
+ ----------
333
+
334
+ Sn : numpy.ndarray
335
+ Array of shape (N, d) containing the first N partial sums of the
336
+ series.
337
+
338
+ Returns
339
+ -------
340
+
341
+ S : numpy.ndarray
342
+ Array of shape (d,) giving the Δ²‑accelerated limit estimate for each
343
+ component.
344
+ """
345
+
346
+ N, d = Sn.shape
347
+
348
+ # Need at least three partial sums to form Δ²
349
+ if N < 3:
350
+ return Sn[-1, :].copy()
351
+
352
+ # First and second forward differences
353
+ delta1 = Sn[1:] - Sn[:-1] # shape (N-1, d)
354
+ delta2 = delta1[1:] - delta1[:-1] # shape (N-2, d)
355
+
356
+ tol = numpy.finfo(Sn.real.dtype).eps
357
+
358
+ # Safe reciprocal of delta2
359
+ small = numpy.abs(delta2) <= tol * numpy.maximum(
360
+ 1.0, numpy.abs(delta1[:-1]))
361
+
362
+ rec_delta2 = numpy.empty_like(delta2)
363
+ rec_delta2[small] = 0.0j
364
+ rec_delta2[~small] = 1.0 / delta2[~small]
365
+
366
+ # Delta sequence, length N-2
367
+ delta_sq = Sn[:-2] - (delta1[:-1] ** 2) * rec_delta2
368
+
369
+ # Return the last Delta2 term as the accelerated estimate
370
+ S = delta_sq[-1, :]
371
+
372
+ return S
373
+
374
+
375
+ # ===============
376
+ # brezinski theta
377
+ # ===============
378
+
379
+ def brezinski_theta(Sn):
380
+ """
381
+ Accelerate convergence of a series using Brezinski's theta algorithm.
382
+
383
+ Parameters
384
+ ----------
385
+
386
+ Sn : numpy.ndarray
387
+ A 2‑D array of the size ``(N, d)``, where `N` is the number of partial
388
+ sums and `d` is the vector size.
389
+
390
+ Returns
391
+ -------
392
+
393
+ S : numpy.ndarray
394
+ A 1‑D array of the size ``(d,)`` – the theta‑accelerated estimate of
395
+ the series limit in each vector component.
396
+ """
397
+
398
+ N, d = Sn.shape
399
+
400
+ theta_prev = Sn.copy() # row k-1
401
+ theta_pprev = numpy.zeros_like(theta_prev) # row k-2
402
+
403
+ tol = numpy.finfo(Sn.dtype).eps
404
+
405
+ for k in range(1, N):
406
+ L_prev = theta_prev.shape[0] # current row length
407
+
408
+ if k % 2 == 1:
409
+
410
+ # Odd row 2m+1
411
+ if L_prev < 2:
412
+ break
413
+
414
+ delta = theta_prev[1:] - theta_prev[:-1] # len = L
415
+ theta_pp = theta_pprev[1:L_prev] # len = L
416
+
417
+ small = numpy.abs(delta) <= \
418
+ tol * numpy.maximum(1.0, numpy.abs(theta_prev[1:]))
419
+
420
+ theta_curr = numpy.empty_like(delta)
421
+ theta_curr[small] = 0.0j
422
+ theta_curr[~small] = theta_pp[~small] + 1.0 / delta[~small]
423
+
424
+ else:
425
+
426
+ # Even row 2m+2
427
+ if L_prev < 3:
428
+ break
429
+
430
+ delta_even = theta_pprev[2:L_prev] - theta_pprev[1:L_prev-1]
431
+ delta_odd = theta_prev[1:L_prev-1] - theta_prev[:L_prev-2]
432
+ delta2_odd = (theta_prev[2:L_prev] - 2.0 * theta_prev[1:L_prev-1]
433
+ + theta_prev[:L_prev-2])
434
+
435
+ small = numpy.abs(delta2_odd) <= tol * numpy.maximum(
436
+ 1.0, numpy.abs(theta_prev[1:L_prev-1]))
437
+
438
+ theta_curr = numpy.empty_like(delta_odd)
439
+ theta_curr[small] = theta_pprev[1:L_prev-1][small]
440
+ theta_curr[~small] = (
441
+ theta_pprev[1:L_prev-1][~small] +
442
+ (delta_even[~small] * delta_odd[~small]) /
443
+ delta2_odd[~small])
444
+
445
+ # roll rows
446
+ theta_pprev = theta_prev
447
+ theta_prev = theta_curr
448
+
449
+ if (N - 1) % 2 == 0:
450
+ S = theta_prev[0]
451
+ else:
452
+ S = theta_pprev[0]
453
+
454
+ return S
@@ -234,13 +234,18 @@ class FreeForm(object):
234
234
  If `True`, it forces the density to have unit mass and to be
235
235
  strictly positive.
236
236
 
237
- continuation : {``'pade'``, ``'wynn'``}, default= ``'pade'``
237
+ continuation : {``'pade'``, ``'wynn-eps'``, ``'wynn-rho'``,
238
+ ``'levin'``, ``'weniger'``, ``'brezinski'``}, default= ``'pade'``
238
239
  Method of analytic continuation to construct the second branch of
239
240
  Steltjes transform in the lower-half complex plane:
240
241
 
241
242
  * ``'pade'``: using Riemann-Hilbert problem with Pade
242
243
  approximation.
243
- * ``'wynn'``: Wynn's epsilon algorithm.
244
+ * ``'wynn-eps'``: Wynn's :math:`\\epsilon` algorithm.
245
+ * ``'wynn-rho'``: Wynn's :math:`\\rho` algorithm.
246
+ * ``'levin'``: Levin's :math:`u` transform.
247
+ * ``'weniger'``: Weniger's :math:`\\delta^2` algorithm.
248
+ * ``'brezinski'``: Brezinski's :math:`\\theta` algorithm.
244
249
 
245
250
  pade_p : int, default=0
246
251
  Degree of polynomial :math:`P(z)` is :math:`q+p` where :math:`p`
@@ -419,7 +424,8 @@ class FreeForm(object):
419
424
  self.beta = beta
420
425
 
421
426
  # Analytic continuation
422
- if continuation not in ['pade', 'wynn']:
427
+ if continuation not in ['pade', 'wynn-eps', 'wynn-rho', 'levin',
428
+ 'weniger', 'brezinski']:
423
429
  raise NotImplementedError('"continuation" method is invalid.')
424
430
 
425
431
  self.continuation = continuation
@@ -815,23 +821,17 @@ class FreeForm(object):
815
821
  # z.real <= self.lam_p)
816
822
  # n_base = 2 * numpy.sum(mask_sup)
817
823
 
818
- if self.continuation == 'wynn':
819
- use_wynn_epsilon = True
820
- else:
821
- use_wynn_epsilon = False
822
-
823
824
  # Stieltjes function
824
825
  if self.method == 'jacobi':
825
826
  stieltjes = partial(jacobi_stieltjes, psi=self.psi,
826
827
  support=self.support, alpha=self.alpha,
827
- beta=self.beta,
828
- use_wynn_epsilon=use_wynn_epsilon)
828
+ beta=self.beta, continuation=self.continuation)
829
829
  # n_base = n_base
830
830
 
831
831
  elif self.method == 'chebyshev':
832
832
  stieltjes = partial(chebyshev_stieltjes, psi=self.psi,
833
833
  support=self.support,
834
- use_wynn_epsilon=use_wynn_epsilon)
834
+ continuation=self.continuation)
835
835
 
836
836
  mask_p = z.imag >= 0.0
837
837
  mask_m = z.imag < 0.0
@@ -852,7 +852,8 @@ class FreeForm(object):
852
852
  m2[mask_m] = -m1[mask_m] + self._glue(
853
853
  z[mask_m].reshape(-1, 1)).ravel()
854
854
 
855
- elif self.continuation == 'wynn':
855
+ elif self.continuation in ['wynn-eps', 'wynn-rho', 'levin', 'weniger',
856
+ 'brezinski']:
856
857
  m2[:] = stieltjes(z.reshape(-1, 1)).reshape(*m2.shape)
857
858
  if branches:
858
859
  m1[mask_p] = m2[mask_p]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: freealg
3
- Version: 0.5.2
3
+ Version: 0.5.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
@@ -1 +0,0 @@
1
- __version__ = "0.5.2"
@@ -1,145 +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
-
15
- __all__ = ['partial_sum', 'wynn_epsilon']
16
-
17
-
18
- # ===========
19
- # partial sum
20
- # ===========
21
-
22
- def partial_sum(coeffs, x):
23
- """
24
- Compute partial sum:
25
-
26
- .. math::
27
-
28
- S_n(x) = \\sum_{n=0}^{N} coeffs[n] * x^n.
29
-
30
- Parameters
31
- ----------
32
-
33
- coeffs : array_like
34
- Coefficients [a0, a1, a2, ...] of the power series of the size N.
35
-
36
- x : numpy.array
37
- A flattened array of the size d.
38
-
39
- Returns
40
- -------
41
-
42
- Sn : numpy.ndarray
43
- Partial sums of the size (N, d), where the n-th row is the n-th
44
- partial sum.
45
- """
46
-
47
- xn = x.ravel()
48
- N = len(coeffs)
49
- d = xn.size
50
-
51
- # Forming partial sum via Horner method
52
- Sn = numpy.zeros((N, d), dtype=x.dtype)
53
- sum_ = numpy.zeros((d,), dtype=x.dtype)
54
- pow_x = numpy.ones((d,), dtype=x.dtype)
55
-
56
- for n in range(N):
57
- sum_ += coeffs[n] * pow_x
58
- Sn[n, :] = sum_
59
-
60
- if n < N-1:
61
- pow_x *= xn
62
-
63
- return Sn
64
-
65
-
66
- # ============
67
- # wynn epsilon
68
- # ============
69
-
70
- def wynn_epsilon(Sn):
71
- """
72
- Accelerate conversion of a series using Wynn's epsilon algorithm.
73
-
74
- Parameters
75
- ----------
76
-
77
- Sn : numpy.ndarray
78
- A 2D array of the size (N, d), where N is the number of partial sums
79
- and d is the vector size.
80
-
81
- Returns
82
- -------
83
-
84
- S : numpy.array
85
- A 1D array of the size (d,) which is the accelerated value of the
86
- series at each vector element.
87
-
88
- Notes
89
- -----
90
-
91
- Given a series of vectors:
92
-
93
- .. math::
94
-
95
- (S_n)_{n=1}^N = (S1, \\dots, S_n)
96
-
97
- this function finds the limit S = \\lim_{n \\to infty} S_n.
98
-
99
- Each :math:`S_i \\in \\mathbb{C}^d` is a vector. However, instead of using
100
- the vector version of the Wynn's epsilon algorithm, we use the scalar
101
- version on each component of the vector. The reason for this is that in our
102
- dataset, each component has its own convergence rate. The convergence rate
103
- of vector version of the algorithm is bounded by the worse point, and this
104
- potentially stall convergence for all points. As such, vector version is
105
- avoided.
106
-
107
- In our dataset, the series is indeed divergent. The Wynn's accelerated
108
- method computes the principal value of the convergence series.
109
- """
110
-
111
- # N: number of partial sums, d: vector size
112
- N, d = Sn.shape
113
-
114
- eps = numpy.zeros((N, N, d), dtype=Sn.dtype)
115
- eps[0, :, :] = Sn
116
-
117
- tol = numpy.finfo(float).eps
118
-
119
- # Wynn's triangle table
120
- for k in range(1, N):
121
- Nk = N - k
122
-
123
- delta = eps[k-1, 1:N-k+1, :] - eps[k-1, :Nk, :]
124
-
125
- # Reciprocal of delta
126
- rec_delta = numpy.empty_like(delta)
127
-
128
- # Avoid division by zero error
129
- mask_inf = numpy.abs(delta) < tol
130
- rec_delta[mask_inf] = numpy.inf
131
- rec_delta[~mask_inf] = 1.0 / delta[~mask_inf]
132
-
133
- mask_zero = numpy.logical_or(numpy.isinf(delta),
134
- numpy.isnan(delta))
135
- rec_delta[mask_zero] = 0.0
136
-
137
- eps[k, :Nk, :] = rec_delta
138
-
139
- if k > 1:
140
- eps[k, :Nk, :] += eps[k-2, 1:Nk+1, :]
141
-
142
- k_even = 2 * ((N - 1) // 2)
143
- S = eps[k_even, 0, :]
144
-
145
- return S
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