freealg 0.1.11__py3-none-any.whl → 0.7.12__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.
Files changed (59) hide show
  1. freealg/__init__.py +8 -2
  2. freealg/__version__.py +1 -1
  3. freealg/_algebraic_form/__init__.py +12 -0
  4. freealg/_algebraic_form/_branch_points.py +288 -0
  5. freealg/_algebraic_form/_constraints.py +139 -0
  6. freealg/_algebraic_form/_continuation_algebraic.py +706 -0
  7. freealg/_algebraic_form/_decompress.py +641 -0
  8. freealg/_algebraic_form/_decompress2.py +204 -0
  9. freealg/_algebraic_form/_edge.py +330 -0
  10. freealg/_algebraic_form/_homotopy.py +323 -0
  11. freealg/_algebraic_form/_moments.py +448 -0
  12. freealg/_algebraic_form/_sheets_util.py +145 -0
  13. freealg/_algebraic_form/_support.py +309 -0
  14. freealg/_algebraic_form/algebraic_form.py +1232 -0
  15. freealg/_free_form/__init__.py +16 -0
  16. freealg/{_chebyshev.py → _free_form/_chebyshev.py} +75 -43
  17. freealg/_free_form/_decompress.py +993 -0
  18. freealg/_free_form/_density_util.py +243 -0
  19. freealg/_free_form/_jacobi.py +359 -0
  20. freealg/_free_form/_linalg.py +508 -0
  21. freealg/{_pade.py → _free_form/_pade.py} +42 -208
  22. freealg/{_plot_util.py → _free_form/_plot_util.py} +37 -22
  23. freealg/{_sample.py → _free_form/_sample.py} +58 -22
  24. freealg/_free_form/_series.py +454 -0
  25. freealg/_free_form/_support.py +214 -0
  26. freealg/_free_form/free_form.py +1362 -0
  27. freealg/_geometric_form/__init__.py +13 -0
  28. freealg/_geometric_form/_continuation_genus0.py +175 -0
  29. freealg/_geometric_form/_continuation_genus1.py +275 -0
  30. freealg/_geometric_form/_elliptic_functions.py +174 -0
  31. freealg/_geometric_form/_sphere_maps.py +63 -0
  32. freealg/_geometric_form/_torus_maps.py +118 -0
  33. freealg/_geometric_form/geometric_form.py +1094 -0
  34. freealg/_util.py +56 -110
  35. freealg/distributions/__init__.py +7 -1
  36. freealg/distributions/_chiral_block.py +494 -0
  37. freealg/distributions/_deformed_marchenko_pastur.py +726 -0
  38. freealg/distributions/_deformed_wigner.py +386 -0
  39. freealg/distributions/_kesten_mckay.py +29 -15
  40. freealg/distributions/_marchenko_pastur.py +224 -95
  41. freealg/distributions/_meixner.py +47 -37
  42. freealg/distributions/_wachter.py +29 -17
  43. freealg/distributions/_wigner.py +27 -14
  44. freealg/visualization/__init__.py +12 -0
  45. freealg/visualization/_glue_util.py +32 -0
  46. freealg/visualization/_rgb_hsv.py +125 -0
  47. freealg-0.7.12.dist-info/METADATA +172 -0
  48. freealg-0.7.12.dist-info/RECORD +53 -0
  49. {freealg-0.1.11.dist-info → freealg-0.7.12.dist-info}/WHEEL +1 -1
  50. freealg/_decompress.py +0 -180
  51. freealg/_jacobi.py +0 -218
  52. freealg/_support.py +0 -85
  53. freealg/freeform.py +0 -967
  54. freealg-0.1.11.dist-info/METADATA +0 -140
  55. freealg-0.1.11.dist-info/RECORD +0 -24
  56. /freealg/{_damp.py → _free_form/_damp.py} +0 -0
  57. {freealg-0.1.11.dist-info → freealg-0.7.12.dist-info}/licenses/AUTHORS.txt +0 -0
  58. {freealg-0.1.11.dist-info → freealg-0.7.12.dist-info}/licenses/LICENSE.txt +0 -0
  59. {freealg-0.1.11.dist-info → freealg-0.7.12.dist-info}/top_level.txt +0 -0
