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.
- {freealg-0.5.2 → freealg-0.5.4}/PKG-INFO +1 -1
- freealg-0.5.4/freealg/__version__.py +1 -0
- {freealg-0.5.2 → freealg-0.5.4}/freealg/_chebyshev.py +26 -12
- {freealg-0.5.2 → freealg-0.5.4}/freealg/_jacobi.py +21 -9
- {freealg-0.5.2 → freealg-0.5.4}/freealg/_linalg.py +1 -1
- freealg-0.5.4/freealg/_series.py +454 -0
- {freealg-0.5.2 → freealg-0.5.4}/freealg/freeform.py +13 -12
- {freealg-0.5.2 → freealg-0.5.4}/freealg.egg-info/PKG-INFO +1 -1
- freealg-0.5.2/freealg/__version__.py +0 -1
- freealg-0.5.2/freealg/_series.py +0 -145
- {freealg-0.5.2 → freealg-0.5.4}/AUTHORS.txt +0 -0
- {freealg-0.5.2 → freealg-0.5.4}/CHANGELOG.rst +0 -0
- {freealg-0.5.2 → freealg-0.5.4}/LICENSE.txt +0 -0
- {freealg-0.5.2 → freealg-0.5.4}/MANIFEST.in +0 -0
- {freealg-0.5.2 → freealg-0.5.4}/README.rst +0 -0
- {freealg-0.5.2 → freealg-0.5.4}/freealg/__init__.py +0 -0
- {freealg-0.5.2 → freealg-0.5.4}/freealg/_damp.py +0 -0
- {freealg-0.5.2 → freealg-0.5.4}/freealg/_decompress.py +0 -0
- {freealg-0.5.2 → freealg-0.5.4}/freealg/_pade.py +0 -0
- {freealg-0.5.2 → freealg-0.5.4}/freealg/_plot_util.py +0 -0
- {freealg-0.5.2 → freealg-0.5.4}/freealg/_sample.py +0 -0
- {freealg-0.5.2 → freealg-0.5.4}/freealg/_support.py +0 -0
- {freealg-0.5.2 → freealg-0.5.4}/freealg/_util.py +0 -0
- {freealg-0.5.2 → freealg-0.5.4}/freealg/distributions/__init__.py +0 -0
- {freealg-0.5.2 → freealg-0.5.4}/freealg/distributions/_kesten_mckay.py +0 -0
- {freealg-0.5.2 → freealg-0.5.4}/freealg/distributions/_marchenko_pastur.py +0 -0
- {freealg-0.5.2 → freealg-0.5.4}/freealg/distributions/_meixner.py +0 -0
- {freealg-0.5.2 → freealg-0.5.4}/freealg/distributions/_wachter.py +0 -0
- {freealg-0.5.2 → freealg-0.5.4}/freealg/distributions/_wigner.py +0 -0
- {freealg-0.5.2 → freealg-0.5.4}/freealg.egg-info/SOURCES.txt +0 -0
- {freealg-0.5.2 → freealg-0.5.4}/freealg.egg-info/dependency_links.txt +0 -0
- {freealg-0.5.2 → freealg-0.5.4}/freealg.egg-info/not-zip-safe +0 -0
- {freealg-0.5.2 → freealg-0.5.4}/freealg.egg-info/requires.txt +0 -0
- {freealg-0.5.2 → freealg-0.5.4}/freealg.egg-info/top_level.txt +0 -0
- {freealg-0.5.2 → freealg-0.5.4}/pyproject.toml +0 -0
- {freealg-0.5.2 → freealg-0.5.4}/requirements.txt +0 -0
- {freealg-0.5.2 → freealg-0.5.4}/setup.cfg +0 -0
- {freealg-0.5.2 → freealg-0.5.4}/setup.py +0 -0
|
@@ -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,
|
|
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
|
-
|
|
201
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
184
|
-
|
|
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
|
|
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
|
|
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
|
|
254
|
+
if continuation != 'pade':
|
|
254
255
|
m_partial[k, :] = m_total.ravel()
|
|
255
256
|
|
|
256
|
-
if
|
|
257
|
-
|
|
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'
|
|
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
|
-
|
|
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
|
|
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 +0,0 @@
|
|
|
1
|
-
__version__ = "0.5.2"
|
freealg-0.5.2/freealg/_series.py
DELETED
|
@@ -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
|
|
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
|