freealg 0.7.4__tar.gz → 0.7.6__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 (61) hide show
  1. {freealg-0.7.4 → freealg-0.7.6}/PKG-INFO +1 -1
  2. freealg-0.7.6/freealg/__version__.py +1 -0
  3. freealg-0.7.6/freealg/_algebraic_form/_constraints.py +98 -0
  4. {freealg-0.7.4 → freealg-0.7.6}/freealg/_algebraic_form/_continuation_algebraic.py +163 -11
  5. {freealg-0.7.4 → freealg-0.7.6}/freealg/_algebraic_form/_homotopy.py +1 -1
  6. {freealg-0.7.4 → freealg-0.7.6}/freealg/_algebraic_form/algebraic_form.py +78 -19
  7. {freealg-0.7.4 → freealg-0.7.6}/freealg/_util.py +24 -1
  8. {freealg-0.7.4 → freealg-0.7.6}/freealg.egg-info/PKG-INFO +1 -1
  9. {freealg-0.7.4 → freealg-0.7.6}/freealg.egg-info/SOURCES.txt +1 -0
  10. freealg-0.7.4/freealg/__version__.py +0 -1
  11. {freealg-0.7.4 → freealg-0.7.6}/AUTHORS.txt +0 -0
  12. {freealg-0.7.4 → freealg-0.7.6}/CHANGELOG.rst +0 -0
  13. {freealg-0.7.4 → freealg-0.7.6}/LICENSE.txt +0 -0
  14. {freealg-0.7.4 → freealg-0.7.6}/MANIFEST.in +0 -0
  15. {freealg-0.7.4 → freealg-0.7.6}/README.rst +0 -0
  16. {freealg-0.7.4 → freealg-0.7.6}/freealg/__init__.py +0 -0
  17. {freealg-0.7.4 → freealg-0.7.6}/freealg/_algebraic_form/__init__.py +0 -0
  18. {freealg-0.7.4 → freealg-0.7.6}/freealg/_algebraic_form/_decompress.py +0 -0
  19. {freealg-0.7.4 → freealg-0.7.6}/freealg/_algebraic_form/_decompress2.py +0 -0
  20. {freealg-0.7.4 → freealg-0.7.6}/freealg/_algebraic_form/_edge.py +0 -0
  21. {freealg-0.7.4 → freealg-0.7.6}/freealg/_algebraic_form/_sheets_util.py +0 -0
  22. {freealg-0.7.4 → freealg-0.7.6}/freealg/_free_form/__init__.py +0 -0
  23. {freealg-0.7.4 → freealg-0.7.6}/freealg/_free_form/_chebyshev.py +0 -0
  24. {freealg-0.7.4 → freealg-0.7.6}/freealg/_free_form/_damp.py +0 -0
  25. {freealg-0.7.4 → freealg-0.7.6}/freealg/_free_form/_decompress.py +0 -0
  26. {freealg-0.7.4 → freealg-0.7.6}/freealg/_free_form/_density_util.py +0 -0
  27. {freealg-0.7.4 → freealg-0.7.6}/freealg/_free_form/_jacobi.py +0 -0
  28. {freealg-0.7.4 → freealg-0.7.6}/freealg/_free_form/_linalg.py +0 -0
  29. {freealg-0.7.4 → freealg-0.7.6}/freealg/_free_form/_pade.py +0 -0
  30. {freealg-0.7.4 → freealg-0.7.6}/freealg/_free_form/_plot_util.py +0 -0
  31. {freealg-0.7.4 → freealg-0.7.6}/freealg/_free_form/_sample.py +0 -0
  32. {freealg-0.7.4 → freealg-0.7.6}/freealg/_free_form/_series.py +0 -0
  33. {freealg-0.7.4 → freealg-0.7.6}/freealg/_free_form/_support.py +0 -0
  34. {freealg-0.7.4 → freealg-0.7.6}/freealg/_free_form/free_form.py +0 -0
  35. {freealg-0.7.4 → freealg-0.7.6}/freealg/_geometric_form/__init__.py +0 -0
  36. {freealg-0.7.4 → freealg-0.7.6}/freealg/_geometric_form/_continuation_genus0.py +0 -0
  37. {freealg-0.7.4 → freealg-0.7.6}/freealg/_geometric_form/_continuation_genus1.py +0 -0
  38. {freealg-0.7.4 → freealg-0.7.6}/freealg/_geometric_form/_elliptic_functions.py +0 -0
  39. {freealg-0.7.4 → freealg-0.7.6}/freealg/_geometric_form/_sphere_maps.py +0 -0
  40. {freealg-0.7.4 → freealg-0.7.6}/freealg/_geometric_form/_torus_maps.py +0 -0
  41. {freealg-0.7.4 → freealg-0.7.6}/freealg/_geometric_form/geometric_form.py +0 -0
  42. {freealg-0.7.4 → freealg-0.7.6}/freealg/distributions/__init__.py +0 -0
  43. {freealg-0.7.4 → freealg-0.7.6}/freealg/distributions/_chiral_block.py +0 -0
  44. {freealg-0.7.4 → freealg-0.7.6}/freealg/distributions/_deformed_marchenko_pastur.py +0 -0
  45. {freealg-0.7.4 → freealg-0.7.6}/freealg/distributions/_deformed_wigner.py +0 -0
  46. {freealg-0.7.4 → freealg-0.7.6}/freealg/distributions/_kesten_mckay.py +0 -0
  47. {freealg-0.7.4 → freealg-0.7.6}/freealg/distributions/_marchenko_pastur.py +0 -0
  48. {freealg-0.7.4 → freealg-0.7.6}/freealg/distributions/_meixner.py +0 -0
  49. {freealg-0.7.4 → freealg-0.7.6}/freealg/distributions/_wachter.py +0 -0
  50. {freealg-0.7.4 → freealg-0.7.6}/freealg/distributions/_wigner.py +0 -0
  51. {freealg-0.7.4 → freealg-0.7.6}/freealg/visualization/__init__.py +0 -0
  52. {freealg-0.7.4 → freealg-0.7.6}/freealg/visualization/_glue_util.py +0 -0
  53. {freealg-0.7.4 → freealg-0.7.6}/freealg/visualization/_rgb_hsv.py +0 -0
  54. {freealg-0.7.4 → freealg-0.7.6}/freealg.egg-info/dependency_links.txt +0 -0
  55. {freealg-0.7.4 → freealg-0.7.6}/freealg.egg-info/not-zip-safe +0 -0
  56. {freealg-0.7.4 → freealg-0.7.6}/freealg.egg-info/requires.txt +0 -0
  57. {freealg-0.7.4 → freealg-0.7.6}/freealg.egg-info/top_level.txt +0 -0
  58. {freealg-0.7.4 → freealg-0.7.6}/pyproject.toml +0 -0
  59. {freealg-0.7.4 → freealg-0.7.6}/requirements.txt +0 -0
  60. {freealg-0.7.4 → freealg-0.7.6}/setup.cfg +0 -0
  61. {freealg-0.7.4 → freealg-0.7.6}/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.6
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.6"
@@ -0,0 +1,98 @@
1
+
2
+ # SPDX-FileCopyrightText: Copyright 2025, Siavash Ameli <sameli@berkeley.edu>
3
+ # SPDX-License-Identifier: BSD-3-Clause
4
+ # SPDX-FileType: SOURCE
5
+ #
6
+ # This program is free software: you can redistribute it and/or modify it under
7
+ # the terms of the license found in the LICENSE.txt file in the root directory
8
+ # of this source tree.
9
+
10
+
11
+ # =======
12
+ # Imports
13
+ # =======
14
+
15
+ import numpy
16
+
17
+ __all__ = ['build_moment_constraint_matrix']
18
+
19
+
20
+ # ==========
21
+ # series mul
22
+ # ==========
23
+
24
+ def _series_mul(a, b, q_max):
25
+
26
+ na = min(len(a), q_max + 1)
27
+ nb = min(len(b), q_max + 1)
28
+ out = numpy.zeros(q_max + 1, dtype=float)
29
+ for i in range(na):
30
+ if a[i] == 0.0:
31
+ continue
32
+ j_max = min(nb - 1, q_max - i)
33
+ if j_max >= 0:
34
+ out[i:i + j_max + 1] += a[i] * b[:j_max + 1]
35
+ return out
36
+
37
+
38
+ # ==========
39
+ # series pow
40
+ # ==========
41
+
42
+ def _series_pow(mser, j, q_max):
43
+ if j == 0:
44
+ out = numpy.zeros(q_max + 1, dtype=float)
45
+ out[0] = 1.0
46
+ return out
47
+ out = mser.copy()
48
+ for _ in range(1, j):
49
+ out = _series_mul(out, mser, q_max)
50
+ return out
51
+
52
+
53
+ # ===============================
54
+ # build moment constraints matrix
55
+ # ===============================
56
+
57
+ def build_moment_constraint_matrix(pairs, deg_z, s, mu):
58
+
59
+ mu = numpy.asarray(mu, dtype=float).ravel()
60
+ if mu.size == 0:
61
+ return numpy.zeros((0, len(pairs)), dtype=float)
62
+
63
+ # m(z) = -sum_{p>=0} mu_p / z^{p+1}; t = 1/z so m(t) = -sum mu_p t^{p+1}
64
+ r = mu.size - 1
65
+ q_max = r
66
+
67
+ mser = numpy.zeros(q_max + 1, dtype=float)
68
+ for p in range(mu.size):
69
+ q = p + 1
70
+ if q <= q_max:
71
+ mser[q] = -float(mu[p])
72
+
73
+ # Precompute (m(t))^j coefficients up to t^{q_max}
74
+ mpow = []
75
+ for j in range(s + 1):
76
+ mpow.append(_series_pow(mser, j, q_max))
77
+
78
+ # Constraints: coeff of t^q in Q(t) := t^{deg_z} P(1/t, m(t)) must be 0
79
+ # Q(t) = sum_{i,j} c_{i,j} * t^{deg_z - i} * (m(t))^j
80
+ n_coef = len(pairs)
81
+ B = numpy.zeros((q_max + 1, n_coef), dtype=float)
82
+
83
+ for k, (i, j) in enumerate(pairs):
84
+ shift = deg_z - i
85
+ if shift < 0:
86
+ continue
87
+ mj = mpow[j]
88
+ for q in range(q_max + 1):
89
+ qq = q - shift
90
+ if 0 <= qq <= q_max:
91
+ B[q, k] = mj[qq]
92
+
93
+ # Drop all-zero rows (can happen if index-set can't support higher moments)
94
+ row_norm = numpy.linalg.norm(B, axis=1)
95
+ keep = row_norm > 0.0
96
+ B = B[keep, :]
97
+
98
+ return B
@@ -13,10 +13,11 @@
13
13
 