@@ -13,8 +13,9 @@
13
13
 
14
14
  import numpy
15
15
  from scipy.interpolate import interp1d
16
- from .._plot_util import plot_density, plot_hilbert, plot_stieltjes, \
17
- plot_stieltjes_on_disk, plot_samples
16
+ from .._free_form._plot_util import plot_density, plot_hilbert, \
17
+ plot_stieltjes, plot_stieltjes_on_disk, plot_samples
18
+ from ..visualization import glue_branches
18
19
 
19
20
  try:
20
21
  from scipy.integrate import cumtrapz
@@ -94,21 +95,26 @@ class MarchenkoPastur(object):
94
95
  # init
95
96
  # ====
96
97
 
97
- def __init__(self, lam):
98
+ def __init__(self, lam, sigma=1.0):
98
99
  """
99
100
  Initialization.
100
101
  """
101
102
 
102
103
  self.lam = lam
103
- self.lam_p = (1 + numpy.sqrt(self.lam))**2
104
- self.lam_m = (1 - numpy.sqrt(self.lam))**2
105
- self.support = (self.lam_m, self.lam_p)
104
+ self.sigma = sigma
105
+
106
+ # self.lam_p = (1 + numpy.sqrt(self.lam))**2
107
+ # self.lam_m = (1 - numpy.sqrt(self.lam))**2
108
+ self.lam_p = sigma**2 * (1.0 + numpy.sqrt(lam))**2
109
+ self.lam_m = sigma**2 * (1.0 - numpy.sqrt(lam))**2
110
+
111
+ self.supp = (self.lam_m, self.lam_p)
106
112
 
107
113
  # =======
108
114
  # density
109
115
  # =======
110
116
 
