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 CHANGED
@@ -1 +1 @@
1
- __version__ = "0.6.1"
1
+ __version__ = "0.6.3"
freealg/_decompress.py CHANGED
@@ -855,16 +855,6 @@ def decompress(freeform, alpha, x, roots_init=None, method='newton',
855
855
  density
856
856
  stieltjes
857
857
 
858
- Notes
859
- -----
860
-
861
- Work in progress.
862
-
863
- References
864
- ----------
865
-
866
- .. [1] tbd
867
-
868
858
  Examples
869
859
  --------
870
860
 
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, p=1, pade_reg=0.0):
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 p == -1:
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 p >= 0 else []
122
- if p == 1:
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 p == -1:
130
+ if dpq == -1:
129
131
  c = 0.0
130
132
  D = 0.0
131
133
  resid = numpy.empty(0)
132
- elif p == 0:
134
+ elif dpq == 0:
133
135
  c = theta[0]
134
136
  D = 0.0
135
137
  resid = numpy.empty(0)
136
- else: # p == 1
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 p >= 0 else []
146
- # if p == 1:
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 p == -1:
153
+ # if dpq == -1:
152
154
  # c = 0.0
153
155
  # D = 0.0
154
156
  # resid = theta
155
- # elif p == 0:
157
+ # elif dpq == 0:
156
158
  # c = theta[0]
157
159
  # D = 0.0
158
160
  # resid = theta[1:]
159
- # else: # p == 1
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 p >= 0 else []
166
- if p == 1:
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 p == 1:
184
+ if dpq == 1:
183
185
  skip = 2 # skip c and D
184
- elif p == 0:
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 p == -1:
203
+ if dpq == -1:
202
204
  c, D, resid = 0.0, 0.0, theta
203
- elif p == 0:
205
+ elif dpq == 0:
204
206
  c, D, resid = theta[0], 0.0, theta[1:]
205
- else: # p == 1
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=1, q=2, odd_side='left', pade_reg=0.0,
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
- if not (p in [-1, 0, 1]):
255
- raise ValueError('"pade_p" can only be -1, 0, or 1.')
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 p <= 0:
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, p, pade_reg=pade_reg)
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, p=p):
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, p, pade_reg=pade_reg)
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, p, pade_reg=pade_reg)
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
- engine = qmc.Halton(d=1, rng=rng)
118
- u = engine.random(num_pts)
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
- if u.min() < 0 or u.max() > 1:
151
- mask = (u > 0) & (u < 1)
152
- u = u[mask]
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
- pdf = numpy.zeros_like(xs, dtype=float)
155
- n = len(u)
186
+ n = u.size
187
+ if n == 0:
188
+ return numpy.zeros_like(xs, dtype=float)
156
189
 
157
- # tiny positive number to keep shape parameters > 0
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
- for ui in u:
160
- a = max(ui / h + 1.0, eps)
161
- b = max((1.0 - ui) / h + 1.0, eps)
162
- pdf += beta.pdf(t, a, b)
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
- pdf /= n * span # renormalise
165
- pdf[(t < 0) | (t > 1)] = 0.0 # exact zeros outside
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
- engine = qmc.Halton(d=1, rng=rng)
531
- u = engine.random(size)
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
- engine = qmc.Halton(d=1, rng=rng)
538
- u = engine.random(size)
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] = (1 - self.c)*(x[mask] - self.a)**2
188
- denom[mask] += self.a * (2 - self.c)*(x[mask] - self.a)
189
- denom[mask] += self.a**2 + self.b * self.c**2
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 = ((self.c - 2.0) * x - self.a * self.c) / 2.0
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 = ((1.0 - self.c) * x**2 + self.a * self.c * x +
270
- self.b * self.c**2) / 4.0
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
- self.b * self.c**2) / 4.0
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 = (-B + sqrtD) / (2 * A)
316
- m2 = (-B - sqrtD) / (2 * A)
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
- engine = qmc.Halton(d=1, rng=rng)
563
- u = engine.random(size)
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
- engine = qmc.Halton(d=1, rng=rng)
538
- u = engine.random(size)
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
- engine = qmc.Halton(d=1, rng=rng)
515
- u = engine.random(size)
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=0, pade_q=1,
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=0
279
- Degree of polynomial :math:`P(z)` is :math:`q+p` where :math:`p`
280
- can only be ``-1``, ``0``, or ``1``. See notes below. This option
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`. See notes below.
285
- This option is applicable if ``continuation='pade'``.
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.1
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 `arXiv paper <https://arxiv.org/abs/2506.11994>`__.
145
+ If you use this work, please cite our `paper <https://openreview.net/pdf?id=2CeGVUpOd7>`__.
146
146
 
147
147
  .. code::
148
148
 
149
- @article{spectral2025,
150
- title={Spectral Estimation with Free Decompression},
151
- author={Siavash Ameli and Chris van der Heide and Liam Hodgkinson and Michael W. Mahoney},
152
- year={2025},
153
- eprint={2506.11994},
154
- archivePrefix={arXiv},
155
- primaryClass={stat.ML},
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,,
@@ -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,,