freealg 0.7.4__tar.gz → 0.7.5__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.
Files changed (60) hide show
  1. {freealg-0.7.4 → freealg-0.7.5}/PKG-INFO +1 -1
  2. freealg-0.7.5/freealg/__version__.py +1 -0
  3. {freealg-0.7.4 → freealg-0.7.5}/freealg/_algebraic_form/_continuation_algebraic.py +84 -5
  4. {freealg-0.7.4 → freealg-0.7.5}/freealg/_algebraic_form/_homotopy.py +1 -1
  5. {freealg-0.7.4 → freealg-0.7.5}/freealg/_algebraic_form/algebraic_form.py +35 -16
  6. {freealg-0.7.4 → freealg-0.7.5}/freealg.egg-info/PKG-INFO +1 -1
  7. freealg-0.7.4/freealg/__version__.py +0 -1
  8. {freealg-0.7.4 → freealg-0.7.5}/AUTHORS.txt +0 -0
  9. {freealg-0.7.4 → freealg-0.7.5}/CHANGELOG.rst +0 -0
  10. {freealg-0.7.4 → freealg-0.7.5}/LICENSE.txt +0 -0
  11. {freealg-0.7.4 → freealg-0.7.5}/MANIFEST.in +0 -0
  12. {freealg-0.7.4 → freealg-0.7.5}/README.rst +0 -0
  13. {freealg-0.7.4 → freealg-0.7.5}/freealg/__init__.py +0 -0
  14. {freealg-0.7.4 → freealg-0.7.5}/freealg/_algebraic_form/__init__.py +0 -0
  15. {freealg-0.7.4 → freealg-0.7.5}/freealg/_algebraic_form/_decompress.py +0 -0
  16. {freealg-0.7.4 → freealg-0.7.5}/freealg/_algebraic_form/_decompress2.py +0 -0
  17. {freealg-0.7.4 → freealg-0.7.5}/freealg/_algebraic_form/_edge.py +0 -0
  18. {freealg-0.7.4 → freealg-0.7.5}/freealg/_algebraic_form/_sheets_util.py +0 -0
  19. {freealg-0.7.4 → freealg-0.7.5}/freealg/_free_form/__init__.py +0 -0
  20. {freealg-0.7.4 → freealg-0.7.5}/freealg/_free_form/_chebyshev.py +0 -0
  21. {freealg-0.7.4 → freealg-0.7.5}/freealg/_free_form/_damp.py +0 -0
  22. {freealg-0.7.4 → freealg-0.7.5}/freealg/_free_form/_decompress.py +0 -0
  23. {freealg-0.7.4 → freealg-0.7.5}/freealg/_free_form/_density_util.py +0 -0
  24. {freealg-0.7.4 → freealg-0.7.5}/freealg/_free_form/_jacobi.py +0 -0
  25. {freealg-0.7.4 → freealg-0.7.5}/freealg/_free_form/_linalg.py +0 -0
  26. {freealg-0.7.4 → freealg-0.7.5}/freealg/_free_form/_pade.py +0 -0
  27. {freealg-0.7.4 → freealg-0.7.5}/freealg/_free_form/_plot_util.py +0 -0
  28. {freealg-0.7.4 → freealg-0.7.5}/freealg/_free_form/_sample.py +0 -0
  29. {freealg-0.7.4 → freealg-0.7.5}/freealg/_free_form/_series.py +0 -0
  30. {freealg-0.7.4 → freealg-0.7.5}/freealg/_free_form/_support.py +0 -0
  31. {freealg-0.7.4 → freealg-0.7.5}/freealg/_free_form/free_form.py +0 -0
  32. {freealg-0.7.4 → freealg-0.7.5}/freealg/_geometric_form/__init__.py +0 -0
  33. {freealg-0.7.4 → freealg-0.7.5}/freealg/_geometric_form/_continuation_genus0.py +0 -0
  34. {freealg-0.7.4 → freealg-0.7.5}/freealg/_geometric_form/_continuation_genus1.py +0 -0
  35. {freealg-0.7.4 → freealg-0.7.5}/freealg/_geometric_form/_elliptic_functions.py +0 -0
  36. {freealg-0.7.4 → freealg-0.7.5}/freealg/_geometric_form/_sphere_maps.py +0 -0
  37. {freealg-0.7.4 → freealg-0.7.5}/freealg/_geometric_form/_torus_maps.py +0 -0
  38. {freealg-0.7.4 → freealg-0.7.5}/freealg/_geometric_form/geometric_form.py +0 -0
  39. {freealg-0.7.4 → freealg-0.7.5}/freealg/_util.py +0 -0
  40. {freealg-0.7.4 → freealg-0.7.5}/freealg/distributions/__init__.py +0 -0
  41. {freealg-0.7.4 → freealg-0.7.5}/freealg/distributions/_chiral_block.py +0 -0
  42. {freealg-0.7.4 → freealg-0.7.5}/freealg/distributions/_deformed_marchenko_pastur.py +0 -0
  43. {freealg-0.7.4 → freealg-0.7.5}/freealg/distributions/_deformed_wigner.py +0 -0
  44. {freealg-0.7.4 → freealg-0.7.5}/freealg/distributions/_kesten_mckay.py +0 -0
  45. {freealg-0.7.4 → freealg-0.7.5}/freealg/distributions/_marchenko_pastur.py +0 -0
  46. {freealg-0.7.4 → freealg-0.7.5}/freealg/distributions/_meixner.py +0 -0
  47. {freealg-0.7.4 → freealg-0.7.5}/freealg/distributions/_wachter.py +0 -0
  48. {freealg-0.7.4 → freealg-0.7.5}/freealg/distributions/_wigner.py +0 -0
  49. {freealg-0.7.4 → freealg-0.7.5}/freealg/visualization/__init__.py +0 -0
  50. {freealg-0.7.4 → freealg-0.7.5}/freealg/visualization/_glue_util.py +0 -0
  51. {freealg-0.7.4 → freealg-0.7.5}/freealg/visualization/_rgb_hsv.py +0 -0
  52. {freealg-0.7.4 → freealg-0.7.5}/freealg.egg-info/SOURCES.txt +0 -0
  53. {freealg-0.7.4 → freealg-0.7.5}/freealg.egg-info/dependency_links.txt +0 -0
  54. {freealg-0.7.4 → freealg-0.7.5}/freealg.egg-info/not-zip-safe +0 -0
  55. {freealg-0.7.4 → freealg-0.7.5}/freealg.egg-info/requires.txt +0 -0
  56. {freealg-0.7.4 → freealg-0.7.5}/freealg.egg-info/top_level.txt +0 -0
  57. {freealg-0.7.4 → freealg-0.7.5}/pyproject.toml +0 -0
  58. {freealg-0.7.4 → freealg-0.7.5}/requirements.txt +0 -0
  59. {freealg-0.7.4 → freealg-0.7.5}/setup.cfg +0 -0
  60. {freealg-0.7.4 → freealg-0.7.5}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: freealg
