freealg 0.6.1__py3-none-any.whl → 0.6.3__py3-none-any.whl
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/__version__.py +1 -1
- freealg/_decompress.py +0 -10
- freealg/_pade.py +33 -152
- freealg/_sample.py +7 -2
- freealg/_util.py +56 -13
- freealg/distributions/_kesten_mckay.py +8 -2
- freealg/distributions/_marchenko_pastur.py +10 -4
- freealg/distributions/_meixner.py +28 -26
- freealg/distributions/_wachter.py +8 -2
- freealg/distributions/_wigner.py +8 -2
- freealg/freeform.py +7 -36
- {freealg-0.6.1.dist-info → freealg-0.6.3.dist-info}/METADATA +9 -11
- freealg-0.6.3.dist-info/RECORD +26 -0
- freealg-0.6.1.dist-info/RECORD +0 -26
- {freealg-0.6.1.dist-info → freealg-0.6.3.dist-info}/WHEEL +0 -0
- {freealg-0.6.1.dist-info → freealg-0.6.3.dist-info}/licenses/AUTHORS.txt +0 -0
- {freealg-0.6.1.dist-info → freealg-0.6.3.dist-info}/licenses/LICENSE.txt +0 -0
- {freealg-0.6.1.dist-info → freealg-0.6.3.dist-info}/top_level.txt +0 -0
freealg/__version__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.6.
|
|
1
|
+
__version__ = "0.6.3"
|
freealg/_decompress.py
CHANGED
freealg/_pade.py
CHANGED
|
@@ -13,7 +13,6 @@
|
|
|
13
13
|
|
|
14
14
|
import numpy
|
|
15
15
|
from numpy.linalg import lstsq
|
|
16
|
-
from itertools import product
|
|
17
16
|
from scipy.optimize import least_squares, differential_evolution
|
|
18
17
|
|
|
19
18
|
__all__ = ['fit_pade', 'eval_pade']
|
|
@@ -108,32 +107,35 @@ def _decode_poles(s, lam_m, lam_p):
|
|
|
108
107
|
# inner ls
|
|
109
108
|
# ========
|
|
110
109
|
|
|
111
|
-
def _inner_ls(x, f, poles,
|
|
110
|
+
def _inner_ls(x, f, poles, dpq=1, pade_reg=0.0):
|
|
112
111
|
"""
|
|
113
112
|
This is the inner least square (blazing fast).
|
|
113
|
+
|
|
114
|
+
dqp is the difference between the order of P (numerator) and Q
|
|
115
|
+
(denominator).
|
|
114
116
|
"""
|
|
115
117
|
|
|
116
|
-
if poles.size == 0 and
|
|
118
|
+
if poles.size == 0 and dpq == -1:
|
|
117
119
|
return 0.0, 0.0, numpy.empty(0)
|
|
118
120
|
|
|
119
121
|
if poles.size == 0: # q = 0
|
|
120
122
|
# A = numpy.column_stack((numpy.ones_like(x), x))
|
|
121
|
-
cols = [numpy.ones_like(x)] if
|
|
122
|
-
if
|
|
123
|
+
cols = [numpy.ones_like(x)] if dpq >= 0 else []
|
|
124
|
+
if dpq == 1:
|
|
123
125
|
cols.append(x)
|
|
124
126
|
A = numpy.column_stack(cols)
|
|
125
127
|
# ---
|
|
126
128
|
theta, *_ = lstsq(A, f, rcond=None)
|
|
127
129
|
# c, D = theta # TEST
|
|
128
|
-
if
|
|
130
|
+
if dpq == -1:
|
|
129
131
|
c = 0.0
|
|
130
132
|
D = 0.0
|
|
131
133
|
resid = numpy.empty(0)
|
|
132
|
-
elif
|
|
134
|
+
elif dpq == 0:
|
|
133
135
|
c = theta[0]
|
|
134
136
|
D = 0.0
|
|
135
137
|
resid = numpy.empty(0)
|
|
136
|
-
else: #
|
|
138
|
+
else: # dpq == 1
|
|
137
139
|
c, D = theta
|
|
138
140
|
resid = numpy.empty(0)
|
|
139
141
|
else:
|
|
@@ -142,28 +144,28 @@ def _inner_ls(x, f, poles, p=1, pade_reg=0.0):
|
|
|
142
144
|
# # theta, *_ = lstsq(A, f, rcond=None)
|
|
143
145
|
# # c, D, resid = theta[0], theta[1], theta[2:]
|
|
144
146
|
# phi = 1.0 / (x[:, None] - poles[None, :])
|
|
145
|
-
# cols = [numpy.ones_like(x)] if
|
|
146
|
-
# if
|
|
147
|
+
# cols = [numpy.ones_like(x)] if dpq >= 0 else []
|
|
148
|
+
# if dpq == 1:
|
|
147
149
|
# cols.append(x)
|
|
148
150
|
# cols.append(phi)
|
|
149
151
|
# A = numpy.column_stack(cols)
|
|
150
152
|
# theta, *_ = lstsq(A, f, rcond=None)
|
|
151
|
-
# if
|
|
153
|
+
# if dpq == -1:
|
|
152
154
|
# c = 0.0
|
|
153
155
|
# D = 0.0
|
|
154
156
|
# resid = theta
|
|
155
|
-
# elif
|
|
157
|
+
# elif dpq == 0:
|
|
156
158
|
# c = theta[0]
|
|
157
159
|
# D = 0.0
|
|
158
160
|
# resid = theta[1:]
|
|
159
|
-
# else: #
|
|
161
|
+
# else: # dpq == 1
|
|
160
162
|
# c = theta[0]
|
|
161
163
|
# D = theta[1]
|
|
162
164
|
# resid = theta[2:]
|
|
163
165
|
|
|
164
166
|
phi = 1.0 / (x[:, None] - poles[None, :])
|
|
165
|
-
cols = [numpy.ones_like(x)] if
|
|
166
|
-
if
|
|
167
|
+
cols = [numpy.ones_like(x)] if dpq >= 0 else []
|
|
168
|
+
if dpq == 1:
|
|
167
169
|
cols.append(x)
|
|
168
170
|
cols.append(phi)
|
|
169
171
|
|
|
@@ -179,9 +181,9 @@ def _inner_ls(x, f, poles, p=1, pade_reg=0.0):
|
|
|
179
181
|
# theta = numpy.linalg.solve(ATA, ATf)
|
|
180
182
|
|
|
181
183
|
# figure out how many elements to skip
|
|
182
|
-
if
|
|
184
|
+
if dpq == 1:
|
|
183
185
|
skip = 2 # skip c and D
|
|
184
|
-
elif
|
|
186
|
+
elif dpq == 0:
|
|
185
187
|
skip = 1 # skip c only
|
|
186
188
|
else:
|
|
187
189
|
skip = 0 # all entries are residues
|
|
@@ -198,11 +200,11 @@ def _inner_ls(x, f, poles, p=1, pade_reg=0.0):
|
|
|
198
200
|
else:
|
|
199
201
|
theta, *_ = lstsq(A, f, rcond=None)
|
|
200
202
|
|
|
201
|
-
if
|
|
203
|
+
if dpq == -1:
|
|
202
204
|
c, D, resid = 0.0, 0.0, theta
|
|
203
|
-
elif
|
|
205
|
+
elif dpq == 0:
|
|
204
206
|
c, D, resid = theta[0], 0.0, theta[1:]
|
|
205
|
-
else: #
|
|
207
|
+
else: # dpq == 1
|
|
206
208
|
c, D, resid = theta[0], theta[1], theta[2:]
|
|
207
209
|
|
|
208
210
|
return c, D, resid
|
|
@@ -240,7 +242,7 @@ def _eval_rational(z, c, D, poles, resid):
|
|
|
240
242
|
# fit pade
|
|
241
243
|
# ========
|
|
242
244
|
|
|
243
|
-
def fit_pade(x, f, lam_m, lam_p, p=
|
|
245
|
+
def fit_pade(x, f, lam_m, lam_p, p=2, q=2, odd_side='left', pade_reg=0.0,
|
|
244
246
|
safety=1.0, max_outer=40, xtol=1e-12, ftol=1e-12, optimizer='ls',
|
|
245
247
|
verbose=0):
|
|
246
248
|
"""
|
|
@@ -251,16 +253,19 @@ def fit_pade(x, f, lam_m, lam_p, p=1, q=2, odd_side='left', pade_reg=0.0,
|
|
|
251
253
|
if not (odd_side in ['left', 'right']):
|
|
252
254
|
raise ValueError('"odd_side" can only be "left" or "right".')
|
|
253
255
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
+
# Difference between the degrees of numerator and denominator
|
|
257
|
+
dpq = p - q
|
|
258
|
+
if not (dpq in [-1, 0, 1]):
|
|
259
|
+
raise ValueError('"pade_p" and "pade_q" can only differ by "+1", ' +
|
|
260
|
+
'"0", or "-1".')
|
|
256
261
|
|
|
257
262
|
x = numpy.asarray(x, float)
|
|
258
263
|
f = numpy.asarray(f, float)
|
|
259
264
|
|
|
260
265
|
poles0 = _default_poles(q, lam_m, lam_p, safety=safety, odd_side=odd_side)
|
|
261
|
-
if q == 0 and
|
|
266
|
+
if q == 0 and dpq <= 0:
|
|
262
267
|
# c, D, resid = _inner_ls(x, f, poles0, pade_reg=pade_reg) # TEST
|
|
263
|
-
c, D, resid = _inner_ls(x, f, poles0,
|
|
268
|
+
c, D, resid = _inner_ls(x, f, poles0, dpq, pade_reg=pade_reg)
|
|
264
269
|
pade_sol = {
|
|
265
270
|
'c': c, 'D': D, 'poles': poles0, 'resid': resid,
|
|
266
271
|
'outer_iters': 0
|
|
@@ -274,10 +279,10 @@ def fit_pade(x, f, lam_m, lam_p, p=1, q=2, odd_side='left', pade_reg=0.0,
|
|
|
274
279
|
# residual
|
|
275
280
|
# --------
|
|
276
281
|
|
|
277
|
-
def residual(s,
|
|
282
|
+
def residual(s, dpq=dpq):
|
|
278
283
|
poles = _decode_poles(s, lam_m, lam_p)
|
|
279
284
|
# c, D, resid = _inner_ls(x, f, poles, pade_reg=pade_reg) # TEST
|
|
280
|
-
c, D, resid = _inner_ls(x, f, poles,
|
|
285
|
+
c, D, resid = _inner_ls(x, f, poles, dpq, pade_reg=pade_reg)
|
|
281
286
|
return _eval_rational(x, c, D, poles, resid) - f
|
|
282
287
|
|
|
283
288
|
# ----------------
|
|
@@ -324,7 +329,7 @@ def fit_pade(x, f, lam_m, lam_p, p=1, q=2, odd_side='left', pade_reg=0.0,
|
|
|
324
329
|
|
|
325
330
|
poles = _decode_poles(res.x, lam_m, lam_p)
|
|
326
331
|
# c, D, resid = _inner_ls(x, f, poles, pade_reg=pade_reg) # TEST
|
|
327
|
-
c, D, resid = _inner_ls(x, f, poles,
|
|
332
|
+
c, D, resid = _inner_ls(x, f, poles, dpq, pade_reg=pade_reg)
|
|
328
333
|
|
|
329
334
|
pade_sol = {
|
|
330
335
|
'c': c, 'D': D, 'poles': poles, 'resid': resid,
|
|
@@ -364,127 +369,3 @@ def eval_pade(z, pade_sol):
|
|
|
364
369
|
for bj, rj in zip(poles, resid):
|
|
365
370
|
out += rj/(z - bj) # each is an (N,) op, no N*q temp
|
|
366
371
|
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
|
freealg/_sample.py
CHANGED
|
@@ -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
|
|
freealg/_util.py
CHANGED
|
@@ -126,6 +126,38 @@ 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
|
+
References
|
|
131
|
+
----------
|
|
132
|
+
|
|
133
|
+
.. [1] `R-package documentation for Beta kernel
|
|
134
|
+
<https://search.r-project.org/CRAN/refmans/DELTD/html/Beta.html>`__
|
|
135
|
+
|
|
136
|
+
.. [2] Chen, S. X. (1999). Beta Kernel estimators for density functions.
|
|
137
|
+
*Computational Statistics and Data Analysis* 31 p. 131--145.
|
|
138
|
+
|
|
139
|
+
Notes
|
|
140
|
+
-----
|
|
141
|
+
|
|
142
|
+
In Beta kernel density estimation, the shape parameters :math:`a` and
|
|
143
|
+
:math:`b` of the :math:`\\mathrm{Beta}(a, b)` distribution are computed
|
|
144
|
+
for each data point :math:`u` as:
|
|
145
|
+
|
|
146
|
+
.. math::
|
|
147
|
+
|
|
148
|
+
a = (u / h) + 1.0
|
|
149
|
+
b = ((1.0 - u) / h) + 1.0
|
|
150
|
+
|
|
151
|
+
This is a standard way of using Beta kernel (see R-package documentation
|
|
152
|
+
[1]_). These equations are derived from *moment matching* method, where
|
|
153
|
+
|
|
154
|
+
.. math::
|
|
155
|
+
|
|
156
|
+
\\mathrm{Mean}(\\mathrm{Beta}(a,b)) = u
|
|
157
|
+
\\mathrm{Var}(\\mathrm{Beta}(a,b)) = (1-u) u h
|
|
158
|
+
|
|
159
|
+
Solving these two equations for :math:`a` and :math:`b` yields the
|
|
160
|
+
relations above. See [2]_ (page 134).
|
|
129
161
|
"""
|
|
130
162
|
|
|
131
163
|
if kernel == 'gaussian':
|
|
@@ -141,28 +173,39 @@ def kde(eig, xs, lam_m, lam_p, h, kernel='beta', plot=False):
|
|
|
141
173
|
|
|
142
174
|
span = lam_p - lam_m
|
|
143
175
|
if span <= 0:
|
|
144
|
-
raise ValueError("lam_p must be larger than lam_m")
|
|
176
|
+
raise ValueError('"lam_p" must be larger than "lam_m".')
|
|
145
177
|
|
|
146
178
|
# map samples and grid to [0, 1]
|
|
147
179
|
u = (eig - lam_m) / span
|
|
148
180
|
t = (xs - lam_m) / span
|
|
149
181
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
u = u[
|
|
182
|
+
# keep only samples strictly inside (0,1)
|
|
183
|
+
if (u.min() < 0) or (u.max() > 1):
|
|
184
|
+
u = u[(u > 0) & (u < 1)]
|
|
153
185
|
|
|
154
|
-
|
|
155
|
-
n
|
|
186
|
+
n = u.size
|
|
187
|
+
if n == 0:
|
|
188
|
+
return numpy.zeros_like(xs, dtype=float)
|
|
156
189
|
|
|
157
|
-
#
|
|
190
|
+
# Shape parameters "a" and "b" or the kernel Beta(a, b), which is
|
|
191
|
+
# computed for each data point "u" (see notes above). These are
|
|
192
|
+
# vectorized.
|
|
193
|
+
a = (u / h) + 1.0
|
|
194
|
+
b = ((1.0 - u) / h) + 1.0
|
|
195
|
+
|
|
196
|
+
# # tiny positive number to keep shape parameters > 0
|
|
158
197
|
eps = 1e-6
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
198
|
+
a = numpy.clip(a, eps, None)
|
|
199
|
+
b = numpy.clip(b, eps, None)
|
|
200
|
+
|
|
201
|
+
# Beta kernel
|
|
202
|
+
pdf_matrix = beta.pdf(t[None, :], a[:, None], b[:, None])
|
|
203
|
+
|
|
204
|
+
# Average and re-normalize back to x variable
|
|
205
|
+
pdf = pdf_matrix.sum(axis=0) / (n * span)
|
|
163
206
|
|
|
164
|
-
|
|
165
|
-
pdf[(t < 0) | (t > 1)] = 0.0
|
|
207
|
+
# Exact zeros outside [lam_m, lam_p]
|
|
208
|
+
pdf[(t < 0) | (t > 1)] = 0.0
|
|
166
209
|
|
|
167
210
|
else:
|
|
168
211
|
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
|
|
|
@@ -283,8 +283,8 @@ class MarchenkoPastur(object):
|
|
|
283
283
|
if numpy.any(not_mask):
|
|
284
284
|
|
|
285
285
|
sign = -1 if alt_branch else 1
|
|
286
|
-
A = self.lam * sigma**2 * z
|
|
287
|
-
B = z - sigma**2 * (1 - self.lam)
|
|
286
|
+
A = self.lam * sigma**2 * z[not_mask]
|
|
287
|
+
B = z[not_mask] - sigma**2 * (1 - self.lam)
|
|
288
288
|
D = B**2 - 4 * A
|
|
289
289
|
sqrtD = numpy.sqrt(D)
|
|
290
290
|
m1 = (-B + sqrtD) / (2 * A)
|
|
@@ -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
|
|
freealg/distributions/_wigner.py
CHANGED
|
@@ -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
|
|
freealg/freeform.py
CHANGED
|
@@ -74,16 +74,6 @@ class FreeForm(object):
|
|
|
74
74
|
Parameters for the :func:`supp` function can also be prescribed
|
|
75
75
|
here when ``support=None``.
|
|
76
76
|
|
|
77
|
-
Notes
|
|
78
|
-
-----
|
|
79
|
-
|
|
80
|
-
TBD
|
|
81
|
-
|
|
82
|
-
References
|
|
83
|
-
----------
|
|
84
|
-
|
|
85
|
-
.. [1] Reference.
|
|
86
|
-
|
|
87
77
|
Attributes
|
|
88
78
|
----------
|
|
89
79
|
|
|
@@ -200,7 +190,7 @@ class FreeForm(object):
|
|
|
200
190
|
|
|
201
191
|
def fit(self, method='jacobi', K=10, alpha=0.0, beta=0.0, n_quad=60,
|
|
202
192
|
reg=0.0, projection='gaussian', kernel_bw=0.001, damp=None,
|
|
203
|
-
force=False, continuation='pade', pade_p=
|
|
193
|
+
force=False, continuation='pade', pade_p=1, pade_q=1,
|
|
204
194
|
odd_side='left', pade_reg=0.0, optimizer='ls', plot=False,
|
|
205
195
|
latex=False, save=False):
|
|
206
196
|
"""
|
|
@@ -275,14 +265,15 @@ class FreeForm(object):
|
|
|
275
265
|
* ``'brezinski'``: Brezinski's :math:`\\theta` algorithm
|
|
276
266
|
(`experimental`).
|
|
277
267
|
|
|
278
|
-
pade_p : int, default=
|
|
279
|
-
Degree of polynomial :math:`P(z)` is :math:`
|
|
280
|
-
|
|
268
|
+
pade_p : int, default=1
|
|
269
|
+
Degree of polynomial :math:`P(z)` is :math:`p` where :math:`p` can
|
|
270
|
+
only be ``q-1``, ``q``, or ``q+1``. See notes below. This option
|
|
281
271
|
is applicable if ``continuation='pade'``.
|
|
282
272
|
|
|
283
273
|
pade_q : int, default=1
|
|
284
|
-
Degree of polynomial :math:`Q(z)` is :math:`q
|
|
285
|
-
|
|
274
|
+
Degree of polynomial :math:`Q(z)` is :math:`q` where :math:`q` can
|
|
275
|
+
only be ``p-1``, ``p``, or ``p+1``. See notes below. This option
|
|
276
|
+
is applicable if ``continuation='pade'``.
|
|
286
277
|
|
|
287
278
|
odd_side : {``'left'``, ``'right'``}, default= ``'left'``
|
|
288
279
|
In case of odd number of poles (when :math:`q` is odd), the extra
|
|
@@ -748,16 +739,6 @@ class FreeForm(object):
|
|
|
748
739
|
density
|
|
749
740
|
hilbert
|
|
750
741
|
|
|
751
|
-
Notes
|
|
752
|
-
-----
|
|
753
|
-
|
|
754
|
-
Notes.
|
|
755
|
-
|
|
756
|
-
References
|
|
757
|
-
----------
|
|
758
|
-
|
|
759
|
-
.. [1] tbd
|
|
760
|
-
|
|
761
742
|
Examples
|
|
762
743
|
--------
|
|
763
744
|
|
|
@@ -972,16 +953,6 @@ class FreeForm(object):
|
|
|
972
953
|
density
|
|
973
954
|
stieltjes
|
|
974
955
|
|
|
975
|
-
Notes
|
|
976
|
-
-----
|
|
977
|
-
|
|
978
|
-
Work in progress.
|
|
979
|
-
|
|
980
|
-
References
|
|
981
|
-
----------
|
|
982
|
-
|
|
983
|
-
.. [1] tbd
|
|
984
|
-
|
|
985
956
|
Examples
|
|
986
957
|
--------
|
|
987
958
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: freealg
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.3
|
|
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
|
|
@@ -142,19 +142,17 @@ code, we also welcome feature requests and bug reports.
|
|
|
142
142
|
How to Cite
|
|
143
143
|
===========
|
|
144
144
|
|
|
145
|
-
If you use this work, please cite our `
|
|
145
|
+
If you use this work, please cite our `paper <https://openreview.net/pdf?id=2CeGVUpOd7>`__.
|
|
146
146
|
|
|
147
147
|
.. code::
|
|
148
148
|
|
|
149
|
-
@
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
url={https://arxiv.org/abs/2506.11994},
|
|
157
|
-
journal={arXiv preprint arXiv:2506.11994},
|
|
149
|
+
@inproceedings{
|
|
150
|
+
AMELI-2025,
|
|
151
|
+
title={Spectral Estimation with Free Decompression},
|
|
152
|
+
author={Siavash Ameli and Chris van der Heide and Liam Hodgkinson and Michael W. Mahoney},
|
|
153
|
+
booktitle={The Thirty-ninth Annual Conference on Neural Information Processing Systems},
|
|
154
|
+
year={2025},
|
|
155
|
+
url={https://openreview.net/forum?id=2CeGVUpOd7}
|
|
158
156
|
}
|
|
159
157
|
|
|
160
158
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
freealg/__init__.py,sha256=muuYCvlsXjuX1W67YGFca9nFxprFsALLyB3CrJpXFnY,728
|
|
2
|
+
freealg/__version__.py,sha256=zYiFHqR7JwbvdK9dvKrh-RTNfUqjHUwC4CTcFAPVYLc,22
|
|
3
|
+
freealg/_chebyshev.py,sha256=zkyVA8NLf7uUKlJdLz4ijd_SurdsqUgkA5nHGWSybaE,6916
|
|
4
|
+
freealg/_damp.py,sha256=k2vtBtWOxQBf4qXaWu_En81lQBXbEO4QbxxWpvuVhdE,1802
|
|
5
|
+
freealg/_decompress.py,sha256=_i37IToZ6oN9DdLXOM8r4y92EbazzcylhcnWwUOpaj0,32108
|
|
6
|
+
freealg/_jacobi.py,sha256=z0X6Ws_BEo_h8EQBzDNHGFhLF9F2PUmnGeBVs0bNL7w,10709
|
|
7
|
+
freealg/_linalg.py,sha256=0BzJNTXiY1VH3OKrCFgbE0QHLgRoKyiILsBWtnygFGc,13141
|
|
8
|
+
freealg/_pade.py,sha256=_y89r7rVc2E5lgiN_ZjnxzW2IaVevWwpq5ISor2NVOo,10310
|
|
9
|
+
freealg/_plot_util.py,sha256=GKvmc1wjVGeqoomrULPbzBEt6P86FdoR2idBLYh5EDY,20068
|
|
10
|
+
freealg/_sample.py,sha256=rhfd_83TCTvvJh8cG8TzEYO4OR8VbtND2YCNtWEhMa8,3205
|
|
11
|
+
freealg/_series.py,sha256=33LLCUe4svmV0eWyzhP_XClfDzccQHTW9WBJlYlLfHY,11475
|
|
12
|
+
freealg/_support.py,sha256=nxDa2OFlWBgjD0_1qoSMWG7kub6-GIuxIA04n5bdaYw,6614
|
|
13
|
+
freealg/_util.py,sha256=fcs18RuWsmWvpSiwvSLFBYNiTLvYkD6LwjeuIGnyV7E,8251
|
|
14
|
+
freealg/freeform.py,sha256=GMlgCqrM-3tb_QZrZszVxC4pGUvQbYtOnbJBkc2PHQg,43544
|
|
15
|
+
freealg/distributions/__init__.py,sha256=t_yZyEkW_W_tSV9IvgYXtVASxD2BEdiNVXcV2ebMy8M,579
|
|
16
|
+
freealg/distributions/_kesten_mckay.py,sha256=FB7UtMxkn3Olg6XmjUKrh5njhdttMo6HwW78fDBTDJk,20078
|
|
17
|
+
freealg/distributions/_marchenko_pastur.py,sha256=PtmOV9npUcbI1HgjeH8mjGlbYYCdrlNtLuQegO2r4NE,17191
|
|
18
|
+
freealg/distributions/_meixner.py,sha256=-gDYBShAXxtfe3vY4-2bSvp7QM8uU9eeDC9v_YVNxrk,17579
|
|
19
|
+
freealg/distributions/_wachter.py,sha256=Kv1qQF1MOFCli5-IDT5lZYRhqRXh4xCugEq7KSYbE4g,17079
|
|
20
|
+
freealg/distributions/_wigner.py,sha256=FBGhSijbex5oFztnV6laEGAiSWGdn1z9g9lSfhgQ_sQ,16058
|
|
21
|
+
freealg-0.6.3.dist-info/licenses/AUTHORS.txt,sha256=0b67Nz4_JgIzUupHJTAZxu5QdSUM_HRM_X_w4xCb17o,30
|
|
22
|
+
freealg-0.6.3.dist-info/licenses/LICENSE.txt,sha256=J-EEYEtxb3VVf_Bn1TYfWnpY5lMFIM15iLDDcnaDTPA,1443
|
|
23
|
+
freealg-0.6.3.dist-info/METADATA,sha256=FhjParK9WyqcDNCu2OBGe_uVssCT-HZhuM5zm0Yzhyw,5516
|
|
24
|
+
freealg-0.6.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
25
|
+
freealg-0.6.3.dist-info/top_level.txt,sha256=eR2wrgYwDdnnJ9Zf5PruPqe4kQav0GMvRsqct6y00Q8,8
|
|
26
|
+
freealg-0.6.3.dist-info/RECORD,,
|
freealg-0.6.1.dist-info/RECORD
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
freealg/__init__.py,sha256=muuYCvlsXjuX1W67YGFca9nFxprFsALLyB3CrJpXFnY,728
|
|
2
|
-
freealg/__version__.py,sha256=baAcEjLSYFIeNZF51tOMmA_zAMhN8HvKael-UU-Ruec,22
|
|
3
|
-
freealg/_chebyshev.py,sha256=zkyVA8NLf7uUKlJdLz4ijd_SurdsqUgkA5nHGWSybaE,6916
|
|
4
|
-
freealg/_damp.py,sha256=k2vtBtWOxQBf4qXaWu_En81lQBXbEO4QbxxWpvuVhdE,1802
|
|
5
|
-
freealg/_decompress.py,sha256=bFhQx--uptWJ7OjVwEs_tWYT6mLijBKJ9EbrD24Sbl0,32199
|
|
6
|
-
freealg/_jacobi.py,sha256=z0X6Ws_BEo_h8EQBzDNHGFhLF9F2PUmnGeBVs0bNL7w,10709
|
|
7
|
-
freealg/_linalg.py,sha256=0BzJNTXiY1VH3OKrCFgbE0QHLgRoKyiILsBWtnygFGc,13141
|
|
8
|
-
freealg/_pade.py,sha256=BthDHScn2lILTTU2hlGNP-8YqddU3Uyxe0n0FkprwDs,13645
|
|
9
|
-
freealg/_plot_util.py,sha256=GKvmc1wjVGeqoomrULPbzBEt6P86FdoR2idBLYh5EDY,20068
|
|
10
|
-
freealg/_sample.py,sha256=yLJSGlq27j8tA-kDntRwfHIUU8Oo2IOmOTxS8yTRGRU,3075
|
|
11
|
-
freealg/_series.py,sha256=33LLCUe4svmV0eWyzhP_XClfDzccQHTW9WBJlYlLfHY,11475
|
|
12
|
-
freealg/_support.py,sha256=nxDa2OFlWBgjD0_1qoSMWG7kub6-GIuxIA04n5bdaYw,6614
|
|
13
|
-
freealg/_util.py,sha256=NaEhcOxbue44l_xAhefnNZYTy3pBBGBFyk9HdaRjQKo,6899
|
|
14
|
-
freealg/freeform.py,sha256=FBC9ab-3JWaQibMAM4LlbYPYvELaUDReJWCwAG0Fwwg,43779
|
|
15
|
-
freealg/distributions/__init__.py,sha256=t_yZyEkW_W_tSV9IvgYXtVASxD2BEdiNVXcV2ebMy8M,579
|
|
16
|
-
freealg/distributions/_kesten_mckay.py,sha256=BM_U8cX3eRstbAA4IZRK4qA_6S9zcogaXeuHyKXen14,19897
|
|
17
|
-
freealg/distributions/_marchenko_pastur.py,sha256=xwk40GwpLvEm9--FN7-T2NWtHTkfzcvOS4tFyrm71ww,16990
|
|
18
|
-
freealg/distributions/_meixner.py,sha256=8zmDnoCp-GOMnd6T2rKLQaMfn6uFmSnd-i5PLlfGOUM,17526
|
|
19
|
-
freealg/distributions/_wachter.py,sha256=d601xAaFSVGeK13SSDavjsJ5a-MJnI2mgzWiplX0Quk,16898
|
|
20
|
-
freealg/distributions/_wigner.py,sha256=w8OlZL9pSfGnXVSSB6A4KBiImr0Zz4iH2PDLCHFfpaY,15877
|
|
21
|
-
freealg-0.6.1.dist-info/licenses/AUTHORS.txt,sha256=0b67Nz4_JgIzUupHJTAZxu5QdSUM_HRM_X_w4xCb17o,30
|
|
22
|
-
freealg-0.6.1.dist-info/licenses/LICENSE.txt,sha256=J-EEYEtxb3VVf_Bn1TYfWnpY5lMFIM15iLDDcnaDTPA,1443
|
|
23
|
-
freealg-0.6.1.dist-info/METADATA,sha256=0iyTPrQe2ZBGlPQr9dAOsunwRx_KS6dRFSaz__Uq2oI,5530
|
|
24
|
-
freealg-0.6.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
25
|
-
freealg-0.6.1.dist-info/top_level.txt,sha256=eR2wrgYwDdnnJ9Zf5PruPqe4kQav0GMvRsqct6y00Q8,8
|
|
26
|
-
freealg-0.6.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|