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.
- {freealg-0.7.4 → freealg-0.7.6}/PKG-INFO +1 -1
- freealg-0.7.6/freealg/__version__.py +1 -0
- freealg-0.7.6/freealg/_algebraic_form/_constraints.py +98 -0
- {freealg-0.7.4 → freealg-0.7.6}/freealg/_algebraic_form/_continuation_algebraic.py +163 -11
- {freealg-0.7.4 → freealg-0.7.6}/freealg/_algebraic_form/_homotopy.py +1 -1
- {freealg-0.7.4 → freealg-0.7.6}/freealg/_algebraic_form/algebraic_form.py +78 -19
- {freealg-0.7.4 → freealg-0.7.6}/freealg/_util.py +24 -1
- {freealg-0.7.4 → freealg-0.7.6}/freealg.egg-info/PKG-INFO +1 -1
- {freealg-0.7.4 → freealg-0.7.6}/freealg.egg-info/SOURCES.txt +1 -0
- freealg-0.7.4/freealg/__version__.py +0 -1
- {freealg-0.7.4 → freealg-0.7.6}/AUTHORS.txt +0 -0
- {freealg-0.7.4 → freealg-0.7.6}/CHANGELOG.rst +0 -0
- {freealg-0.7.4 → freealg-0.7.6}/LICENSE.txt +0 -0
- {freealg-0.7.4 → freealg-0.7.6}/MANIFEST.in +0 -0
- {freealg-0.7.4 → freealg-0.7.6}/README.rst +0 -0
- {freealg-0.7.4 → freealg-0.7.6}/freealg/__init__.py +0 -0
- {freealg-0.7.4 → freealg-0.7.6}/freealg/_algebraic_form/__init__.py +0 -0
- {freealg-0.7.4 → freealg-0.7.6}/freealg/_algebraic_form/_decompress.py +0 -0
- {freealg-0.7.4 → freealg-0.7.6}/freealg/_algebraic_form/_decompress2.py +0 -0
- {freealg-0.7.4 → freealg-0.7.6}/freealg/_algebraic_form/_edge.py +0 -0
- {freealg-0.7.4 → freealg-0.7.6}/freealg/_algebraic_form/_sheets_util.py +0 -0
- {freealg-0.7.4 → freealg-0.7.6}/freealg/_free_form/__init__.py +0 -0
- {freealg-0.7.4 → freealg-0.7.6}/freealg/_free_form/_chebyshev.py +0 -0
- {freealg-0.7.4 → freealg-0.7.6}/freealg/_free_form/_damp.py +0 -0
- {freealg-0.7.4 → freealg-0.7.6}/freealg/_free_form/_decompress.py +0 -0
- {freealg-0.7.4 → freealg-0.7.6}/freealg/_free_form/_density_util.py +0 -0
- {freealg-0.7.4 → freealg-0.7.6}/freealg/_free_form/_jacobi.py +0 -0
- {freealg-0.7.4 → freealg-0.7.6}/freealg/_free_form/_linalg.py +0 -0
- {freealg-0.7.4 → freealg-0.7.6}/freealg/_free_form/_pade.py +0 -0
- {freealg-0.7.4 → freealg-0.7.6}/freealg/_free_form/_plot_util.py +0 -0
- {freealg-0.7.4 → freealg-0.7.6}/freealg/_free_form/_sample.py +0 -0
- {freealg-0.7.4 → freealg-0.7.6}/freealg/_free_form/_series.py +0 -0
- {freealg-0.7.4 → freealg-0.7.6}/freealg/_free_form/_support.py +0 -0
- {freealg-0.7.4 → freealg-0.7.6}/freealg/_free_form/free_form.py +0 -0
- {freealg-0.7.4 → freealg-0.7.6}/freealg/_geometric_form/__init__.py +0 -0
- {freealg-0.7.4 → freealg-0.7.6}/freealg/_geometric_form/_continuation_genus0.py +0 -0
- {freealg-0.7.4 → freealg-0.7.6}/freealg/_geometric_form/_continuation_genus1.py +0 -0
- {freealg-0.7.4 → freealg-0.7.6}/freealg/_geometric_form/_elliptic_functions.py +0 -0
- {freealg-0.7.4 → freealg-0.7.6}/freealg/_geometric_form/_sphere_maps.py +0 -0
- {freealg-0.7.4 → freealg-0.7.6}/freealg/_geometric_form/_torus_maps.py +0 -0
- {freealg-0.7.4 → freealg-0.7.6}/freealg/_geometric_form/geometric_form.py +0 -0
- {freealg-0.7.4 → freealg-0.7.6}/freealg/distributions/__init__.py +0 -0
- {freealg-0.7.4 → freealg-0.7.6}/freealg/distributions/_chiral_block.py +0 -0
- {freealg-0.7.4 → freealg-0.7.6}/freealg/distributions/_deformed_marchenko_pastur.py +0 -0
- {freealg-0.7.4 → freealg-0.7.6}/freealg/distributions/_deformed_wigner.py +0 -0
- {freealg-0.7.4 → freealg-0.7.6}/freealg/distributions/_kesten_mckay.py +0 -0
- {freealg-0.7.4 → freealg-0.7.6}/freealg/distributions/_marchenko_pastur.py +0 -0
- {freealg-0.7.4 → freealg-0.7.6}/freealg/distributions/_meixner.py +0 -0
- {freealg-0.7.4 → freealg-0.7.6}/freealg/distributions/_wachter.py +0 -0
- {freealg-0.7.4 → freealg-0.7.6}/freealg/distributions/_wigner.py +0 -0
- {freealg-0.7.4 → freealg-0.7.6}/freealg/visualization/__init__.py +0 -0
- {freealg-0.7.4 → freealg-0.7.6}/freealg/visualization/_glue_util.py +0 -0
- {freealg-0.7.4 → freealg-0.7.6}/freealg/visualization/_rgb_hsv.py +0 -0
- {freealg-0.7.4 → freealg-0.7.6}/freealg.egg-info/dependency_links.txt +0 -0
- {freealg-0.7.4 → freealg-0.7.6}/freealg.egg-info/not-zip-safe +0 -0
- {freealg-0.7.4 → freealg-0.7.6}/freealg.egg-info/requires.txt +0 -0
- {freealg-0.7.4 → freealg-0.7.6}/freealg.egg-info/top_level.txt +0 -0
- {freealg-0.7.4 → freealg-0.7.6}/pyproject.toml +0 -0
- {freealg-0.7.4 → freealg-0.7.6}/requirements.txt +0 -0
- {freealg-0.7.4 → freealg-0.7.6}/setup.cfg +0 -0
- {freealg-0.7.4 → freealg-0.7.6}/setup.py +0 -0
|
@@ -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', '
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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=
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
245
|
-
print(
|
|
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=
|
|
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
|
|
298
|
+
print(f'{v.real:>+0.8f}', end=' ')
|
|
255
299
|
print('')
|
|
256
300
|
|
|
257
|
-
|
|
258
|
-
|
|
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
|
-
|
|
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)]
|
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|