3
- Version: 0.7.4
3
+ Version: 0.7.5
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
@@ -0,0 +1 @@
1
+ __version__ = "0.7.5"
@@ -15,8 +15,8 @@ import numpy
15
15
  from .._geometric_form._continuation_genus0 import joukowski_z
16
16
 
17
17
  __all__ = ['sample_z_joukowski', 'filter_z_away_from_cuts', 'powers',
18
- 'fit_polynomial_relation', 'eval_P', 'eval_roots',
19
- 'build_sheets_from_roots']
18
+ 'fit_polynomial_relation', 'sanity_check_stieltjes_branch',
19
+ 'eval_P', 'eval_roots', 'build_sheets_from_roots']
20
20
 
21
21
 
22
22
  # ======================
@@ -191,12 +191,15 @@ def fit_polynomial_relation(z, m, s, deg_z, ridge_lambda=0.0, weights=None,
191
191
  if w is not None:
192
192
  A = A * w[:, None]
193
193
 
194
- s_col = numpy.max(numpy.abs(A), axis=0)
194
+ # Enforce real coefficients by solving: Re(A) c = 0 and Im(A) c = 0
195
+ Ar = numpy.vstack([A.real, A.imag])
196
+
197
+ s_col = numpy.max(numpy.abs(Ar), axis=0)
195
198
  s_col[s_col == 0.0] = 1.0
196
- As = A / s_col[None, :]
199
+ As = Ar / s_col[None, :]
197
200
 
198
201
  if ridge_lambda > 0.0:
199
- L = numpy.sqrt(ridge_lambda) * numpy.eye(n_coef, dtype=complex)
202
+ L = numpy.sqrt(ridge_lambda) * numpy.eye(n_coef, dtype=float)
200
203
  As = numpy.vstack([As, L])
201
204
 
202
205
  _, _, vh = numpy.linalg.svd(As, full_matrices=False)
@@ -213,6 +216,77 @@ def fit_polynomial_relation(z, m, s, deg_z, ridge_lambda=0.0, weights=None,
213
216
  return full
214
217
 
215
218
 
219
+ # =============================
220
+ # sanity check stieltjes branch
221
+ # =============================
222
+
223
+ def sanity_check_stieltjes_branch(a_coeffs, x_min, x_max, eta=0.1,
224
+ n_x=64, y0=None, max_bad_frac=0.05):
225
+ """
226
+ Quick sanity check: does P(z,m)=0 admit a continuously trackable root with
227
+ Im(m)>0 along z=x+i*eta.
228
+ """
229
+
230
+ x_min = float(x_min)
231
+ x_max = float(x_max)
232
+ eta = float(eta)
233
+ n_x = int(n_x)
234
+ if n_x < 4:
235
+ n_x = 4
236
+
237
+ if y0 is None:
238
+ y0 = 10.0 * max(1.0, abs(x_min), abs(x_max))
239
+ y0 = float(y0)
240
+
241
+ z0 = 1j * y0
242
+ m0_target = -1.0 / z0
243
+
244
+ c0 = _poly_coef_in_m(numpy.array([z0]), a_coeffs)[0]
245
+ r0 = numpy.roots(c0[::-1])
246
+ if r0.size == 0:
247
+ return {'ok': False, 'frac_bad': 1.0, 'n_test': 0, 'n_bad': 0}
248
+
249
+ k0 = int(numpy.argmin(numpy.abs(r0 - m0_target)))
250
+ m_prev = r0[k0]
251
+
252
+ xs = numpy.linspace(x_min, x_max, n_x)
253
+ zs = xs + 1j * eta
254
+
255
+ n_bad = 0
256
+ n_ok = 0
257
+
258
+ for z in zs:
259
+ c = _poly_coef_in_m(numpy.array([z]), a_coeffs)[0]
260
+ r = numpy.roots(c[::-1])
261
+ if r.size == 0 or not numpy.all(numpy.isfinite(r)):
262
+ n_bad += 1
263
+ continue
264
+
265
+ k = int(numpy.argmin(numpy.abs(r - m_prev)))
266
+ m_sel = r[k]
267
+ m_prev = m_sel
268
+ n_ok += 1
269
+
270
+ if not numpy.isfinite(m_sel) or (m_sel.imag <= 0.0):
271
+ n_bad += 1
272
+
273
+ n_test = n_ok + (n_bad - (n_x - n_ok))
274
+ if n_test <= 0:
275
+ n_test = n_x
276
+
277
+ frac_bad = float(n_bad) / float(n_x)
278
+ ok = frac_bad <= float(max_bad_frac)
279
+
280
+ status = {
281
+ 'ok': ok,
282
+ 'frac_bad': frac_bad,
283
+ 'n_test': n_x,
284
+ 'n_bad': n_bad
285
+ }
286
+
287
+ return status
288
+
289
+
216
290
  # ======
217
291
  # eval P
218
292
  # ======
@@ -361,6 +435,10 @@ def eval_roots(z, a_coeffs):
361
435
  # =======================
362
436
 
363
437
  def track_one_sheet_on_grid(z, roots, sheet_seed, cuts=None, i0=None, j0=None):
438
+ """
439
+ This is mostly used for visualization of the sheets.
440
+ """
441
+
364
442
  z = numpy.asarray(z)
365
443
  n_y, n_x = z.shape
366
444
  s = roots.shape[1]
@@ -467,6 +545,7 @@ def track_one_sheet_on_grid(z, roots, sheet_seed, cuts=None, i0=None, j0=None):
467
545
  # =======================
468
546
 
469
547
  def build_sheets_from_roots(z, roots, m1, cuts=None, i0=None, j0=None):
548
+
470
549
  z = numpy.asarray(z)
471
550
  m1 = numpy.asarray(m1)
472
551
 
@@ -62,7 +62,7 @@ def stieltjes_select_root(roots, z, w_prev=None):
62
62
  # stieltjes poly
63
63
  # ==============
64
64
 
65
- def stieltjes_poly(z, a, eps=None, height=1e4, steps=100):
65
+ def stieltjes_poly(z, a, eps=None, height=1e+4, steps=100):
66
66
  """
67
67
  Evaluate the Stieltjes-branch solution m(z) of an algebraic equation.
68
68
 
@@ -11,12 +11,12 @@
11
11
  # Imports
12
12
  # =======
13
13
 
14
- import inspect
15
14
  import numpy
16
15
  from .._util import resolve_complex_dtype, compute_eig
17
16
  # from .._util import compute_eig
18
17
  from ._continuation_algebraic import sample_z_joukowski, \
19
- filter_z_away_from_cuts, fit_polynomial_relation, eval_P
18
+ filter_z_away_from_cuts, fit_polynomial_relation, \
19
+ sanity_check_stieltjes_branch, eval_P
20
20
  from ._edge import evolve_edges, merge_edges
21
21
  from ._decompress import decompress_newton
22
22
  from ._decompress2 import decompress_coeffs
@@ -149,8 +149,7 @@ class AlgebraicForm(object):
149
149
  # Data type for complex arrays
150
150
  self.dtype = resolve_complex_dtype(dtype)
151
151
 
152
- if inspect.isclass(A) and hasattr(A, "stieltjes") and \
153
- callable(getattr(A, "stieltjes", None)):
152
+ if hasattr(A, 'stieltjes') and callable(getattr(A, 'stieltjes', None)):
154
153
  # This is one of the distribution objects, like MarchenkoPastur
155
154
  self.stieltjes = A.stieltjes
156
155
  self.n = 1
@@ -215,7 +214,7 @@ class AlgebraicForm(object):
215
214
  """
216
215
 
217
216
  # Very important: reset cache whenever this function is called. This
218
- # also empties all references holdign a cache copy.
217
+ # also empties all references holding a cache copy.
219
218
  # self.cache.clear()
220
219
 
221
220
  z_fits = []
@@ -240,25 +239,45 @@ class AlgebraicForm(object):
240
239
 
241
240
  self.a_coeffs = a_coeffs
242
241
 
242
+ # Reporting error
243
+ P_res = numpy.abs(eval_P(z_fit, m1_fit, a_coeffs))
244
+ res_max = numpy.max(P_res[numpy.isfinite(P_res)])
245
+ res_99_9 = numpy.quantile(P_res[numpy.isfinite(P_res)], 0.999)
246
+
247
+ # Check polynomial has Stieltjes root
248
+ x_min = self.lam_m - 1.0
249
+ x_max = self.lam_p + 1.0
250
+ status = sanity_check_stieltjes_branch(a_coeffs, x_min, x_max,
251
+ eta=max(y_eps, 1e-2), n_x=128,
252
+ max_bad_frac=0.05)
253
+
254
+ status['res_max'] = res_max
255
+ status['res_99_9'] = res_99_9
256
+
243
257
  if verbose:
244
- P_res = numpy.abs(eval_P(z_fit, m1_fit, a_coeffs))
245
- print("fit residual max:", numpy.max(P_res[numpy.isfinite(P_res)]))
246
- print("fit residual 99.9%:",
247
- numpy.quantile(P_res[numpy.isfinite(P_res)], 0.999))
258
+ print(f'fit residual max : {res_max:>0.4e}')
259
+ print(f'fit residual 99.9%: {res_99_9:>0.4e}')
248
260
 
249
- print('\nCoefficients')
250
- with numpy.printoptions(precision=4, suppress=True):
261
+ print('\nCoefficients (real)')
262
+ with numpy.printoptions(precision=8, suppress=True):
251
263
  for i in range(a_coeffs.shape[0]):
252
264
  for j in range(a_coeffs.shape[1]):
253
265
  v = a_coeffs[i, j]
254
- print(f"{v.real:>+0.4f}{v.imag:>+0.4f}j", end=" ")
266
+ print(f'{v.real:>+0.8f}', end=' ')
255
267
  print('')
256
268
 
257
- print('\nCoefficient Magnitudes')
258
- with numpy.printoptions(precision=6, suppress=True):
259
- print(numpy.abs(a_coeffs))
269
+ a_coeffs_img_norm = numpy.linalg.norm(a_coeffs.imag, ord='fro')
270
+ print(f'\nCoefficients (imag) norm: {a_coeffs_img_norm:>0.4e}')
271
+
272
+ if not status['ok']:
273
+ print("\nWARNING: sanity check failed:\n" +
274
+ f"\tfrac_bad: {status['frac_bad']:>0.3f}\n" +
275
+ f"\tn_bad : {status['n_bad']}\n" +
276
+ f"\tn_test : {status['n_test']}")
277
+ else:
278
+ print('\nStieltjes sanity check: OK')
260
279
 
261
- return a_coeffs
280
+ return a_coeffs, status
262
281
 
263
282
  # =============
264
283
  # generate grid
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: freealg
3
- Version: 0.7.4
3
+ Version: 0.7.5
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
@@ -1 +0,0 @@
1
- __version__ = "0.7.4"
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