111
- def density(self, x=None, plot=False, latex=False, save=False):
117
+ def density(self, x=None, plot=False, latex=False, save=False, eig=None):
112
118
  """
113
119
  Density of distribution.
114
120
 
@@ -117,7 +123,7 @@ class MarchenkoPastur(object):
117
123
 
118
124
  x : numpy.array, default=None
119
125
  The locations where density is evaluated at. If `None`, an interval
120
- slightly larger than the support interval of the spectral density
126
+ slightly larger than the supp interval of the spectral density
121
127
  is used.
122
128
 
123
129
  rho : numpy.array, default=None
@@ -132,9 +138,13 @@ class MarchenkoPastur(object):
132
138
 
133
139
  save : bool, default=False
134
140
  If not `False`, the plot is saved. If a string is given, it is
135
- assumed to the save filename (with the file extension). This option
141
+ assumed to the save filename (with the file extension). This option
136
142
  is relevant only if ``plot=True``.
137
143
 
144
+ eig : numpy.array, default=None
145
+ A collection of eigenvalues to compare to via histogram. This
146
+ option is relevant only if ``plot=True``.
147
+
138
148
  Returns
139
149
  -------
140
150
 
@@ -164,17 +174,42 @@ class MarchenkoPastur(object):
164
174
  x_max = numpy.ceil(center + radius * scale)
165
175
  x = numpy.linspace(x_min, x_max, 500)
166
176
 
177
+ # Unpack parameters
178
+ lam = self.lam
179
+ lam_p = self.lam_p
180
+ lam_m = self.lam_m
181
+ sigma = self.sigma
182
+
167
183
  rho = numpy.zeros_like(x)
168
- mask = numpy.logical_and(x >= self.lam_m, x <= self.lam_p)
184
+ # mask = numpy.logical_and(x >= self.lam_m, x <= self.lam_p)
185
+ mask = (x > lam_m) & (x < lam_p)
186
+
187
+ # rho[mask] = (1.0 / (2.0 * numpy.pi * x[mask] * self.lam)) * \
188
+ # numpy.sqrt((self.lam_p - x[mask]) * (x[mask] - self.lam_m))
169
189
 
170
- rho[mask] = (1.0 / (2.0 * numpy.pi * x[mask] * self.lam)) * \
171
- numpy.sqrt((self.lam_p - x[mask]) * (x[mask] - self.lam_m))
190
+ rho[mask] = numpy.sqrt((lam_p - x[mask]) * (x[mask] - lam_m)) / \
191
+ (lam * x[mask] * 2 * numpy.pi * sigma**2)
172
192
 
173
193
  if plot:
174
- plot_density(x, rho, label='', latex=latex, save=save)
194
+ if eig is not None:
195
+ label = 'Theoretical'
196
+ else:
197
+ label = ''
198
+ plot_density(x, rho, label=label, latex=latex, save=save, eig=eig)
175
199
 
176
200
  return rho
177
201
 
202
+ # =======
203
+ # support
204
+ # =======
205
+
206
+ def support(self):
207
+ """
208
+ supp
209
+ """
210
+
211
+ return [self.supp]
212
+
178
213
  # =======
179
214
  # hilbert
180
215
  # =======
@@ -188,7 +223,7 @@ class MarchenkoPastur(object):
188
223
 
189
224
  x : numpy.array, default=None
190
225
  The locations where Hilbert transform is evaluated at. If `None`,
191
- an interval slightly larger than the support interval of the
226
+ an interval slightly larger than the supp interval of the
192
227
  spectral density is used.
193
228
 
194
229
  plot : bool, default=False
@@ -249,7 +284,7 @@ class MarchenkoPastur(object):
249
284
  hilb = -hilb
250
285
 
251
286
  if plot:
252
- plot_hilbert(x, hilb, support=self.support, latex=latex, save=save)
287
+ plot_hilbert(x, hilb, support=self.supp, latex=latex, save=save)
253
288
 
254
289
  return hilb
255
290
 
@@ -257,59 +292,126 @@ class MarchenkoPastur(object):
257
292
  # m mp numeric vectorized
258
293
  # =======================
259
294
 
260
- def _m_mp_numeric_vectorized(self, z, alt_branch=False, tol=1e-8):
295
+ # def _m_mp_numeric_vectorized(self, z, alt_branch=False, tol=1e-8):
296
+ # """
297
+ # Stieltjes transform (principal or secondary branch)
298
+ # for Marchenko-Pastur distribution on upper half-plane.
299
+ # """
300
+ #
301
+ # sigma = 1.0
302
+ # m = numpy.empty_like(z, dtype=complex)
303
+ #
304
+ # # When z is too small, do not use quadratic form.
305
+ # mask = numpy.abs(z) < tol
306
+ # m[mask] = 1 / (sigma**2 * (1 - self.lam))
307
+ #
308
+ # # Use quadratic form
309
+ # not_mask = ~mask
310
+ # if numpy.any(not_mask):
311
+ #
312
+ # sign = -1 if alt_branch else 1
313
+ # A = self.lam * sigma**2 * z[not_mask]
314
+ # B = z[not_mask] - sigma**2 * (1 - self.lam)
315
+ # D = B**2 - 4 * A
316
+ # sqrtD = numpy.sqrt(D)
317
+ # m1 = (-B + sqrtD) / (2 * A)
318
+ # m2 = (-B - sqrtD) / (2 * A)
319
+ #
320
+ # # pick correct branch only for non-masked entries
321
+ # upper = z[not_mask].imag >= 0
322
+ # branch = numpy.empty_like(m1)
323
+ # branch[upper] = numpy.where(sign*m1[upper].imag > 0, m1[upper],
324
+ # m2[upper])
325
+ # branch[~upper] = numpy.where(sign*m1[~upper].imag < 0,
326
+ # m1[~upper], m2[~upper])
327
+ # m[not_mask] = branch
328
+ #
329
+ # return m
330
+
331
+ # =============
332
+ # sqrt pos imag
333
+ # =============
334
+
335
+ def _sqrt_pos_imag(self, z):
261
336
  """
262
- Stieltjes transform (principal or secondary branch)
263
- for Marchenko–Pastur distribution on upper half-plane.
337
+ Square root on a branch cut with always positive imaginary part.
264
338
  """
