freealg 0.6.0__tar.gz → 0.6.2__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.6.0 → freealg-0.6.2}/PKG-INFO +1 -1
- freealg-0.6.2/freealg/__version__.py +1 -0
- {freealg-0.6.0 → freealg-0.6.2}/freealg/_chebyshev.py +8 -8
- {freealg-0.6.0 → freealg-0.6.2}/freealg/_jacobi.py +123 -51
- {freealg-0.6.0 → freealg-0.6.2}/freealg/_pade.py +33 -151
- {freealg-0.6.0 → freealg-0.6.2}/freealg/_sample.py +7 -2
- {freealg-0.6.0 → freealg-0.6.2}/freealg/_util.py +45 -13
- {freealg-0.6.0 → freealg-0.6.2}/freealg/distributions/_kesten_mckay.py +8 -2
- {freealg-0.6.0 → freealg-0.6.2}/freealg/distributions/_marchenko_pastur.py +8 -2
- {freealg-0.6.0 → freealg-0.6.2}/freealg/distributions/_meixner.py +28 -26
- {freealg-0.6.0 → freealg-0.6.2}/freealg/distributions/_wachter.py +8 -2
- {freealg-0.6.0 → freealg-0.6.2}/freealg/distributions/_wigner.py +8 -2
- {freealg-0.6.0 → freealg-0.6.2}/freealg/freeform.py +56 -28
- {freealg-0.6.0 → freealg-0.6.2}/freealg.egg-info/PKG-INFO +1 -1
- freealg-0.6.0/freealg/__version__.py +0 -1
- {freealg-0.6.0 → freealg-0.6.2}/AUTHORS.txt +0 -0
- {freealg-0.6.0 → freealg-0.6.2}/CHANGELOG.rst +0 -0
- {freealg-0.6.0 → freealg-0.6.2}/LICENSE.txt +0 -0
- {freealg-0.6.0 → freealg-0.6.2}/MANIFEST.in +0 -0
- {freealg-0.6.0 → freealg-0.6.2}/README.rst +0 -0
- {freealg-0.6.0 → freealg-0.6.2}/freealg/__init__.py +0 -0
- {freealg-0.6.0 → freealg-0.6.2}/freealg/_damp.py +0 -0
- {freealg-0.6.0 → freealg-0.6.2}/freealg/_decompress.py +0 -0
- {freealg-0.6.0 → freealg-0.6.2}/freealg/_linalg.py +0 -0
- {freealg-0.6.0 → freealg-0.6.2}/freealg/_plot_util.py +0 -0
- {freealg-0.6.0 → freealg-0.6.2}/freealg/_series.py +0 -0
- {freealg-0.6.0 → freealg-0.6.2}/freealg/_support.py +0 -0
- {freealg-0.6.0 → freealg-0.6.2}/freealg/distributions/__init__.py +0 -0
- {freealg-0.6.0 → freealg-0.6.2}/freealg.egg-info/SOURCES.txt +0 -0
- {freealg-0.6.0 → freealg-0.6.2}/freealg.egg-info/dependency_links.txt +0 -0
- {freealg-0.6.0 → freealg-0.6.2}/freealg.egg-info/not-zip-safe +0 -0
- {freealg-0.6.0 → freealg-0.6.2}/freealg.egg-info/requires.txt +0 -0
- {freealg-0.6.0 → freealg-0.6.2}/freealg.egg-info/top_level.txt +0 -0
- {freealg-0.6.0 → freealg-0.6.2}/pyproject.toml +0 -0
- {freealg-0.6.0 → freealg-0.6.2}/requirements.txt +0 -0
- {freealg-0.6.0 → freealg-0.6.2}/setup.cfg +0 -0
- {freealg-0.6.0 → freealg-0.6.2}/setup.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.6.2"
|
|
@@ -203,7 +203,7 @@ def chebyshev_stieltjes(z, psi, support, continuation='pade',
|
|
|
203
203
|
Methof of analytiv continuation.
|
|
204
204
|
|
|
205
205
|
dtype : numpy.type, default=numpy.complex128
|
|
206
|
-
Data type for
|
|
206
|
+
Data type for complex arrays. This might enhance series acceleration.
|
|
207
207
|
|
|
208
208
|
Returns
|
|
209
209
|
-------
|
|
@@ -218,7 +218,7 @@ def chebyshev_stieltjes(z, psi, support, continuation='pade',
|
|
|
218
218
|
span = lam_p - lam_m
|
|
219
219
|
center = 0.5 * (lam_m + lam_p)
|
|
220
220
|
|
|
221
|
-
# Map z
|
|
221
|
+
# Map z to u in the standard [-1,1] domain
|
|
222
222
|
u = (2.0 * (z - center)) / span
|
|
223
223
|
|
|
224
224
|
# Inverse-Joukowski: pick branch sqrt with +Im
|
|
@@ -232,15 +232,15 @@ def chebyshev_stieltjes(z, psi, support, continuation='pade',
|
|
|
232
232
|
|
|
233
233
|
# This depends on the method of analytic continuation
|
|
234
234
|
if continuation == 'pade':
|
|
235
|
-
#
|
|
235
|
+
# Horner summation for S0(J) = sum_{k=0}^K psi_k * J**k
|
|
236
236
|
K = len(psi) - 1
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
S =
|
|
237
|
+
S0 = numpy.zeros_like(J)
|
|
238
|
+
for k in range(K, -1, -1):
|
|
239
|
+
S0 = psi[k] + J * S0
|
|
240
|
+
S = J * S0
|
|
241
241
|
|
|
242
242
|
else:
|
|
243
|
-
# Flatten J before passing to
|
|
243
|
+
# Flatten J before passing to any of the acceleration methods.
|
|
244
244
|
psi_zero = numpy.concatenate([[0.0], psi])
|
|
245
245
|
Sn = partial_sum(psi_zero, J.ravel(), p=0)
|
|
246
246
|
|
|
@@ -97,7 +97,7 @@ def jacobi_kernel_proj(xs, pdf, support, K=10, alpha=0.0, beta=0.0, reg=0.0):
|
|
|
97
97
|
Pk = eval_jacobi(k, alpha, beta, t)
|
|
98
98
|
N_k = jacobi_sq_norm(k, alpha, beta)
|
|
99
99
|
|
|
100
|
-
#
|
|
100
|
+
# \int P_k(t) w(t) \rho(t) dt. w(t) cancels with pdf already being rho
|
|
101
101
|
moment = numpy.trapz(Pk * pdf, xs)
|
|
102
102
|
|
|
103
103
|
if k == 0:
|
|
@@ -168,36 +168,41 @@ def jacobi_density(x, psi, support, alpha=0.0, beta=0.0):
|
|
|
168
168
|
# jacobi stieltjes
|
|
169
169
|
# ================
|
|
170
170
|
|
|
171
|
-
def jacobi_stieltjes(z, psi, support, alpha=0.0, beta=0.0,
|
|
171
|
+
def jacobi_stieltjes(z, cache, psi, support, alpha=0.0, beta=0.0, n_quad=None,
|
|
172
172
|
continuation='pade', dtype=numpy.complex128):
|
|
173
173
|
"""
|
|
174
174
|
Compute m(z) = sum_k psi_k * m_k(z) where
|
|
175
175
|
|
|
176
|
-
|
|
176
|
+
.. math::
|
|
177
|
+
|
|
178
|
+
m_k(z) = \\int \\frac{w^{(alpha, beta)}(t) P_k^{(alpha, beta)}(t)}{
|
|
179
|
+
(u(z)-t)} \\mathrm{d} t
|
|
177
180
|
|
|
178
181
|
Each m_k is evaluated *separately* with a Gauss-Jacobi rule sized
|
|
179
|
-
for that k.
|
|
182
|
+
for that k. This follows the user's request: 1 quadrature rule per P_k.
|
|
180
183
|
|
|
181
184
|
Parameters
|
|
182
185
|
----------
|
|
183
186
|
|
|
184
187
|
z : complex or ndarray
|
|
185
188
|
|
|
189
|
+
cache : dict
|
|
190
|
+
Pass a dict to enable cross-call caching.
|
|
191
|
+
|
|
186
192
|
psi : (K+1,) array_like
|
|
187
193
|
|
|
188
194
|
support : (lambda_minus, lambda_plus)
|
|
189
195
|
|
|
190
196
|
alpha, beta : float
|
|
191
197
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
n_quad = max(n_base, k+1).
|
|
198
|
+
n_quad : int, default=None
|
|
199
|
+
Number of Gauss-Jacobi quadrature points.
|
|
195
200
|
|
|
196
201
|
continuation : str, default= ``'pade'``
|
|
197
|
-
|
|
202
|
+
Method of analytic continuation.
|
|
198
203
|
|
|
199
204
|
dtype : numpy.type, default=numpy.complex128
|
|
200
|
-
Data type for
|
|
205
|
+
Data type for complex arrays. This might enhance series acceleration.
|
|
201
206
|
|
|
202
207
|
Returns
|
|
203
208
|
-------
|
|
@@ -209,67 +214,132 @@ def jacobi_stieltjes(z, psi, support, alpha=0.0, beta=0.0, n_base=40,
|
|
|
209
214
|
Same shape as z
|
|
210
215
|
"""
|
|
211
216
|
|
|
217
|
+
if not isinstance(cache, dict):
|
|
218
|
+
raise TypeError('"cache" must be a dict; pass a persistent dict '
|
|
219
|
+
'(e.g., self.cache).')
|
|
220
|
+
|
|
221
|
+
# Number of quadratures
|
|
222
|
+
if 'n_quad' not in cache:
|
|
223
|
+
if n_quad is None:
|
|
224
|
+
# Set number of quadratures based on Bernstein ellipse. Here using
|
|
225
|
+
# an evaluation point a with distance delta from support, to
|
|
226
|
+
# achieve the quadrature error below tol.
|
|
227
|
+
tol = 1e-16
|
|
228
|
+
delta = 1e-2
|
|
229
|
+
n_quad = int(-numpy.log(tol) / (2.0 * numpy.sqrt(delta)))
|
|
230
|
+
n_quad = max(n_quad, psi.size)
|
|
231
|
+
cache['n_quad'] = n_quad
|
|
232
|
+
else:
|
|
233
|
+
n_quad = cache['n_quad']
|
|
234
|
+
|
|
235
|
+
# Quadrature nodes and weights
|
|
236
|
+
if ('t_nodes' not in cache) or ('w_nodes' not in cache):
|
|
237
|
+
t_nodes, w_nodes = roots_jacobi(n_quad, alpha, beta) # (n_quad,)
|
|
238
|
+
cache['t_nodes'] = t_nodes
|
|
239
|
+
cache['w_nodes'] = w_nodes
|
|
240
|
+
else:
|
|
241
|
+
t_nodes = cache['t_nodes']
|
|
242
|
+
w_nodes = cache['w_nodes']
|
|
243
|
+
|
|
212
244
|
z = numpy.asarray(z, dtype=dtype)
|
|
213
245
|
lam_minus, lam_plus = support
|
|
214
246
|
span = lam_plus - lam_minus
|
|
215
247
|
centre = 0.5 * (lam_plus + lam_minus)
|
|
216
248
|
|
|
217
|
-
# Map z
|
|
249
|
+
# Map z to u in the standard [-1,1] domain
|
|
218
250
|
u = (2.0 / span) * (z - centre)
|
|
219
251
|
|
|
220
|
-
|
|
252
|
+
# Cauchy Kernel (flattened for all z)
|
|
253
|
+
u_flat = u.ravel()
|
|
254
|
+
ker = (1.0 / (t_nodes[:, None] - u_flat[None, :])).astype(
|
|
255
|
+
dtype, copy=False) # (n_quad, Ny*Nx)
|
|
256
|
+
|
|
257
|
+
if continuation == 'pade':
|
|
258
|
+
|
|
259
|
+
if 'integrand_nodes' not in cache:
|
|
260
|
+
|
|
261
|
+
# Compute sum_k psi_k P_k (call it s_node)
|
|
262
|
+
s_nodes = numpy.zeros_like(t_nodes, dtype=dtype)
|
|
263
|
+
for k, psi_k in enumerate(psi):
|
|
264
|
+
|
|
265
|
+
# Evaluate P_k at the quadrature nodes
|
|
266
|
+
P_k_nodes = eval_jacobi(k, alpha, beta, t_nodes) # (n_quad,)
|
|
267
|
+
s_nodes += psi_k * P_k_nodes
|
|
268
|
+
|
|
269
|
+
integrand_nodes = (2.0 / span) * (w_nodes * s_nodes).astype(dtype)
|
|
270
|
+
cache['integrand_nodes'] = integrand_nodes
|
|
221
271
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
272
|
+
else:
|
|
273
|
+
integrand_nodes = cache['integrand_nodes']
|
|
274
|
+
|
|
275
|
+
Q_flat = (integrand_nodes[:, None] * ker).sum(axis=0)
|
|
276
|
+
m_total = Q_flat.reshape(z.shape)
|
|
277
|
+
|
|
278
|
+
return m_total
|
|
279
|
+
|
|
280
|
+
else:
|
|
225
281
|
|
|
226
|
-
|
|
227
|
-
#
|
|
228
|
-
n_quad = max(n_base, k + 1)
|
|
229
|
-
t_nodes, w_nodes = roots_jacobi(n_quad, alpha, beta) # (n_quad,)
|
|
282
|
+
# Continuation is not Pade. This is one of Wynn, Levin, etc. These
|
|
283
|
+
# methods need the series for m for 1, ..., k.
|
|
230
284
|
|
|
231
|
-
|
|
232
|
-
|
|
285
|
+
if 'B' not in cache:
|
|
286
|
+
# All P_k at quadrature nodes (real), row-scale by weights
|
|
287
|
+
P_nodes = numpy.empty((psi.size, n_quad), dtype=w_nodes.dtype)
|
|
288
|
+
for k in range(psi.size):
|
|
289
|
+
P_nodes[k, :] = eval_jacobi(k, alpha, beta, t_nodes)
|
|
233
290
|
|
|
234
|
-
|
|
235
|
-
|
|
291
|
+
# All P_k * w shape (K+1, n_quad)
|
|
292
|
+
B = (2.0 / span) * (P_nodes * w_nodes[None, :]).astype(
|
|
293
|
+
dtype, copy=False)
|
|
294
|
+
cache['B'] = B
|
|
236
295
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
296
|
+
else:
|
|
297
|
+
B = cache['B']
|
|
298
|
+
|
|
299
|
+
# Principal branch. 2D matrix for all k
|
|
300
|
+
m_k_all = B @ ker
|
|
240
301
|
|
|
241
|
-
#
|
|
242
|
-
m_k =
|
|
302
|
+
# Compute m on secondary branch from the principal branch, which is
|
|
303
|
+
# m_k = m_k + 2 \pi i rho_k(z), and rho(z) is the analytic extension of
|
|
304
|
+
# rho_k(x) using the k-th basis. Basically, rho_k(z) is w * P_k(z).
|
|
243
305
|
|
|
244
|
-
#
|
|
245
|
-
|
|
306
|
+
# Lower-half-plane jump for ALL k at once (vectorized)
|
|
307
|
+
mask_m = (z.imag <= 0)
|
|
308
|
+
if numpy.any(mask_m):
|
|
309
|
+
idx = numpy.flatnonzero(mask_m.ravel())
|
|
310
|
+
u_m = u_flat[idx].astype(dtype, copy=False) # complex
|
|
246
311
|
|
|
247
|
-
#
|
|
248
|
-
#
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
312
|
+
# Scipy's eval_jacobi tops out at complex128 type. If u_m is
|
|
313
|
+
# complex256, downcast to complex128.
|
|
314
|
+
if u_m.dtype.itemsize > numpy.dtype(numpy.complex128).itemsize:
|
|
315
|
+
u_m_eval = u_m.astype(numpy.complex128, copy=False)
|
|
316
|
+
down_cast = True
|
|
317
|
+
else:
|
|
318
|
+
u_m_eval = u_m
|
|
319
|
+
down_cast = False
|
|
253
320
|
|
|
254
|
-
#
|
|
255
|
-
|
|
256
|
-
|
|
321
|
+
# P_k at complex u_m (all means for all k = 1,...,K)
|
|
322
|
+
P_all_m = numpy.empty((psi.size, u_m.size), dtype=dtype)
|
|
323
|
+
for k in range(psi.size):
|
|
324
|
+
P_all_m[k, :] = eval_jacobi(k, alpha, beta, u_m_eval)
|
|
257
325
|
|
|
258
|
-
#
|
|
259
|
-
|
|
260
|
-
alpha=alpha, beta=beta).reshape(z_m.shape)
|
|
326
|
+
# Jacobi weight. Must match jacobi_density's branch
|
|
327
|
+
w_m = numpy.power(1.0 - u_m, alpha) * numpy.power(1.0 + u_m, beta)
|
|
261
328
|
|
|
262
|
-
#
|
|
263
|
-
|
|
264
|
-
|
|
329
|
+
# rho_k(z) in x-units is (2/span) * w(u) * P_k(u)
|
|
330
|
+
rho_all = ((2.0 / span) * w_m[None, :] * P_all_m).astype(
|
|
331
|
+
dtype, copy=False)
|
|
265
332
|
|
|
266
|
-
|
|
267
|
-
|
|
333
|
+
if down_cast:
|
|
334
|
+
rho_all = rho_all.astype(dtype)
|
|
268
335
|
|
|
269
|
-
|
|
270
|
-
|
|
336
|
+
# compute analytic extension of rho(z) to lower-half plane for when
|
|
337
|
+
# rho is just the k-th Jacobi basis: w(z) P_k(z). For this, we
|
|
338
|
+
m_k_all[:, idx] = m_k_all[:, idx] + (2.0 * numpy.pi * 1j) * rho_all
|
|
271
339
|
|
|
272
|
-
|
|
340
|
+
# Partial sums S_k = sum_{j<=k} psi_j * m_j
|
|
341
|
+
WQ = (psi[:, None].astype(dtype, copy=False) * m_k_all)
|
|
342
|
+
m_partial = numpy.cumsum(WQ, axis=0)
|
|
273
343
|
|
|
274
344
|
if continuation == 'wynn-eps':
|
|
275
345
|
S = wynn_epsilon(m_partial)
|
|
@@ -281,7 +351,9 @@ def jacobi_stieltjes(z, psi, support, alpha=0.0, beta=0.0, n_base=40,
|
|
|
281
351
|
S = weniger_delta(m_partial)
|
|
282
352
|
elif continuation == 'brezinski':
|
|
283
353
|
S = brezinski_theta(m_partial)
|
|
354
|
+
else:
|
|
355
|
+
# No acceleration (likely diverges in the lower-half plane)
|
|
356
|
+
S = m_partial[-1, :]
|
|
284
357
|
|
|
285
358
|
m_total = S.reshape(z.shape)
|
|
286
|
-
|
|
287
|
-
return m_total
|
|
359
|
+
return m_total
|
|
@@ -108,32 +108,35 @@ def _decode_poles(s, lam_m, lam_p):
|
|
|
108
108
|
# inner ls
|
|
109
109
|
# ========
|
|
110
110
|
|
|
111
|
-
def _inner_ls(x, f, poles,
|
|
111
|
+
def _inner_ls(x, f, poles, dpq=1, pade_reg=0.0):
|
|
112
112
|
"""
|
|
113
113
|
This is the inner least square (blazing fast).
|
|
114
|
+
|
|
115
|
+
dqp is the difference between the order of P (numerator) and Q
|
|
116
|
+
(denominator).
|
|
114
117
|
"""
|
|
115
118
|
|
|
116
|
-
if poles.size == 0 and
|
|
119
|
+
if poles.size == 0 and dpq == -1:
|
|
117
120
|
return 0.0, 0.0, numpy.empty(0)
|
|
118
121
|
|
|
119
122
|
if poles.size == 0: # q = 0
|
|
120
123
|
# A = numpy.column_stack((numpy.ones_like(x), x))
|
|
121
|
-
cols = [numpy.ones_like(x)] if
|
|
122
|
-
if
|
|
124
|
+
cols = [numpy.ones_like(x)] if dpq >= 0 else []
|
|
125
|
+
if dpq == 1:
|
|
123
126
|
cols.append(x)
|
|
124
127
|
A = numpy.column_stack(cols)
|
|
125
128
|
# ---
|
|
126
129
|
theta, *_ = lstsq(A, f, rcond=None)
|
|
127
130
|
# c, D = theta # TEST
|
|
128
|
-
if
|
|
131
|
+
if dpq == -1:
|
|
129
132
|
c = 0.0
|
|
130
133
|
D = 0.0
|
|
131
134
|
resid = numpy.empty(0)
|
|
132
|
-
elif
|
|
135
|
+
elif dpq == 0:
|
|
133
136
|
c = theta[0]
|
|
134
137
|
D = 0.0
|
|
135
138
|
resid = numpy.empty(0)
|
|
136
|
-
else: #
|
|
139
|
+
else: # dpq == 1
|
|
137
140
|
c, D = theta
|
|
138
141
|
resid = numpy.empty(0)
|
|
139
142
|
else:
|
|
@@ -142,28 +145,28 @@ def _inner_ls(x, f, poles, p=1, pade_reg=0.0):
|
|
|
142
145
|
# # theta, *_ = lstsq(A, f, rcond=None)
|
|
143
146
|
# # c, D, resid = theta[0], theta[1], theta[2:]
|
|
144
147
|
# phi = 1.0 / (x[:, None] - poles[None, :])
|
|
145
|
-
# cols = [numpy.ones_like(x)] if
|
|
146
|
-
# if
|
|
148
|
+
# cols = [numpy.ones_like(x)] if dpq >= 0 else []
|
|
149
|
+
# if dpq == 1:
|
|
147
150
|
# cols.append(x)
|
|
148
151
|
# cols.append(phi)
|
|
149
152
|
# A = numpy.column_stack(cols)
|
|
150
153
|
# theta, *_ = lstsq(A, f, rcond=None)
|
|
151
|
-
# if
|
|
154
|
+
# if dpq == -1:
|
|
152
155
|
# c = 0.0
|
|
153
156
|
# D = 0.0
|
|
154
157
|
# resid = theta
|
|
155
|
-
# elif
|
|
158
|
+
# elif dpq == 0:
|
|
156
159
|
# c = theta[0]
|
|
157
160
|
# D = 0.0
|
|
158
161
|
# resid = theta[1:]
|
|
159
|
-
# else: #
|
|
162
|
+
# else: # dpq == 1
|
|
160
163
|
# c = theta[0]
|
|
161
164
|
# D = theta[1]
|
|
162
165
|
# resid = theta[2:]
|
|
163
166
|
|
|
164
167
|
phi = 1.0 / (x[:, None] - poles[None, :])
|
|
165
|
-
cols = [numpy.ones_like(x)] if
|
|
166
|
-
if
|
|
168
|
+
cols = [numpy.ones_like(x)] if dpq >= 0 else []
|
|
169
|
+
if dpq == 1:
|
|
167
170
|
cols.append(x)
|
|
168
171
|
cols.append(phi)
|
|
169
172
|
|
|
@@ -179,9 +182,9 @@ def _inner_ls(x, f, poles, p=1, pade_reg=0.0):
|
|
|
179
182
|
# theta = numpy.linalg.solve(ATA, ATf)
|
|
180
183
|
|
|
181
184
|
# figure out how many elements to skip
|
|
182
|
-
if
|
|
185
|
+
if dpq == 1:
|
|
183
186
|
skip = 2 # skip c and D
|
|
184
|
-
elif
|
|
187
|
+
elif dpq == 0:
|
|
185
188
|
skip = 1 # skip c only
|
|
186
189
|
else:
|
|
187
190
|
skip = 0 # all entries are residues
|
|
@@ -198,11 +201,11 @@ def _inner_ls(x, f, poles, p=1, pade_reg=0.0):
|
|
|
198
201
|
else:
|
|
199
202
|
theta, *_ = lstsq(A, f, rcond=None)
|
|
200
203
|
|
|
201
|
-
if
|
|
204
|
+
if dpq == -1:
|
|
202
205
|
c, D, resid = 0.0, 0.0, theta
|
|
203
|
-
elif
|
|
206
|
+
elif dpq == 0:
|
|
204
207
|
c, D, resid = theta[0], 0.0, theta[1:]
|
|
205
|
-
else: #
|
|
208
|
+
else: # dpq == 1
|
|
206
209
|
c, D, resid = theta[0], theta[1], theta[2:]
|
|
207
210
|
|
|
208
211
|
return c, D, resid
|
|
@@ -240,7 +243,7 @@ def _eval_rational(z, c, D, poles, resid):
|
|
|
240
243
|
# fit pade
|
|
241
244
|
# ========
|
|
242
245
|
|
|
243
|
-
def fit_pade(x, f, lam_m, lam_p, p=
|
|
246
|
+
def fit_pade(x, f, lam_m, lam_p, p=2, q=2, odd_side='left', pade_reg=0.0,
|
|
244
247
|
safety=1.0, max_outer=40, xtol=1e-12, ftol=1e-12, optimizer='ls',
|
|
245
248
|
verbose=0):
|
|
246
249
|
"""
|
|
@@ -251,16 +254,19 @@ def fit_pade(x, f, lam_m, lam_p, p=1, q=2, odd_side='left', pade_reg=0.0,
|
|
|
251
254
|
if not (odd_side in ['left', 'right']):
|
|
252
255
|
raise ValueError('"odd_side" can only be "left" or "right".')
|
|
253
256
|
|
|
254
|
-
|
|
255
|
-
|
|
257
|
+
# Difference between the degrees of numerator and denominator
|
|
258
|
+
dpq = p - q
|
|
259
|
+
if not (dpq in [-1, 0, 1]):
|
|
260
|
+
raise ValueError('"pade_p" and "pade_q" can only differ by "+1", ' +
|
|
261
|
+
'"0", or "-1".')
|
|
256
262
|
|
|
257
263
|
x = numpy.asarray(x, float)
|
|
258
264
|
f = numpy.asarray(f, float)
|
|
259
265
|
|
|
260
266
|
poles0 = _default_poles(q, lam_m, lam_p, safety=safety, odd_side=odd_side)
|
|
261
|
-
if q == 0 and
|
|
267
|
+
if q == 0 and dpq <= 0:
|
|
262
268
|
# c, D, resid = _inner_ls(x, f, poles0, pade_reg=pade_reg) # TEST
|
|
263
|
-
c, D, resid = _inner_ls(x, f, poles0,
|
|
269
|
+
c, D, resid = _inner_ls(x, f, poles0, dpq, pade_reg=pade_reg)
|
|
264
270
|
pade_sol = {
|
|
265
271
|
'c': c, 'D': D, 'poles': poles0, 'resid': resid,
|
|
266
272
|
'outer_iters': 0
|
|
@@ -274,10 +280,10 @@ def fit_pade(x, f, lam_m, lam_p, p=1, q=2, odd_side='left', pade_reg=0.0,
|
|
|
274
280
|
# residual
|
|
275
281
|
# --------
|
|
276
282
|
|
|
277
|
-
def residual(s,
|
|
283
|
+
def residual(s, dpq=dpq):
|
|
278
284
|
poles = _decode_poles(s, lam_m, lam_p)
|
|
279
285
|
# c, D, resid = _inner_ls(x, f, poles, pade_reg=pade_reg) # TEST
|
|
280
|
-
c, D, resid = _inner_ls(x, f, poles,
|
|
286
|
+
c, D, resid = _inner_ls(x, f, poles, dpq, pade_reg=pade_reg)
|
|
281
287
|
return _eval_rational(x, c, D, poles, resid) - f
|
|
282
288
|
|
|
283
289
|
# ----------------
|
|
@@ -324,7 +330,7 @@ def fit_pade(x, f, lam_m, lam_p, p=1, q=2, odd_side='left', pade_reg=0.0,
|
|
|
324
330
|
|
|
325
331
|
poles = _decode_poles(res.x, lam_m, lam_p)
|
|
326
332
|
# c, D, resid = _inner_ls(x, f, poles, pade_reg=pade_reg) # TEST
|
|
327
|
-
c, D, resid = _inner_ls(x, f, poles,
|
|
333
|
+
c, D, resid = _inner_ls(x, f, poles, dpq, pade_reg=pade_reg)
|
|
328
334
|
|
|
329
335
|
pade_sol = {
|
|
330
336
|
'c': c, 'D': D, 'poles': poles, 'resid': resid,
|
|
@@ -364,127 +370,3 @@ def eval_pade(z, pade_sol):
|
|
|
364
370
|
for bj, rj in zip(poles, resid):
|
|
365
371
|
out += rj/(z - bj) # each is an (N,) op, no N*q temp
|
|
366
372
|
return out
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
# ============
|
|
370
|
-
# fit pade old
|
|
371
|
-
# ============
|
|
372
|
-
|
|
373
|
-
def fit_pade_old(x, f, lam_m, lam_p, p, q, delta=1e-8, B=numpy.inf,
|
|
374
|
-
S=numpy.inf, B_default=10.0, S_factor=2.0, maxiter_de=200):
|
|
375
|
-
"""
|
|
376
|
-
Deprecated.
|
|
377
|
-
|
|
378
|
-
Fit a [p/q] rational P/Q of the form:
|
|
379
|
-
P(x) = s * prod_{i=0..p-1}(x - a_i)
|
|
380
|
-
Q(x) = prod_{j=0..q-1}(x - b_j)
|
|
381
|
-
|
|
382
|
-
Constraints:
|
|
383
|
-
a_i in [lam_m, lam_p]
|
|
384
|
-
b_j in (-infty, lam_m - delta] cup [lam_p + delta, infty)
|
|
385
|
-
|
|
386
|
-
Approach:
|
|
387
|
-
- Brute-force all 2^q left/right assignments for denominator roots
|
|
388
|
-
- Global search with differential_evolution, fallback to zeros if needed
|
|
389
|
-
- Local refinement with least_squares
|
|
390
|
-
|
|
391
|
-
Returns a dict with keys:
|
|
392
|
-
's' : optimal scale factor
|
|
393
|
-
'a' : array of p numerator roots (in [lam_m, lam_p])
|
|
394
|
-
'b' : array of q denominator roots (outside the interval)
|
|
395
|
-
'resid' : final residual norm
|
|
396
|
-
'signs' : tuple indicating left/right pattern for each b_j
|
|
397
|
-
"""
|
|
398
|
-
|
|
399
|
-
# Determine finite bounds for DE
|
|
400
|
-
if not numpy.isfinite(B):
|
|
401
|
-
B_eff = B_default
|
|
402
|
-
else:
|
|
403
|
-
B_eff = B
|
|
404
|
-
if not numpy.isfinite(S):
|
|
405
|
-
# scale bound: S_factor * max|f| * interval width + safety
|
|
406
|
-
S_eff = S_factor * numpy.max(numpy.abs(f)) * (lam_p - lam_m) + 1.0
|
|
407
|
-
if S_eff <= 0:
|
|
408
|
-
S_eff = 1.0
|
|
409
|
-
else:
|
|
410
|
-
S_eff = S
|
|
411
|
-
|
|
412
|
-
def map_roots(signs, b):
|
|
413
|
-
"""Map unconstrained b_j -> real root outside the interval."""
|
|
414
|
-
out = numpy.empty_like(b)
|
|
415
|
-
for j, (s_val, bj) in enumerate(zip(signs, b)):
|
|
416
|
-
if s_val > 0:
|
|
417
|
-
out[j] = lam_p + delta + numpy.exp(bj)
|
|
418
|
-
else:
|
|
419
|
-
out[j] = lam_m - delta - numpy.exp(bj)
|
|
420
|
-
return out
|
|
421
|
-
|
|
422
|
-
best = {'resid': numpy.inf}
|
|
423
|
-
|
|
424
|
-
# Enumerate all left/right sign patterns
|
|
425
|
-
for signs in product([-1, 1], repeat=q):
|
|
426
|
-
# Residual vector for current pattern
|
|
427
|
-
def resid_vec(z):
|
|
428
|
-
s_val = z[0]
|
|
429
|
-
a = z[1:1+p]
|
|
430
|
-
b = z[1+p:]
|
|
431
|
-
P = s_val * numpy.prod(x[:, None] - a[None, :], axis=1)
|
|
432
|
-
roots_Q = map_roots(signs, b)
|
|
433
|
-
Q = numpy.prod(x[:, None] - roots_Q[None, :], axis=1)
|
|
434
|
-
return P - f * Q
|
|
435
|
-
|
|
436
|
-
def obj(z):
|
|
437
|
-
r = resid_vec(z)
|
|
438
|
-
return r.dot(r)
|
|
439
|
-
|
|
440
|
-
# Build bounds for DE
|
|
441
|
-
bounds = []
|
|
442
|
-
bounds.append((-S_eff, S_eff)) # s
|
|
443
|
-
bounds += [(lam_m, lam_p)] * p # a_i
|
|
444
|
-
bounds += [(-B_eff, B_eff)] * q # b_j
|
|
445
|
-
|
|
446
|
-
# 1) Global search
|
|
447
|
-
try:
|
|
448
|
-
de = differential_evolution(obj, bounds,
|
|
449
|
-
maxiter=maxiter_de,
|
|
450
|
-
polish=False)
|
|
451
|
-
z0 = de.x
|
|
452
|
-
except ValueError:
|
|
453
|
-
# fallback: start at zeros
|
|
454
|
-
z0 = numpy.zeros(1 + p + q)
|
|
455
|
-
|
|
456
|
-
# 2) Local refinement
|
|
457
|
-
ls = least_squares(resid_vec, z0, xtol=1e-12, ftol=1e-12)
|
|
458
|
-
|
|
459
|
-
rnorm = numpy.linalg.norm(resid_vec(ls.x))
|
|
460
|
-
if rnorm < best['resid']:
|
|
461
|
-
best.update(resid=rnorm, signs=signs, x=ls.x.copy())
|
|
462
|
-
|
|
463
|
-
# Unpack best solution
|
|
464
|
-
z_best = best['x']
|
|
465
|
-
s_opt = z_best[0]
|
|
466
|
-
a_opt = z_best[1:1+p]
|
|
467
|
-
b_opt = map_roots(best['signs'], z_best[1+p:])
|
|
468
|
-
|
|
469
|
-
return {
|
|
470
|
-
's': s_opt,
|
|
471
|
-
'a': a_opt,
|
|
472
|
-
'b': b_opt,
|
|
473
|
-
'resid': best['resid'],
|
|
474
|
-
'signs': best['signs'],
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
# =============
|
|
479
|
-
# eval pade old
|
|
480
|
-
# =============
|
|
481
|
-
|
|
482
|
-
def eval_pade_old(z, s, a, b):
|
|
483
|
-
"""
|
|
484
|
-
Deprecated.
|
|
485
|
-
"""
|
|
486
|
-
|
|
487
|
-
Pz = s * numpy.prod([z - aj for aj in a], axis=0)
|
|
488
|
-
Qz = numpy.prod([z - bj for bj in b], axis=0)
|
|
489
|
-
|
|
490
|
-
return Pz / Qz
|
|
@@ -113,9 +113,14 @@ def sample(x, rho, num_pts, method='qmc', seed=None):
|
|
|
113
113
|
# Draw from uniform distribution
|
|
114
114
|
if method == 'mc':
|
|
115
115
|
u = rng.random(num_pts)
|
|
116
|
+
|
|
116
117
|
elif method == 'qmc':
|
|
117
|
-
|
|
118
|
-
|
|
118
|
+
try:
|
|
119
|
+
engine = qmc.Halton(d=1, scramble=True, rng=rng)
|
|
120
|
+
except TypeError:
|
|
121
|
+
engine = qmc.Halton(d=1, scramble=True, seed=rng)
|
|
122
|
+
u = engine.random(num_pts).ravel()
|
|
123
|
+
|
|
119
124
|
else:
|
|
120
125
|
raise NotImplementedError('"method" is invalid.')
|
|
121
126
|
|
|
@@ -126,6 +126,27 @@ def kde(eig, xs, lam_m, lam_p, h, kernel='beta', plot=False):
|
|
|
126
126
|
|
|
127
127
|
freealg.supp
|
|
128
128
|
freealg.sample
|
|
129
|
+
|
|
130
|
+
Notes
|
|
131
|
+
-----
|
|
132
|
+
|
|
133
|
+
In Beta kernel density estimation, the shape parameters "a" and "b" of the
|
|
134
|
+
Beta(a, b)) distribution are computed for each data point "u" as:
|
|
135
|
+
|
|
136
|
+
a = (u / h) + 1.0
|
|
137
|
+
b = ((1.0 - u) / h) + 1.0
|
|
138
|
+
|
|
139
|
+
This is a standard way of using Beta kernel (see R-package documentation:
|
|
140
|
+
https://search.r-project.org/CRAN/refmans/DELTD/html/Beta.html
|
|
141
|
+
|
|
142
|
+
These equations are derived from "moment matching" method, where
|
|
143
|
+
|
|
144
|
+
Mean(Beta(a,b)) = u
|
|
145
|
+
Var(Beta(a,b)) = (1-u) u h
|
|
146
|
+
|
|
147
|
+
Solving these two equations for "a" and "b" yields the relations above.
|
|
148
|
+
See paper (page 134)
|
|
149
|
+
https://www.songxichen.com/Uploads/Files/Publication/Chen-CSD-99.pdf
|
|
129
150
|
"""
|
|
130
151
|
|
|
131
152
|
if kernel == 'gaussian':
|
|
@@ -141,28 +162,39 @@ def kde(eig, xs, lam_m, lam_p, h, kernel='beta', plot=False):
|
|
|
141
162
|
|
|
142
163
|
span = lam_p - lam_m
|
|
143
164
|
if span <= 0:
|
|
144
|
-
raise ValueError("lam_p must be larger than lam_m")
|
|
165
|
+
raise ValueError('"lam_p" must be larger than "lam_m".')
|
|
145
166
|
|
|
146
167
|
# map samples and grid to [0, 1]
|
|
147
168
|
u = (eig - lam_m) / span
|
|
148
169
|
t = (xs - lam_m) / span
|
|
149
170
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
u = u[
|
|
171
|
+
# keep only samples strictly inside (0,1)
|
|
172
|
+
if (u.min() < 0) or (u.max() > 1):
|
|
173
|
+
u = u[(u > 0) & (u < 1)]
|
|
174
|
+
|
|
175
|
+
n = u.size
|
|
176
|
+
if n == 0:
|
|
177
|
+
return numpy.zeros_like(xs, dtype=float)
|
|
153
178
|
|
|
154
|
-
|
|
155
|
-
|
|
179
|
+
# Shape parameters "a" and "b" or the kernel Beta(a, b), which is
|
|
180
|
+
# computed for each data point "u" (see notes above). These are
|
|
181
|
+
# vectorized.
|
|
182
|
+
a = (u / h) + 1.0
|
|
183
|
+
b = ((1.0 - u) / h) + 1.0
|
|
156
184
|
|
|
157
|
-
# tiny positive number to keep shape parameters > 0
|
|
185
|
+
# # tiny positive number to keep shape parameters > 0
|
|
158
186
|
eps = 1e-6
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
187
|
+
a = numpy.clip(a, eps, None)
|
|
188
|
+
b = numpy.clip(b, eps, None)
|
|
189
|
+
|
|
190
|
+
# Beta kernel
|
|
191
|
+
pdf_matrix = beta.pdf(t[None, :], a[:, None], b[:, None])
|
|
192
|
+
|
|
193
|
+
# Average and re-normalize back to x variable
|
|
194
|
+
pdf = pdf_matrix.sum(axis=0) / (n * span)
|
|
163
195
|
|
|
164
|
-
|
|
165
|
-
pdf[(t < 0) | (t > 1)] = 0.0
|
|
196
|
+
# Exact zeros outside [lam_m, lam_p]
|
|
197
|
+
pdf[(t < 0) | (t > 1)] = 0.0
|
|
166
198
|
|
|
167
199
|
else:
|
|
168
200
|
raise NotImplementedError('"kernel" is invalid.')
|
|
@@ -526,9 +526,15 @@ class KestenMcKay(object):
|
|
|
526
526
|
# Draw from uniform distribution
|
|
527
527
|
if method == 'mc':
|
|
528
528
|
u = rng.random(size)
|
|
529
|
+
|
|
529
530
|
elif method == 'qmc':
|
|
530
|
-
|
|
531
|
-
|
|
531
|
+
try:
|
|
532
|
+
engine = qmc.Halton(d=1, scramble=True, rng=rng)
|
|
533
|
+
except TypeError:
|
|
534
|
+
# Older scipy versions
|
|
535
|
+
engine = qmc.Halton(d=1, scramble=True, seed=rng)
|
|
536
|
+
u = engine.random(size).ravel()
|
|
537
|
+
|
|
532
538
|
else:
|
|
533
539
|
raise NotImplementedError('"method" is invalid.')
|
|
534
540
|
|
|
@@ -533,9 +533,15 @@ class MarchenkoPastur(object):
|
|
|
533
533
|
# Draw from uniform distribution
|
|
534
534
|
if method == 'mc':
|
|
535
535
|
u = rng.random(size)
|
|
536
|
+
|
|
536
537
|
elif method == 'qmc':
|
|
537
|
-
|
|
538
|
-
|
|
538
|
+
try:
|
|
539
|
+
engine = qmc.Halton(d=1, scramble=True, rng=rng)
|
|
540
|
+
except TypeError:
|
|
541
|
+
# Older scipy versions
|
|
542
|
+
engine = qmc.Halton(d=1, scramble=True, seed=rng)
|
|
543
|
+
u = engine.random(size).ravel()
|
|
544
|
+
|
|
539
545
|
else:
|
|
540
546
|
raise NotImplementedError('"method" is invalid.')
|
|
541
547
|
|
|
@@ -177,18 +177,12 @@ class Meixner(object):
|
|
|
177
177
|
rho = numpy.zeros_like(x)
|
|
178
178
|
mask = numpy.logical_and(x > self.lam_m, x < self.lam_p)
|
|
179
179
|
|
|
180
|
-
# rho[mask] = \
|
|
181
|
-
# numpy.sqrt(4.0 * (1.0 + self.b) - (x[mask] - self.a)**2) / \
|
|
182
|
-
# (2.0 * numpy.pi * (self.b * x[mask]**2 + self.a * x[mask] + 1))
|
|
183
|
-
|
|
184
180
|
numer = numpy.zeros_like(x)
|
|
185
181
|
denom = numpy.ones_like(x)
|
|
186
182
|
numer[mask] = self.c * numpy.sqrt(4.0 * self.b - (x[mask] - self.a)**2)
|
|
187
|
-
denom[mask] =
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
denom[mask] *= 2 * numpy.pi
|
|
191
|
-
|
|
183
|
+
denom[mask] = 2.0 * numpy.pi * (
|
|
184
|
+
(1.0 - self.c) * x[mask]**2 + self.a * self.c * x[mask] +
|
|
185
|
+
self.b * self.c**2)
|
|
192
186
|
rho[mask] = numer[mask] / denom[mask]
|
|
193
187
|
|
|
194
188
|
if plot:
|
|
@@ -260,14 +254,14 @@ class Meixner(object):
|
|
|
260
254
|
def _P(x):
|
|
261
255
|
# denom = 1.0 + self.b
|
|
262
256
|
# return ((1.0 + 2.0 * self.b) * x + self.a) / denom
|
|
263
|
-
P = (
|
|
257
|
+
P = (self.c - 2.0) * x - self.a * self.c
|
|
264
258
|
return P
|
|
265
259
|
|
|
266
260
|
def _Q(x):
|
|
267
261
|
# denom = 1.0 + self.b
|
|
268
262
|
# return (self.b * x**2 + self.a * x + 1.0) / denom
|
|
269
|
-
Q = (
|
|
270
|
-
self.b * self.c**2
|
|
263
|
+
Q = (1.0 - self.c) * x**2 + self.a * self.c * x + \
|
|
264
|
+
self.b * self.c**2
|
|
271
265
|
return Q
|
|
272
266
|
|
|
273
267
|
P = _P(x)
|
|
@@ -277,9 +271,6 @@ class Meixner(object):
|
|
|
277
271
|
sign = numpy.sign(P)
|
|
278
272
|
hilb = (P - sign * Delta) / (2.0 * Q)
|
|
279
273
|
|
|
280
|
-
# using negative sign convention
|
|
281
|
-
hilb = -hilb
|
|
282
|
-
|
|
283
274
|
if plot:
|
|
284
275
|
plot_hilbert(x, hilb, support=self.support, latex=latex, save=save)
|
|
285
276
|
|
|
@@ -299,21 +290,26 @@ class Meixner(object):
|
|
|
299
290
|
# denom = 1.0 + self.b
|
|
300
291
|
# A = (self.b * z**2 + self.a * z + 1.0) / denom
|
|
301
292
|
# B = ((1.0 + 2.0 * self.b) * z + self.a) / denom
|
|
302
|
-
A = ((1.0 - self.c) * z**2 + self.a * self.c * z +
|
|
303
|
-
|
|
304
|
-
B = ((self.c - 2.0) * z - self.a * self.c) / 2.0
|
|
293
|
+
# A = ((1.0 - self.c) * z**2 + self.a * self.c * z +
|
|
294
|
+
# self.b * self.c**2) / 4.0
|
|
295
|
+
# B = ((self.c - 2.0) * z - self.a * self.c) / 2.0
|
|
296
|
+
|
|
297
|
+
Q = (1.0 - self.c) * z**2 + self.a * self.c * z + \
|
|
298
|
+
self.b * self.c**2
|
|
299
|
+
P = (self.c - 2.0) * z - self.a * self.c
|
|
305
300
|
|
|
306
301
|
# D = B**2 - 4 * A
|
|
307
302
|
# sqrtD = numpy.sqrt(D)
|
|
308
303
|
|
|
309
304
|
# Avoid numpy picking the wrong branch
|
|
310
|
-
d = 2 * numpy.sqrt(1.0 + self.b)
|
|
311
|
-
r_min = self.a - d
|
|
312
|
-
r_max = self.a + d
|
|
313
|
-
sqrtD = numpy.sqrt(z - r_min) * numpy.sqrt(z - r_max)
|
|
305
|
+
# d = 2 * numpy.sqrt(1.0 + self.b)
|
|
306
|
+
# r_min = self.a - d
|
|
307
|
+
# r_max = self.a + d
|
|
308
|
+
# sqrtD = numpy.sqrt(z - r_min) * numpy.sqrt(z - r_max)
|
|
309
|
+
sqrtD = numpy.sqrt(P**2 - 4.0 * Q)
|
|
314
310
|
|
|
315
|
-
m1 = (
|
|
316
|
-
m2 = (
|
|
311
|
+
m1 = (P + sqrtD) / (2 * Q)
|
|
312
|
+
m2 = (P - sqrtD) / (2 * Q)
|
|
317
313
|
|
|
318
314
|
# pick correct branch only for non-masked entries
|
|
319
315
|
upper = z.imag >= 0
|
|
@@ -558,9 +554,15 @@ class Meixner(object):
|
|
|
558
554
|
# Draw from uniform distribution
|
|
559
555
|
if method == 'mc':
|
|
560
556
|
u = rng.random(size)
|
|
557
|
+
|
|
561
558
|
elif method == 'qmc':
|
|
562
|
-
|
|
563
|
-
|
|
559
|
+
try:
|
|
560
|
+
engine = qmc.Halton(d=1, scramble=True, rng=rng)
|
|
561
|
+
except TypeError:
|
|
562
|
+
# Older scipy versions
|
|
563
|
+
engine = qmc.Halton(d=1, scramble=True, seed=rng)
|
|
564
|
+
u = engine.random(size).ravel()
|
|
565
|
+
|
|
564
566
|
else:
|
|
565
567
|
raise NotImplementedError('"method" is invalid.')
|
|
566
568
|
|
|
@@ -533,9 +533,15 @@ class Wachter(object):
|
|
|
533
533
|
# Draw from uniform distribution
|
|
534
534
|
if method == 'mc':
|
|
535
535
|
u = rng.random(size)
|
|
536
|
+
|
|
536
537
|
elif method == 'qmc':
|
|
537
|
-
|
|
538
|
-
|
|
538
|
+
try:
|
|
539
|
+
engine = qmc.Halton(d=1, scramble=True, rng=rng)
|
|
540
|
+
except TypeError:
|
|
541
|
+
# Older scipy versions
|
|
542
|
+
engine = qmc.Halton(d=1, scramble=True, seed=rng)
|
|
543
|
+
u = engine.random(size).ravel()
|
|
544
|
+
|
|
539
545
|
else:
|
|
540
546
|
raise NotImplementedError('"method" is invalid.')
|
|
541
547
|
|
|
@@ -510,9 +510,15 @@ class Wigner(object):
|
|
|
510
510
|
# Draw from uniform distribution
|
|
511
511
|
if method == 'mc':
|
|
512
512
|
u = rng.random(size)
|
|
513
|
+
|
|
513
514
|
elif method == 'qmc':
|
|
514
|
-
|
|
515
|
-
|
|
515
|
+
try:
|
|
516
|
+
engine = qmc.Halton(d=1, scramble=True, rng=rng)
|
|
517
|
+
except TypeError:
|
|
518
|
+
# Older scipy versions
|
|
519
|
+
engine = qmc.Halton(d=1, scramble=True, seed=rng)
|
|
520
|
+
u = engine.random(size).ravel()
|
|
521
|
+
|
|
516
522
|
else:
|
|
517
523
|
raise NotImplementedError('"method" is invalid.')
|
|
518
524
|
|
|
@@ -178,10 +178,13 @@ class FreeForm(object):
|
|
|
178
178
|
# Detect support
|
|
179
179
|
self.lam_m, self.lam_p = supp(self.eig, **kwargs)
|
|
180
180
|
else:
|
|
181
|
-
self.lam_m = support[0]
|
|
182
|
-
self.lam_p = support[1]
|
|
181
|
+
self.lam_m = float(support[0])
|
|
182
|
+
self.lam_p = float(support[1])
|
|
183
183
|
self.support = (self.lam_m, self.lam_p)
|
|
184
184
|
|
|
185
|
+
# Number of quadrature points to evaluate Stieltjes using Gauss-Jacobi
|
|
186
|
+
self.n_quad = None
|
|
187
|
+
|
|
185
188
|
# Initialize
|
|
186
189
|
self.method = None # fitting rho: jacobi, chebyshev
|
|
187
190
|
self.continuation = None # analytic continuation: pade, wynn
|
|
@@ -189,15 +192,17 @@ class FreeForm(object):
|
|
|
189
192
|
self.psi = None # coefficients of estimating rho
|
|
190
193
|
self.alpha = None # Jacobi polynomials alpha parameter
|
|
191
194
|
self.beta = None # Jacobi polynomials beta parameter
|
|
195
|
+
self.cache = {} # Cache inner-computations
|
|
192
196
|
|
|
193
197
|
# ===
|
|
194
198
|
# fit
|
|
195
199
|
# ===
|
|
196
200
|
|
|
197
|
-
def fit(self, method='jacobi', K=10, alpha=0.0, beta=0.0,
|
|
198
|
-
projection='gaussian', kernel_bw=0.001, damp=None,
|
|
199
|
-
continuation='pade', pade_p=
|
|
200
|
-
pade_reg=0.0, optimizer='ls', plot=False,
|
|
201
|
+
def fit(self, method='jacobi', K=10, alpha=0.0, beta=0.0, n_quad=60,
|
|
202
|
+
reg=0.0, projection='gaussian', kernel_bw=0.001, damp=None,
|
|
203
|
+
force=False, continuation='pade', pade_p=1, pade_q=1,
|
|
204
|
+
odd_side='left', pade_reg=0.0, optimizer='ls', plot=False,
|
|
205
|
+
latex=False, save=False):
|
|
201
206
|
"""
|
|
202
207
|
Fit model to eigenvalues.
|
|
203
208
|
|
|
@@ -221,6 +226,11 @@ class FreeForm(object):
|
|
|
221
226
|
fitting model on the left side of interval. This should be greater
|
|
222
227
|
then -1. This option is only applicable when ``method='jacobi'``.
|
|
223
228
|
|
|
229
|
+
n_quad : int, default=60
|
|
230
|
+
Number of quadrature points to evaluate Stieltjes transform later
|
|
231
|
+
on (when :func:`decompress` is called) using Gauss-Jacob
|
|
232
|
+
quadrature. This option is relevant only if ``method='jacobi'``.
|
|
233
|
+
|
|
224
234
|
reg : float, default=0.0
|
|
225
235
|
Tikhonov regularization coefficient.
|
|
226
236
|
|
|
@@ -265,14 +275,15 @@ class FreeForm(object):
|
|
|
265
275
|
* ``'brezinski'``: Brezinski's :math:`\\theta` algorithm
|
|
266
276
|
(`experimental`).
|
|
267
277
|
|
|
268
|
-
pade_p : int, default=
|
|
269
|
-
Degree of polynomial :math:`P(z)` is :math:`
|
|
270
|
-
|
|
278
|
+
pade_p : int, default=1
|
|
279
|
+
Degree of polynomial :math:`P(z)` is :math:`p` where :math:`p` can
|
|
280
|
+
only be ``q-1``, ``q``, or ``q+1``. See notes below. This option
|
|
271
281
|
is applicable if ``continuation='pade'``.
|
|
272
282
|
|
|
273
283
|
pade_q : int, default=1
|
|
274
|
-
Degree of polynomial :math:`Q(z)` is :math:`q
|
|
275
|
-
|
|
284
|
+
Degree of polynomial :math:`Q(z)` is :math:`q` where :math:`q` can
|
|
285
|
+
only be ``p-1``, ``p``, or ``p+1``. See notes below. This option
|
|
286
|
+
is applicable if ``continuation='pade'``.
|
|
276
287
|
|
|
277
288
|
odd_side : {``'left'``, ``'right'``}, default= ``'left'``
|
|
278
289
|
In case of odd number of poles (when :math:`q` is odd), the extra
|
|
@@ -336,6 +347,10 @@ class FreeForm(object):
|
|
|
336
347
|
>>> from freealg import FreeForm
|
|
337
348
|
"""
|
|
338
349
|
|
|
350
|
+
# Very important: reset cache whenever this function is called. This
|
|
351
|
+
# also empties all references holdign a cache copy.
|
|
352
|
+
self.cache.clear()
|
|
353
|
+
|
|
339
354
|
if alpha <= -1:
|
|
340
355
|
raise ValueError('"alpha" should be greater then "-1".')
|
|
341
356
|
|
|
@@ -351,6 +366,10 @@ class FreeForm(object):
|
|
|
351
366
|
# Project eigenvalues to Jacobi polynomials basis
|
|
352
367
|
if method == 'jacobi':
|
|
353
368
|
|
|
369
|
+
# Set number of Gauss-Jacobi quadratures. This is not used in this
|
|
370
|
+
# function (used later when decompress is called)
|
|
371
|
+
self.n_quad = n_quad
|
|
372
|
+
|
|
354
373
|
if projection == 'sample':
|
|
355
374
|
psi = jacobi_sample_proj(self.eig, support=self.support, K=K,
|
|
356
375
|
alpha=alpha, beta=beta, reg=reg)
|
|
@@ -726,6 +745,7 @@ class FreeForm(object):
|
|
|
726
745
|
|
|
727
746
|
See Also
|
|
728
747
|
--------
|
|
748
|
+
|
|
729
749
|
density
|
|
730
750
|
hilbert
|
|
731
751
|
|
|
@@ -757,7 +777,7 @@ class FreeForm(object):
|
|
|
757
777
|
|
|
758
778
|
# Create y if not given
|
|
759
779
|
if (plot is False) and (y is None):
|
|
760
|
-
# Do
|
|
780
|
+
# Do not use a Cartesian grid. Create a 1D array z slightly above
|
|
761
781
|
# the real line.
|
|
762
782
|
y = self.delta * 1j
|
|
763
783
|
z = x.astype(complex) + y # shape (Nx,)
|
|
@@ -809,25 +829,27 @@ class FreeForm(object):
|
|
|
809
829
|
if self.psi is None:
|
|
810
830
|
raise RuntimeError('"fit" the model first.')
|
|
811
831
|
|
|
812
|
-
# Allow for arbitrary input shapes
|
|
813
832
|
z = numpy.asarray(z)
|
|
814
|
-
shape = z.shape
|
|
815
|
-
if len(shape) == 0:
|
|
816
|
-
shape = (1,)
|
|
817
|
-
z = z.reshape(-1, 1)
|
|
818
|
-
|
|
819
|
-
# # Set the number of bases as the number of x points insides support
|
|
820
|
-
# mask_sup = numpy.logical_and(z.real >= self.lam_m,
|
|
821
|
-
# z.real <= self.lam_p)
|
|
822
|
-
# n_base = 2 * numpy.sum(mask_sup)
|
|
823
833
|
|
|
824
834
|
# Stieltjes function
|
|
825
835
|
if self.method == 'jacobi':
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
836
|
+
|
|
837
|
+
# Number of quadrature points
|
|
838
|
+
if z.ndim == 2:
|
|
839
|
+
# set to twice num x points inside support. This oversampling
|
|
840
|
+
# avoids anti-aliasing when visualizing.
|
|
841
|
+
x = z[0, :].real
|
|
842
|
+
mask_sup = numpy.logical_and(x >= self.lam_m, x <= self.lam_p)
|
|
843
|
+
n_quad = 2 * numpy.sum(mask_sup)
|
|
844
|
+
else:
|
|
845
|
+
# If this is None, the calling function will handle it.
|
|
846
|
+
n_quad = self.n_quad
|
|
847
|
+
|
|
848
|
+
stieltjes = partial(jacobi_stieltjes, cache=self.cache,
|
|
849
|
+
psi=self.psi, support=self.support,
|
|
850
|
+
alpha=self.alpha, beta=self.beta,
|
|
851
|
+
continuation=self.continuation,
|
|
852
|
+
dtype=self.dtype, n_quad=n_quad)
|
|
831
853
|
|
|
832
854
|
elif self.method == 'chebyshev':
|
|
833
855
|
stieltjes = partial(chebyshev_stieltjes, psi=self.psi,
|
|
@@ -835,6 +857,12 @@ class FreeForm(object):
|
|
|
835
857
|
continuation=self.continuation,
|
|
836
858
|
dtype=self.dtype)
|
|
837
859
|
|
|
860
|
+
# Allow for arbitrary input shapes
|
|
861
|
+
shape = z.shape
|
|
862
|
+
if len(shape) == 0:
|
|
863
|
+
shape = (1,)
|
|
864
|
+
z = z.reshape(-1, 1)
|
|
865
|
+
|
|
838
866
|
mask_p = z.imag >= 0.0
|
|
839
867
|
mask_m = z.imag < 0.0
|
|
840
868
|
|
|
@@ -930,7 +958,7 @@ class FreeForm(object):
|
|
|
930
958
|
Estimated spectral density at locations x. ``rho`` can be a 1D or
|
|
931
959
|
2D array output:
|
|
932
960
|
|
|
933
|
-
* If ``size`` is a scalar, ``rho`` is a 1D array
|
|
961
|
+
* If ``size`` is a scalar, ``rho`` is a 1D array of the same size
|
|
934
962
|
as ``x``.
|
|
935
963
|
* If ``size`` is an array of size `n`, ``rho`` is a 2D array with
|
|
936
964
|
`n` rows, where each row corresponds to decompression to a size.
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.6.0"
|
|
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
|