14
14
  import numpy
15
15
  from .._geometric_form._continuation_genus0 import joukowski_z
16
+ from ._constraints import build_moment_constraint_matrix
16
17
 
17
18
  __all__ = ['sample_z_joukowski', 'filter_z_away_from_cuts', 'powers',
18
- 'fit_polynomial_relation', 'eval_P', 'eval_roots',
19
- 'build_sheets_from_roots']
19
+ 'fit_polynomial_relation', 'sanity_check_stieltjes_branch',
20
+ 'eval_P', 'eval_roots', 'build_sheets_from_roots']
20
21
 
21
22
 
22
23
  # ======================
@@ -131,7 +132,11 @@ def powers(x, deg):
131
132
  # =======================
132
133
 
133
134
  def fit_polynomial_relation(z, m, s, deg_z, ridge_lambda=0.0, weights=None,
134
- triangular=None, normalize=False):
135
+ triangular=None, normalize=False,
136
+ mu=None, mu_reg=None):
137
+ """
138
+ Fits polynomial P(z, m) = 0 with samples from the physical branch.
139
+ """
135
140
 
136
141
  z = numpy.asarray(z, dtype=complex).ravel()
137
142
  m = numpy.asarray(m, dtype=complex).ravel()
@@ -191,17 +196,88 @@ def fit_polynomial_relation(z, m, s, deg_z, ridge_lambda=0.0, weights=None,
191
196
  if w is not None:
192
197
  A = A * w[:, None]
193
198
 
194
- s_col = numpy.max(numpy.abs(A), axis=0)
199
+ # Enforce real coefficients by solving: Re(A) c = 0 and Im(A) c = 0
200
+ Ar = numpy.vstack([A.real, A.imag])
201
+
202
+ s_col = numpy.max(numpy.abs(Ar), axis=0)
195
203
  s_col[s_col == 0.0] = 1.0
196
- As = A / s_col[None, :]
204
+ As = Ar / s_col[None, :]
205
+
206
+ # Optional moment constraints B c = 0 (hard via nullspace, soft via
207
+ # weighted rows)
208
+ if mu is not None:
209
+ B = build_moment_constraint_matrix(pairs, deg_z, s, mu)
210
+ if B.shape[0] > 0:
211
+ Bs = B / s_col[None, :]
212
+
213
+ if mu_reg is None:
214
+ # Hard constraints: solve in nullspace of Bs
215
+ uB, sB, vhB = numpy.linalg.svd(Bs, full_matrices=True)
216
+ tolB = 1e-12 * (sB[0] if sB.size else 1.0)
217
+ rankB = int(numpy.sum(sB > tolB))
218
+ if rankB >= n_coef:
219
+ raise RuntimeError(
220
+ 'Moment constraints leave no feasible coefficients.')
221
+
222
+ N = vhB[rankB:, :].T # (n_coef, n_free)
223
+ AN = As @ N
224
+
225
+ if ridge_lambda > 0.0:
226
+ L = numpy.sqrt(ridge_lambda) * numpy.eye(N.shape[1],
227
+ dtype=float)
228
+ AN = numpy.vstack([AN, L])
229
+
230
+ _, _, vhN = numpy.linalg.svd(AN, full_matrices=False)
231
+ y = vhN[-1, :]
232
+ coef_scaled = N @ y
233
+
234
+ coef = coef_scaled / s_col
235
+
236
+ else:
237
+ mu_reg = float(mu_reg)
238
+ if mu_reg > 0.0:
239
+ As_aug = As
240
+ Bs_w = numpy.sqrt(mu_reg) * Bs
241
+ As_aug = numpy.vstack([As_aug, Bs_w])
242
+
243
+ if ridge_lambda > 0.0:
244
+ L = numpy.sqrt(ridge_lambda) * numpy.eye(n_coef,
245
+ dtype=float)
246
+ As_aug = numpy.vstack([As_aug, L])
247
+
248
+ _, _, vh = numpy.linalg.svd(As_aug, full_matrices=False)
249
+ coef_scaled = vh[-1, :]
250
+ coef = coef_scaled / s_col
251
+ else:
252
+ # mu_reg == 0 => ignore constraints
253
+ if ridge_lambda > 0.0:
254
+ L = numpy.sqrt(ridge_lambda) * numpy.eye(n_coef,
255
+ dtype=float)
256
+ As = numpy.vstack([As, L])
257
+
258
+ _, _, vh = numpy.linalg.svd(As, full_matrices=False)
259
+ coef_scaled = vh[-1, :]
260
+ coef = coef_scaled / s_col
261
+
262
+ else:
263
+ # B has no effective rows -> proceed unconstrained
264
+ if ridge_lambda > 0.0:
265
+ L = numpy.sqrt(ridge_lambda) * numpy.eye(n_coef, dtype=float)
266
+ As = numpy.vstack([As, L])
267
+
268
+ _, _, vh = numpy.linalg.svd(As, full_matrices=False)
269
+ coef_scaled = vh[-1, :]
270
+ coef = coef_scaled / s_col
197
271
 
198
- if ridge_lambda > 0.0:
199
- L = numpy.sqrt(ridge_lambda) * numpy.eye(n_coef, dtype=complex)
200
- As = numpy.vstack([As, L])
272
+ else:
273
+ # No moment constraints
274
+ if ridge_lambda > 0.0:
275
+ L = numpy.sqrt(ridge_lambda) * numpy.eye(n_coef, dtype=float)
276
+ As = numpy.vstack([As, L])
201
277
 
202
- _, _, vh = numpy.linalg.svd(As, full_matrices=False)
203
- coef_scaled = vh[-1, :]
204
- coef = coef_scaled / s_col
278
+ _, _, vh = numpy.linalg.svd(As, full_matrices=False)
279
+ coef_scaled = vh[-1, :]
280
+ coef = coef_scaled / s_col
205
281
 
206
282
  full = numpy.zeros((deg_z + 1, s + 1), dtype=complex)
207
283
  for k, (i, j) in enumerate(pairs):
@@ -213,6 +289,77 @@ def fit_polynomial_relation(z, m, s, deg_z, ridge_lambda=0.0, weights=None,
213
289
  return full
214
290
 
215
291
 
292
+ # =============================
293
+ # sanity check stieltjes branch
294
+ # =============================
295
+
296
+ def sanity_check_stieltjes_branch(a_coeffs, x_min, x_max, eta=0.1,
297
+ n_x=64, y0=None, max_bad_frac=0.05):
298
+ """
299
+ Quick sanity check: does P(z,m)=0 admit a continuously trackable root with
300
+ Im(m)>0 along z=x+i*eta.
301
+ """
302
+
303
+ x_min = float(x_min)
304
+ x_max = float(x_max)
305
+ eta = float(eta)
306
+ n_x = int(n_x)
307
+ if n_x < 4:
308
+ n_x = 4
309
+
310
+ if y0 is None:
311
+ y0 = 10.0 * max(1.0, abs(x_min), abs(x_max))
312
+ y0 = float(y0)
313
+
314
+ z0 = 1j * y0
315
+ m0_target = -1.0 / z0
316
+
317
+ c0 = _poly_coef_in_m(numpy.array([z0]), a_coeffs)[0]
318
+ r0 = numpy.roots(c0[::-1])
319
+ if r0.size == 0:
320
+ return {'ok': False, 'frac_bad': 1.0, 'n_test': 0, 'n_bad': 0}
321
+
322
+ k0 = int(numpy.argmin(numpy.abs(r0 - m0_target)))
323
+ m_prev = r0[k0]
324
+
325
+ xs = numpy.linspace(x_min, x_max, n_x)
326
+ zs = xs + 1j * eta
327
+
328
+ n_bad = 0
329
+ n_ok = 0
330
+
331
+ for z in zs:
332
+ c = _poly_coef_in_m(numpy.array([z]), a_coeffs)[0]
333
+ r = numpy.roots(c[::-1])
334
+ if r.size == 0 or not numpy.all(numpy.isfinite(r)):
335
+ n_bad += 1
336
+ continue
337
+
338
+ k = int(numpy.argmin(numpy.abs(r - m_prev)))
339
+ m_sel = r[k]
340
+ m_prev = m_sel
341
+ n_ok += 1
342
+
343
+ if not numpy.isfinite(m_sel) or (m_sel.imag <= 0.0):
344
+ n_bad += 1
345
+
346
+ n_test = n_ok + (n_bad - (n_x - n_ok))
347
+ if n_test <= 0:
348
+ n_test = n_x
349
+
350
+ frac_bad = float(n_bad) / float(n_x)
351
+ ok = frac_bad <= float(max_bad_frac)
352
+
353
+ status = {
354
+ 'ok': ok,
355
+ 'frac_bad': frac_bad,
356
+ 'n_test': n_x,
357
+ 'n_bad': n_bad
358
+ }
359
+
360
+ return status
361
+
362
+
216
363
  # ======
217
364
  # eval P
218
365
  # ======
@@ -361,6 +508,10 @@ def eval_roots(z, a_coeffs):
361
508
  # =======================
362
509
 
363
510
  def track_one_sheet_on_grid(z, roots, sheet_seed, cuts=None, i0=None, j0=None):
511
+ """
512
+ This is mostly used for visualization of the sheets.
513
+ """
514
+
364
515
  z = numpy.asarray(z)
365
516
  n_y, n_x = z.shape
366
517
  s = roots.shape[1]
@@ -467,6 +618,7 @@ def track_one_sheet_on_grid(z, roots, sheet_seed, cuts=None, i0=None, j0=None):
467
618
  # =======================
468
619
 
469
620
  def build_sheets_from_roots(z, roots, m1, cuts=None, i0=None, j0=None):
621
+
470
622
  z = numpy.asarray(z)
471
623
  m1 = numpy.asarray(m1)
472
624
 
@@ -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
@@ -195,6 +194,7 @@ class AlgebraicForm(object):
195
194
 
196
195
  # Initialize
197
196
  self.a_coeffs = None # Polynomial coefficients
197
+ self.status = None # Fitting status
198
198
  self.cache = {} # Cache inner-computations
199
199
 
200
200
  # ===
@@ -208,14 +208,42 @@ class AlgebraicForm(object):
208
208
  y_eps=2e-2,
209
209
  x_pad=0.0,
210
210
  triangular=None,
211
+ mu=None,
212
+ mu_reg=None,
211
213
  normalize=False,
212
214
  verbose=False):
213
215
  """
214
- Fits polynomial.
216
+ Fit polynomial.
217
+
218
+ Parameters
219
+ ----------
220
+
221
+ deg_m : int
222
+ Degree :math:`\\deg_m(P)`
223
+
224
+ deg_z : int
225
+ Degree :math:`\\deg_z(P)`
226
+
227
+ mu : array_like, default=None
228
+ If an array :math:`[\\mu_0, \\mu_`, \\dots, \\mu_r]` is given,
229
+ it enforces the first :math:`r+1` moments. Note that :math:`\\mu_0`
230
+ should be :math:`1` to ensure unit mass. See also ``mu_reg`.
231
+
232
+ mu_reg: float, default=None
233
+ If `None`, the constraints ``mu`` are applied as hard constraint.
234
+ If a positive number, the constraints are applied as a soft
235
+ constraints with regularisation ``mu_reg``.
236
+
237
+ Notes
238
+ -----
239
+
240
+ When the input data are from an exact model, hard moment constraint is
241
+ preferred over soft constraint as the latter can hurt an already a good
242
+ fit.
215
243
  """
216
244
 
217
245
  # Very important: reset cache whenever this function is called. This
218
- # also empties all references holdign a cache copy.
246
+ # also empties all references holding a cache copy.
219
247
  # self.cache.clear()
220
248
 
221
249
  z_fits = []
@@ -232,33 +260,56 @@ class AlgebraicForm(object):
232
260
  z_fit = filter_z_away_from_cuts(z_fit, self.support, y_eps=y_eps,
233
261
  x_pad=x_pad)
234
262
 
263
+ # Fitting (w_inf = None means adaptive weight selection)
235
264
  m1_fit = self.stieltjes(z_fit)
236
265
  a_coeffs = fit_polynomial_relation(z_fit, m1_fit, s=deg_m, deg_z=deg_z,
237
266
  ridge_lambda=reg,
238
267
  triangular=triangular,
239
- normalize=normalize)
268
+ normalize=normalize, mu=mu,
269
+ mu_reg=mu_reg)
240
270
 