265
339
 
266
- sigma = 1.0
267
- m = numpy.empty_like(z, dtype=complex)
340
+ sq = numpy.sqrt(z)
341
+ sq = numpy.where(sq.imag < 0, -sq, sq)
268
342
 
269
- # When z is too small, do not use quadratic form.
270
- mask = numpy.abs(z) < tol
271
- m[mask] = 1 / (sigma**2 * (1 - self.lam))
272
-
273
- # Use quadratic form
274
- not_mask = ~mask
275
- if numpy.any(not_mask):
276
-
277
- sign = -1 if alt_branch else 1
278
- A = self.lam * sigma**2 * z
279
- B = z - sigma**2 * (1 - self.lam)
280
- D = B**2 - 4 * A
281
- sqrtD = numpy.sqrt(D)
282
- m1 = (-B + sqrtD) / (2 * A)
283
- m2 = (-B - sqrtD) / (2 * A)
284
-
285
- # pick correct branch only for non‑masked entries
286
- upper = z[not_mask].imag >= 0
287
- branch = numpy.empty_like(m1)
288
- branch[upper] = numpy.where(sign*m1[upper].imag > 0, m1[upper],
289
- m2[upper])
290
- branch[~upper] = numpy.where(sign*m1[~upper].imag < 0, m1[~upper],
291
- m2[~upper])
292
- m[not_mask] = branch
293
-
294
- return m
343
+ return sq
295
344
 
296
345
  # ============
297
346
  # m mp reflect
298
347
  # ============
299
348
 
300
- def _m_mp_reflect(self, z, alt_branch=False):
349
+ # def _m_mp_reflect(self, z, alt_branch=False):
350
+ # """
351
+ # Analytic continuation using Schwarz reflection.
352
+ # """
353
+ #
354
+ # mask_p = z.imag >= 0.0
355
+ # mask_n = z.imag < 0.0
356
+ #
357
+ # m = numpy.zeros_like(z)
358
+ #
359
+ # f = self._m_mp_numeric_vectorized
360
+ # m[mask_p] = f(z[mask_p], alt_branch=False)
361
+ # m[mask_n] = f(z[mask_n], alt_branch=alt_branch)
362
+ #
363
+ # return m
364
+
365
+ # ================
366
+ # stieltjes branch
367
+ # ================
368
+
369
+ def _stieltjes_branch(self, z, alt_branch=False, tol=1e-8):
301
370
  """
302
- Analytic continuation using Schwarz reflection.
303
371
  """
304
372
 
305
- mask_p = z.imag >= 0.0
306
- mask_n = z.imag < 0.0
373
+ # Unpack parameters
374
+ lam = self.lam
375
+ sigma = self.sigma
376
+
377
+ z = numpy.asarray(z, dtype=complex)
378
+ m = numpy.empty_like(z, dtype=complex)
379
+
380
+ def _eval_upper(zu):
381
+ mu = numpy.empty_like(zu, dtype=complex)
382
+
383
+ mask = numpy.abs(zu) < tol
384
+ if numpy.any(mask):
385
+ if alt_branch:
386
+ mu[mask] = numpy.inf + 0.0j
387
+ else:
388
+ mu[mask] = 1.0 / (sigma**2 * (1.0 - lam))
389
+
390
+ not_mask = ~mask
391
+ if numpy.any(not_mask):
392
+ sign = -1 if alt_branch else 1
307
393
 
308
- m = numpy.zeros_like(z)
394
+ A = lam * sigma**2 * zu[not_mask]
395
+ B = zu[not_mask] - sigma**2 * (1.0 - lam)
396
+ D = B**2 - 4.0 * A
309
397
 
310
- f = self._m_mp_numeric_vectorized
311
- m[mask_p] = f(z[mask_p], alt_branch=False)
312
- m[mask_n] = f(z[mask_n], alt_branch=alt_branch)
398
+ sqrtD = self._sqrt_pos_imag(D)
399
+
400
+ r1 = (-B + sqrtD) / (2.0 * A)
401
+ r2 = (-B - sqrtD) / (2.0 * A)
402
+
403
+ mu[not_mask] = numpy.where(sign * r1.imag > 0.0, r1, r2)
404
+
405
+ return mu
406
+
407
+ mask_p = numpy.imag(z) >= 0.0
408
+ if numpy.any(mask_p):
409
+ m[mask_p] = _eval_upper(z[mask_p])
410
+
411
+ mask_n = ~mask_p
412
+ if numpy.any(mask_n):
413
+ z_ref = numpy.conjugate(z[mask_n])
414
+ m[mask_n] = numpy.conjugate(_eval_upper(z_ref))
313
415
 
314
416
  return m
315
417
 
@@ -317,8 +419,8 @@ class MarchenkoPastur(object):
317
419
  # stieltjes
318
420
  # =========
319
421
 
320
- def stieltjes(self, x=None, y=None, plot=False, on_disk=False, latex=False,
321
- save=False):
422
+ def stieltjes(self, z=None, x=None, y=None, alt_branch='both', plot=False,
423
+ on_disk=False, latex=False, save=False):
322
424
  """
323
425
  Stieltjes transform of distribution.
324
426
 
@@ -327,13 +429,17 @@ class MarchenkoPastur(object):
327
429
 
328
430
  x : numpy.array, default=None
329
431
  The x axis of the grid where the Stieltjes transform is evaluated.
330
- If `None`, an interval slightly larger than the support interval of
432
+ If `None`, an interval slightly larger than the supp interval of
331
433
  the spectral density is used.
332
434
 
333
435
  y : numpy.array, default=None
334
436
  The y axis of the grid where the Stieltjes transform is evaluated.
335
437
  If `None`, a grid on the interval ``[-1, 1]`` is used.
336
438
 
439
+ alt_branch : {``True``, ``False``, ``'both'``} default=``'both'``
440
+ If `True`, returns non-physical branch. If `False`, returns
441
+ physical branch. If ``'both'``, returns both.
442
+
337
443
  plot : bool, default=False
338
444
  If `True`, Stieltjes transform is plotted.
339
445
 
@@ -399,38 +505,57 @@ class MarchenkoPastur(object):
399
505
  # Cayley transform mapping zeta on D to z on H
400
506
  z_H = 1j * (1 + zeta) / (1 - zeta)
401
507
 
402
- m1_D = self._m_mp_reflect(z_H, alt_branch=False)
403
- m2_D = self._m_mp_reflect(z_H, alt_branch=True)
508
+ # m1_D = self._m_mp_reflect(z_H, alt_branch=False)
509
+ # m2_D = self._m_mp_reflect(z_H, alt_branch=True)
510
+ m1_D = self._stieltjes_branch(z_H, alt_branch=False)
511
+ m2_D = self._stieltjes_branch(z_H, alt_branch=True)
512
+ m12_D = glue_branches(z_H, m1_D, m2_D)
404
513
 
405
- plot_stieltjes_on_disk(r, t, m1_D, m2_D, support=self.support,
514
+ plot_stieltjes_on_disk(r, t, m1_D, m12_D, support=self.supp,
406
515
  latex=latex, save=save)
407
516
 
408
- return m1_D, m2_D
409
-
410
- # Create x if not given
411
- if x is None:
412
- radius = 0.5 * (self.lam_p - self.lam_m)
413
- center = 0.5 * (self.lam_p + self.lam_m)
414
- scale = 2.0
415
- x_min = numpy.floor(2.0 * (center - 2.0 * radius * scale)) / 2.0
416
- x_max = numpy.ceil(2.0 * (center + 2.0 * radius * scale)) / 2.0
417
- x = numpy.linspace(x_min, x_max, 500)
418
-
419
- # Create y if not given
420
- if y is None:
421
- y = numpy.linspace(-1, 1, 400)
422
-
423
- x_grid, y_grid = numpy.meshgrid(x, y)
424
- z = x_grid + 1j * y_grid # shape (Ny, Nx)
425
-
426
- m1 = self._m_mp_reflect(z, alt_branch=False)
427
- m2 = self._m_mp_reflect(z, alt_branch=True)
517
+ if alt_branch == 'both':
518
+ return m1_D, m2_D
519
+ elif alt_branch is True:
520
+ return m2_D
521
+ else:
522
+ return m1_D
523
+
524
+ if z is None:
525
+ # Create x if not given
526
+ if x is None:
527
+ radius = 0.5 * (self.lam_p - self.lam_m)
528
+ center = 0.5 * (self.lam_p + self.lam_m)
529
+ scale = 2.0
530
+ x_min = numpy.floor(
531
+ 2.0 * (center - 2.0 * radius * scale)) / 2.0
532
+ x_max = numpy.ceil(
533
+ 2.0 * (center + 2.0 * radius * scale)) / 2.0
534
+ x = numpy.linspace(x_min, x_max, 500)
535
+
536
+ # Create y if not given
537
+ if y is None:
538
+ y = numpy.linspace(-1, 1, 400)
539
+
540
+ x_grid, y_grid = numpy.meshgrid(x, y)
541
+ z = x_grid + 1j * y_grid # shape (Ny, Nx)
542
+
543
+ # m1 = self._m_mp_reflect(z, alt_branch=False)
544
+ # m2 = self._m_mp_reflect(z, alt_branch=True)
545
+ m1 = self._stieltjes_branch(z, alt_branch=False)
546
+ m2 = self._stieltjes_branch(z, alt_branch=True)
428
547
 
429
548
  if plot:
430
- plot_stieltjes(x, y, m1, m2, support=self.support, latex=latex,
549
+ m12 = glue_branches(z, m1, m2)
550
+ plot_stieltjes(x, y, m1, m12, support=self.supp, latex=latex,
431
551
  save=save)
432
552
 
433
- return m1, m2
553
+ if alt_branch == 'both':
554
+ return m1, m2
555
+ elif alt_branch is True:
556
+ return m2
557
+ else:
558
+ return m1
434
559
 
435
560
  # ======
436
561
  # sample
@@ -448,11 +573,11 @@ class MarchenkoPastur(object):
448
573
  Size of sample.
449
574
 
450
575
  x_min : float, default=None
451
- Minimum of sample values. If `None`, the left edge of the support
576
+ Minimum of sample values. If `None`, the left edge of the supp
452
577
  is used.
453
578
 
454
579
  x_max : float, default=None
455
- Maximum of sample values. If `None`, the right edge of the support
580
+ Maximum of sample values. If `None`, the right edge of the supp
456
581
  is used.
457
582
 
458
583
  method : {``'mc'``, ``'qmc'``}, default= ``'qmc'``
@@ -501,9 +626,6 @@ class MarchenkoPastur(object):
501
626
  :class: custom-dark
502
627
  """