241
271
  self.a_coeffs = a_coeffs
242
272
 
273
+ # Reporting error
274
+ P_res = numpy.abs(eval_P(z_fit, m1_fit, a_coeffs))
275
+ res_max = numpy.max(P_res[numpy.isfinite(P_res)])
276
+ res_99_9 = numpy.quantile(P_res[numpy.isfinite(P_res)], 0.999)
277
+
278
+ # Check polynomial has Stieltjes root
279
+ x_min = self.lam_m - 1.0
280
+ x_max = self.lam_p + 1.0
281
+ status = sanity_check_stieltjes_branch(a_coeffs, x_min, x_max,
282
+ eta=max(y_eps, 1e-2), n_x=128,
283
+ max_bad_frac=0.05)
284
+
285
+ status['res_max'] = float(res_max)
286
+ status['res_99_9'] = float(res_99_9)
287
+ self.status = status
288
+
243
289
  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))
290
+ print(f'fit residual max : {res_max:>0.4e}')
291
+ print(f'fit residual 99.9%: {res_99_9:>0.4e}')
248
292
 
249
- print('\nCoefficients')
250
- with numpy.printoptions(precision=4, suppress=True):
293
+ print('\nCoefficients (real)')
294
+ with numpy.printoptions(precision=8, suppress=True):
251
295
  for i in range(a_coeffs.shape[0]):