503
628
 
504
- if seed is not None:
505
- numpy.random.seed(seed)
506
-
507
629
  if x_min is None:
508
630
  x_min = self.lam_m
509
631
 
@@ -522,14 +644,23 @@ class MarchenkoPastur(object):
522
644
  inv_cdf = interp1d(cdf, xs, bounds_error=False,
523
645
  fill_value=(x_min, x_max))
524
646
 
647
+ # Random generator
648
+ rng = numpy.random.default_rng(seed)
649
+
525
650
  # Draw from uniform distribution
526
651
  if method == 'mc':
527
- u = numpy.random.rand(size)
652
+ u = rng.random(size)
653
+
528
654
  elif method == 'qmc':
529
- engine = qmc.Halton(d=1)
530
- u = engine.random(size)
655
+ try:
656
+ engine = qmc.Halton(d=1, scramble=True, rng=rng)
657
+ except TypeError:
658
+ # Older scipy versions
659
+ engine = qmc.Halton(d=1, scramble=True, seed=rng)
660
+ u = engine.random(size).ravel()
661
+
531
662
  else:
532
- raise ValueError('"method" is invalid.')
663
+ raise NotImplementedError('"method" is invalid.')
533
664
 
534
665
  # Draw from distribution by mapping from inverse CDF
535
666
  samples = inv_cdf(u).ravel()