252
296
  for j in range(a_coeffs.shape[1]):
253
297
  v = a_coeffs[i, j]
254
- print(f"{v.real:>+0.4f}{v.imag:>+0.4f}j", end=" ")
298
+ print(f'{v.real:>+0.8f}', end=' ')
255
299
  print('')
256
300
 
257
- print('\nCoefficient Magnitudes')
258
- with numpy.printoptions(precision=6, suppress=True):
259
- print(numpy.abs(a_coeffs))
301
+ a_coeffs_img_norm = numpy.linalg.norm(a_coeffs.imag, ord='fro')
302
+ print(f'\nCoefficients (imag) norm: {a_coeffs_img_norm:>0.4e}')
260
303
 
261
- return a_coeffs
304
+ if not status['ok']:
305
+ print("\nWARNING: sanity check failed:\n" +
306
+ f"\tfrac_bad: {status['frac_bad']:>0.3f}\n" +
307
+ f"\tn_bad : {status['n_bad']}\n" +
308
+ f"\tn_test : {status['n_test']}")
309
+ else:
310
+ print('\nStieltjes sanity check: OK')
311
+
312
+ return a_coeffs, status
262
313
 
263
314
  # =============
264
315
  # generate grid
@@ -638,11 +689,16 @@ class AlgebraicForm(object):
638
689
  z_query = x + 1j * self.delta
639
690
 
640
691
  # Initial condition at t=0 (physical branch)
641
- w0_list = self.stieltjes(z_query)
692
+ # w0_list = self.stieltjes(z_query)
693
+ stieltjes = numpy.vectorize(m_fn)
694
+ w0_list = stieltjes(z_query)
642
695
 
643
696
  # Times
644
697
  t = numpy.log(alpha)
645
698
 
699
+ # Ensure it starts from t = 0
700
+ t = numpy.concatenate([numpy.zeros(1), t])
701
+
646
702
  # Evolve
647
703
  W, ok = decompress_newton(
648
704
  z_query, t, self.a_coeffs,
@@ -650,6 +706,9 @@ class AlgebraicForm(object):
650
706
 
651
707
  rho = W.imag / numpy.pi
652
708
 
709
+ # Remove time zero
710
+ rho = rho[1:, :]
711
+
653
712
  if verbose:
654
713
  print("success rate per t:", ok.mean(axis=1))
655
714
 
@@ -14,7 +14,7 @@
14
14
  import numpy
15
15
  import scipy
16
16
 
17
- __all__ = ['resolve_complex_dtype', 'compute_eig']
17
+ __all__ = ['resolve_complex_dtype', 'compute_eig', 'subsample_matrix']
18
18
 
19
19
 
20
20
  # =====================
@@ -70,3 +70,26 @@ def compute_eig(A, lower=False):
70
70
  eig = scipy.linalg.eigvalsh(A, lower=lower, driver='ev')
71
71
 
72
72
  return eig
73
+
74
+
75
+ # ================
76
+ # subsample matrix
77
+ # ================
78
+
79
+ def subsample_matrix(matrix, submatrix_size, seed=None):
80
+ """
81
+ Generate a random subsample of a larger matrix
82
+ """
83
+
84
+ if matrix.shape[0] != matrix.shape[1]:
85
+ raise ValueError("Matrix must be square")
86
+
87
+ n = matrix.shape[0]
88
+ if submatrix_size > n:
89
+ raise ValueError("Submatrix size cannot exceed matrix size")
90
+
91
+ rng = numpy.random.default_rng(seed)
92
+ idx = rng.choice(n, size=submatrix_size, replace=False)
93
+ idx = numpy.sort(idx) # optional, preserves original ordering
94
+
95
+ return matrix[numpy.ix_(idx, idx)]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: freealg
3
- Version: 0.7.4
3
+ Version: 0.7.6
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
@@ -17,6 +17,7 @@ freealg.egg-info/not-zip-safe
17
17
  freealg.egg-info/requires.txt
18
18
  freealg.egg-info/top_level.txt
19
19
  freealg/_algebraic_form/__init__.py
20
+ freealg/_algebraic_form/_constraints.py
20
21
  freealg/_algebraic_form/_continuation_algebraic.py
21
22
  freealg/_algebraic_form/_decompress.py
22
23
  freealg/_algebraic_form/_decompress2.py
@@ -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