@@ -579,14 +710,12 @@ class MarchenkoPastur(object):
579
710
  >>> A = mp.matrix(2000)
580
711
  """
581
712
 
582
- if seed is not None:
583
- numpy.random.seed(seed)
584
-
585
713
  # Parameters
586
714
  m = int(size / self.lam)
587
715
 
588
716
  # Generate random matrix X (n x m) with i.i.d. standard normal entries.
589
- X = numpy.random.randn(size, m)
717
+ rng = numpy.random.default_rng(seed)
718
+ X = rng.standard_normal((size, m))
590
719
 
591
720
  # Form the sample covariance matrix A = (1/m)*XX^T.
592
721
  A = X @ X.T / m
@@ -13,8 +13,8 @@
13
13
 
14
14
  import numpy
15
15
  from scipy.interpolate import interp1d
16
- from .._plot_util import plot_density, plot_hilbert, plot_stieltjes, \
17
- plot_stieltjes_on_disk, plot_samples
16
+ from .._free_form._plot_util import plot_density, plot_hilbert, \
17
+ plot_stieltjes, plot_stieltjes_on_disk, plot_samples
18
18
 
19
19
  try:
20
20
  from scipy.integrate import cumtrapz
@@ -83,7 +83,7 @@ class Meixner(object):
83
83
 
84
84
  .. [1] Saitoh, N. & Yosnida, M. (2001). The infinite divisibility and
85
85
  orthogonal polynomials with a constant recursion formula in free
86
- probability theory. Probab. Math. Statist., 21, 159170.
86
+ probability theory. Probab. Math. Statist., 21, 159-170.
87
87
 
88
88
  Examples
89
89
  --------
@@ -114,7 +114,7 @@ class Meixner(object):
114
114
  # density
115
115
  # =======
116
116
 
117
- def density(self, x=None, plot=False, latex=False, save=False):
117
+ def density(self, x=None, plot=False, latex=False, save=False, eig=None):
118
118
  """
119
119
  Density of distribution.
120
120
 
@@ -141,6 +141,10 @@ class Meixner(object):
141
141
  assumed to the save filename (with the file extension). This option
142
142
  is relevant only if ``plot=True``.
143
143
 
144
+ eig : numpy.array, default=None
145
+ A collection of eigenvalues to compare to via histogram. This
146
+ option is relevant only if ``plot=True``.
147
+
144
148
  Returns
145
149
  -------
146
150
 
@@ -173,22 +177,20 @@ class Meixner(object):
173
177
  rho = numpy.zeros_like(x)
174
178
  mask = numpy.logical_and(x > self.lam_m, x < self.lam_p)
175
179
 
176
- # rho[mask] = \
177
- # numpy.sqrt(4.0 * (1.0 + self.b) - (x[mask] - self.a)**2) / \
178
- # (2.0 * numpy.pi * (self.b * x[mask]**2 + self.a * x[mask] + 1))
179
-
180
180
  numer = numpy.zeros_like(x)
181
181
  denom = numpy.ones_like(x)
182
182
  numer[mask] = self.c * numpy.sqrt(4.0 * self.b - (x[mask] - self.a)**2)
183
- denom[mask] = (1 - self.c)*(x[mask] - self.a)**2
184
- denom[mask] += self.a * (2 - self.c)*(x[mask] - self.a)
185
- denom[mask] += self.a**2 + self.b * self.c**2
186
- denom[mask] *= 2 * numpy.pi
187
-
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)
188
186
  rho[mask] = numer[mask] / denom[mask]
189
187
 
190
188
  if plot:
191
- plot_density(x, rho, label='', latex=latex, save=save)
189
+ if eig is not None:
190
+ label = 'Theoretical'
191
+ else:
192
+ label = ''
193
+ plot_density(x, rho, label=label, latex=latex, save=save, eig=eig)
192
194
 
193
195
  return rho
194
196
 
@@ -252,14 +254,14 @@ class Meixner(object):
252
254
  def _P(x):
253
255
  # denom = 1.0 + self.b
254
256
  # return ((1.0 + 2.0 * self.b) * x + self.a) / denom
255
- P = ((self.c - 2.0) * x - self.a * self.c) / 2.0
257
+ P = (self.c - 2.0) * x - self.a * self.c
256
258
  return P
257
259
 
258
260
  def _Q(x):
259
261
  # denom = 1.0 + self.b
260
262
  # return (self.b * x**2 + self.a * x + 1.0) / denom
261
- Q = ((1.0 - self.c) * x**2 + self.a * self.c * x +
262
- 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
263
265
  return Q
264
266
 
265
267
  P = _P(x)
@@ -269,9 +271,6 @@ class Meixner(object):
269
271
  sign = numpy.sign(P)
270
272
  hilb = (P - sign * Delta) / (2.0 * Q)
271
273
 
272
- # using negative sign convention
273
- hilb = -hilb
274
-
275
274
  if plot:
276
275
  plot_hilbert(x, hilb, support=self.support, latex=latex, save=save)
277
276
 
@@ -291,23 +290,28 @@ class Meixner(object):
291
290
  # denom = 1.0 + self.b
292
291
  # A = (self.b * z**2 + self.a * z + 1.0) / denom
293
292
  # B = ((1.0 + 2.0 * self.b) * z + self.a) / denom
294
- A = ((1.0 - self.c) * z**2 + self.a * self.c * z +
295
- self.b * self.c**2) / 4.0
296
- 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
297
300
 
298
301
  # D = B**2 - 4 * A
299
302
  # sqrtD = numpy.sqrt(D)
300
303
 
301
304
  # Avoid numpy picking the wrong branch
302
- d = 2 * numpy.sqrt(1.0 + self.b)
303
- r_min = self.a - d
304
- r_max = self.a + d
305
- 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)
306
310
 
307
- m1 = (-B + sqrtD) / (2 * A)
308
- m2 = (-B - sqrtD) / (2 * A)
311
+ m1 = (P + sqrtD) / (2 * Q)
312
+ m2 = (P - sqrtD) / (2 * Q)
309
313
 
310
- # pick correct branch only for nonmasked entries
314
+ # pick correct branch only for non-masked entries
311
315
  upper = z.imag >= 0
312
316
  branch = numpy.empty_like(m1)
313
317
  branch[upper] = numpy.where(
@@ -526,9 +530,6 @@ class Meixner(object):
526
530
  :class: custom-dark
527
531
  """
528
532
 
529
- if seed is not None:
530
- numpy.random.seed(seed)
531
-
532
533
  if x_min is None:
533
534
  x_min = self.lam_m
534
535
 
@@ -547,14 +548,23 @@ class Meixner(object):
547
548
  inv_cdf = interp1d(cdf, xs, bounds_error=False,
548
549
  fill_value=(x_min, x_max))
549
550
 
551
+ # Random generator
552
+ rng = numpy.random.default_rng(seed)
553
+
550
554
  # Draw from uniform distribution
551
555
  if method == 'mc':
552
- u = numpy.random.rand(size)
556
+ u = rng.random(size)
557
+
553
558
  elif method == 'qmc':
554
- engine = qmc.Halton(d=1)
555
- 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
+
556
566
  else:
557
- raise ValueError('"method" is invalid.')
567
+ raise NotImplementedError('"method" is invalid.')
558
568
 
559
569
  # Draw from distribution by mapping from inverse CDF
560
570
  samples = inv_cdf(u).ravel()