freealg 0.7.5__tar.gz → 0.7.7__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.5 → freealg-0.7.7}/PKG-INFO +1 -1
- freealg-0.7.7/freealg/__version__.py +1 -0
- freealg-0.7.7/freealg/_algebraic_form/_constraints.py +98 -0
- {freealg-0.7.5 → freealg-0.7.7}/freealg/_algebraic_form/_continuation_algebraic.py +89 -8
- freealg-0.7.7/freealg/_algebraic_form/_discriminant.py +226 -0
- {freealg-0.7.5 → freealg-0.7.7}/freealg/_algebraic_form/algebraic_form.py +55 -9
- {freealg-0.7.5 → freealg-0.7.7}/freealg/_util.py +24 -1
- {freealg-0.7.5 → freealg-0.7.7}/freealg.egg-info/PKG-INFO +1 -1
- {freealg-0.7.5 → freealg-0.7.7}/freealg.egg-info/SOURCES.txt +2 -0
- freealg-0.7.5/freealg/__version__.py +0 -1
- {freealg-0.7.5 → freealg-0.7.7}/AUTHORS.txt +0 -0
- {freealg-0.7.5 → freealg-0.7.7}/CHANGELOG.rst +0 -0
- {freealg-0.7.5 → freealg-0.7.7}/LICENSE.txt +0 -0
- {freealg-0.7.5 → freealg-0.7.7}/MANIFEST.in +0 -0
- {freealg-0.7.5 → freealg-0.7.7}/README.rst +0 -0
- {freealg-0.7.5 → freealg-0.7.7}/freealg/__init__.py +0 -0
- {freealg-0.7.5 → freealg-0.7.7}/freealg/_algebraic_form/__init__.py +0 -0
- {freealg-0.7.5 → freealg-0.7.7}/freealg/_algebraic_form/_decompress.py +0 -0
- {freealg-0.7.5 → freealg-0.7.7}/freealg/_algebraic_form/_decompress2.py +0 -0
- {freealg-0.7.5 → freealg-0.7.7}/freealg/_algebraic_form/_edge.py +0 -0
- {freealg-0.7.5 → freealg-0.7.7}/freealg/_algebraic_form/_homotopy.py +0 -0
- {freealg-0.7.5 → freealg-0.7.7}/freealg/_algebraic_form/_sheets_util.py +0 -0
- {freealg-0.7.5 → freealg-0.7.7}/freealg/_free_form/__init__.py +0 -0
- {freealg-0.7.5 → freealg-0.7.7}/freealg/_free_form/_chebyshev.py +0 -0
- {freealg-0.7.5 → freealg-0.7.7}/freealg/_free_form/_damp.py +0 -0
- {freealg-0.7.5 → freealg-0.7.7}/freealg/_free_form/_decompress.py +0 -0
- {freealg-0.7.5 → freealg-0.7.7}/freealg/_free_form/_density_util.py +0 -0
- {freealg-0.7.5 → freealg-0.7.7}/freealg/_free_form/_jacobi.py +0 -0
- {freealg-0.7.5 → freealg-0.7.7}/freealg/_free_form/_linalg.py +0 -0
- {freealg-0.7.5 → freealg-0.7.7}/freealg/_free_form/_pade.py +0 -0
- {freealg-0.7.5 → freealg-0.7.7}/freealg/_free_form/_plot_util.py +0 -0
- {freealg-0.7.5 → freealg-0.7.7}/freealg/_free_form/_sample.py +0 -0
- {freealg-0.7.5 → freealg-0.7.7}/freealg/_free_form/_series.py +0 -0
- {freealg-0.7.5 → freealg-0.7.7}/freealg/_free_form/_support.py +0 -0
- {freealg-0.7.5 → freealg-0.7.7}/freealg/_free_form/free_form.py +0 -0
- {freealg-0.7.5 → freealg-0.7.7}/freealg/_geometric_form/__init__.py +0 -0
- {freealg-0.7.5 → freealg-0.7.7}/freealg/_geometric_form/_continuation_genus0.py +0 -0
- {freealg-0.7.5 → freealg-0.7.7}/freealg/_geometric_form/_continuation_genus1.py +0 -0
- {freealg-0.7.5 → freealg-0.7.7}/freealg/_geometric_form/_elliptic_functions.py +0 -0
- {freealg-0.7.5 → freealg-0.7.7}/freealg/_geometric_form/_sphere_maps.py +0 -0
- {freealg-0.7.5 → freealg-0.7.7}/freealg/_geometric_form/_torus_maps.py +0 -0
- {freealg-0.7.5 → freealg-0.7.7}/freealg/_geometric_form/geometric_form.py +0 -0
- {freealg-0.7.5 → freealg-0.7.7}/freealg/distributions/__init__.py +0 -0
- {freealg-0.7.5 → freealg-0.7.7}/freealg/distributions/_chiral_block.py +0 -0
- {freealg-0.7.5 → freealg-0.7.7}/freealg/distributions/_deformed_marchenko_pastur.py +0 -0
- {freealg-0.7.5 → freealg-0.7.7}/freealg/distributions/_deformed_wigner.py +0 -0
- {freealg-0.7.5 → freealg-0.7.7}/freealg/distributions/_kesten_mckay.py +0 -0
- {freealg-0.7.5 → freealg-0.7.7}/freealg/distributions/_marchenko_pastur.py +0 -0
- {freealg-0.7.5 → freealg-0.7.7}/freealg/distributions/_meixner.py +0 -0
- {freealg-0.7.5 → freealg-0.7.7}/freealg/distributions/_wachter.py +0 -0
- {freealg-0.7.5 → freealg-0.7.7}/freealg/distributions/_wigner.py +0 -0
- {freealg-0.7.5 → freealg-0.7.7}/freealg/visualization/__init__.py +0 -0
- {freealg-0.7.5 → freealg-0.7.7}/freealg/visualization/_glue_util.py +0 -0
- {freealg-0.7.5 → freealg-0.7.7}/freealg/visualization/_rgb_hsv.py +0 -0
- {freealg-0.7.5 → freealg-0.7.7}/freealg.egg-info/dependency_links.txt +0 -0
- {freealg-0.7.5 → freealg-0.7.7}/freealg.egg-info/not-zip-safe +0 -0
- {freealg-0.7.5 → freealg-0.7.7}/freealg.egg-info/requires.txt +0 -0
- {freealg-0.7.5 → freealg-0.7.7}/freealg.egg-info/top_level.txt +0 -0
- {freealg-0.7.5 → freealg-0.7.7}/pyproject.toml +0 -0
- {freealg-0.7.5 → freealg-0.7.7}/requirements.txt +0 -0
- {freealg-0.7.5 → freealg-0.7.7}/setup.cfg +0 -0
- {freealg-0.7.5 → freealg-0.7.7}/setup.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.7.7"
|
|
@@ -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,6 +13,7 @@
|
|
|
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
19
|
'fit_polynomial_relation', 'sanity_check_stieltjes_branch',
|
|
@@ -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()
|
|
@@ -198,13 +203,82 @@ def fit_polynomial_relation(z, m, s, deg_z, ridge_lambda=0.0, weights=None,
|
|
|
198
203
|
s_col[s_col == 0.0] = 1.0
|
|
199
204
|
As = Ar / s_col[None, :]
|
|
200
205
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
+
_, svals, 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
|
+
_, svals, vh = numpy.linalg.svd(As_aug,
|
|
249
|
+
full_matrices=False)
|
|
250
|
+
coef_scaled = vh[-1, :]
|
|
251
|
+
coef = coef_scaled / s_col
|
|
252
|
+
else:
|
|
253
|
+
# mu_reg == 0 => ignore constraints
|
|
254
|
+
if ridge_lambda > 0.0:
|
|
255
|
+
L = numpy.sqrt(ridge_lambda) * numpy.eye(n_coef,
|
|
256
|
+
dtype=float)
|
|
257
|
+
As = numpy.vstack([As, L])
|
|
258
|
+
|
|
259
|
+
_, svals, vh = numpy.linalg.svd(As, full_matrices=False)
|
|
260
|
+
coef_scaled = vh[-1, :]
|
|
261
|
+
coef = coef_scaled / s_col
|
|
262
|
+
|
|
263
|
+
else:
|
|
264
|
+
# B has no effective rows -> proceed unconstrained
|
|
265
|
+
if ridge_lambda > 0.0:
|
|
266
|
+
L = numpy.sqrt(ridge_lambda) * numpy.eye(n_coef, dtype=float)
|
|
267
|
+
As = numpy.vstack([As, L])
|
|
204
268
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
269
|
+
_, svals, vh = numpy.linalg.svd(As, full_matrices=False)
|
|
270
|
+
coef_scaled = vh[-1, :]
|
|
271
|
+
coef = coef_scaled / s_col
|
|
272
|
+
|
|
273
|
+
else:
|
|
274
|
+
# No moment constraints
|
|
275
|
+
if ridge_lambda > 0.0:
|
|
276
|
+
L = numpy.sqrt(ridge_lambda) * numpy.eye(n_coef, dtype=float)
|
|
277
|
+
As = numpy.vstack([As, L])
|
|
278
|
+
|
|
279
|
+
_, svals, vh = numpy.linalg.svd(As, full_matrices=False)
|
|
280
|
+
coef_scaled = vh[-1, :]
|
|
281
|
+
coef = coef_scaled / s_col
|
|
208
282
|
|
|
209
283
|
full = numpy.zeros((deg_z + 1, s + 1), dtype=complex)
|
|
210
284
|
for k, (i, j) in enumerate(pairs):
|
|
@@ -213,7 +287,14 @@ def fit_polynomial_relation(z, m, s, deg_z, ridge_lambda=0.0, weights=None,
|
|
|
213
287
|
if normalize:
|
|
214
288
|
full = _normalize_coefficients(full)
|
|
215
289
|
|
|
216
|
-
|
|
290
|
+
# Diagnostic metrics
|
|
291
|
+
fit_metrics = {
|
|
292
|
+
's_min': svals[-1],
|
|
293
|
+
'gap_ratio': float(svals[-2] / svals[-1]),
|
|
294
|
+
'n_small': float(int(numpy.sum(svals <= svals[0] * 1e-12))),
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return full, fit_metrics
|
|
217
298
|
|
|
218
299
|
|
|
219
300
|
# =============================
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright 2026, Siavash Ameli <sameli@berkeley.edu>
|
|
2
|
+
# SPDX-License-Identifier: BSD-3-Clause
|
|
3
|
+
# SPDX-FileType: SOURCE
|
|
4
|
+
#
|
|
5
|
+
# This program is free software: you can redistribute it and/or modify it under
|
|
6
|
+
# the terms of the license found in the LICENSE.txt file in the root directory
|
|
7
|
+
# of this source tree.
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# =======
|
|
11
|
+
# Imports
|
|
12
|
+
# =======
|
|
13
|
+
|
|
14
|
+
import numpy as np
|
|
15
|
+
import numpy.polynomial.polynomial as poly
|
|
16
|
+
|
|
17
|
+
__all__ = ['compute_singular_points']
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# =========
|
|
21
|
+
# ploy trim
|
|
22
|
+
# =========
|
|
23
|
+
|
|
24
|
+
def _poly_trim(p, tol):
|
|
25
|
+
|
|
26
|
+
p = np.asarray(p, dtype=complex).ravel()
|
|
27
|
+
if p.size == 0:
|
|
28
|
+
return np.zeros(1, dtype=complex)
|
|
29
|
+
k = p.size - 1
|
|
30
|
+
while k > 0 and abs(p[k]) <= tol:
|
|
31
|
+
k -= 1
|
|
32
|
+
return p[:k + 1].copy()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# ============
|
|
36
|
+
# poly is zero
|
|
37
|
+
# ============
|
|
38
|
+
|
|
39
|
+
def _poly_is_zero(p, tol):
|
|
40
|
+
|
|
41
|
+
p = _poly_trim(p, tol)
|
|
42
|
+
return (p.size == 1) and (abs(p[0]) <= tol)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# ========
|
|
46
|
+
# poly add
|
|
47
|
+
# ========
|
|
48
|
+
|
|
49
|
+
def _poly_add(a, b, tol):
|
|
50
|
+
|
|
51
|
+
return _poly_trim(poly.polyadd(a, b), tol)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# ========
|
|
55
|
+
# poly sub
|
|
56
|
+
# ========
|
|
57
|
+
|
|
58
|
+
def _poly_sub(a, b, tol):
|
|
59
|
+
|
|
60
|
+
return _poly_trim(poly.polysub(a, b), tol)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# =======
|
|
64
|
+
# ply mul
|
|
65
|
+
# =======
|
|
66
|
+
|
|
67
|
+
def _poly_mul(a, b, tol):
|
|
68
|
+
|
|
69
|
+
return _poly_trim(poly.polymul(a, b), tol)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# ==============
|
|
73
|
+
# poly div exact
|
|
74
|
+
# ==============
|
|
75
|
+
|
|
76
|
+
def _poly_div_exact(a, b, tol):
|
|
77
|
+
|
|
78
|
+
a = _poly_trim(a, tol)
|
|
79
|
+
b = _poly_trim(b, tol)
|
|
80
|
+
if _poly_is_zero(b, tol):
|
|
81
|
+
raise ZeroDivisionError("poly division by zero")
|
|
82
|
+
|
|
83
|
+
q, r = poly.polydiv(a, b)
|
|
84
|
+
r = _poly_trim(r, tol)
|
|
85
|
+
|
|
86
|
+
# Bareiss expects exact division; with floats it's only approximate.
|
|
87
|
+
# If the remainder is small, drop it.
|
|
88
|
+
scale = max(1.0, np.linalg.norm(a))
|
|
89
|
+
if np.linalg.norm(r) > 1e3 * tol * scale:
|
|
90
|
+
# Fallback: still drop remainder (keeps algorithm running).
|
|
91
|
+
# This is acceptable because we only need the resultant roots
|
|
92
|
+
# robustly, not exact symbolic coefficients.
|
|
93
|
+
pass
|
|
94
|
+
|
|
95
|
+
return _poly_trim(q, tol)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
# ================
|
|
99
|
+
# det bareiss poly
|
|
100
|
+
# ================
|
|
101
|
+
|
|
102
|
+
def _det_bareiss_poly(M, tol):
|
|
103
|
+
|
|
104
|
+
n = len(M)
|
|
105
|
+
A = [[_poly_trim(M[i][j], tol) for j in range(n)] for i in range(n)]
|
|
106
|
+
|
|
107
|
+
denom = np.array([1.0], dtype=complex)
|
|
108
|
+
sign = 1.0
|
|
109
|
+
|
|
110
|
+
for k in range(n - 1):
|
|
111
|
+
if _poly_is_zero(A[k][k], tol):
|
|
112
|
+
piv = -1
|
|
113
|
+
for i in range(k + 1, n):
|
|
114
|
+
if not _poly_is_zero(A[i][k], tol):
|
|
115
|
+
piv = i
|
|
116
|
+
break
|
|
117
|
+
if piv == -1:
|
|
118
|
+
return np.array([0.0], dtype=complex)
|
|
119
|
+
A[k], A[piv] = A[piv], A[k]
|
|
120
|
+
sign *= -1.0
|
|
121
|
+
|
|
122
|
+
pivot = A[k][k]
|
|
123
|
+
for i in range(k + 1, n):
|
|
124
|
+
for j in range(k + 1, n):
|
|
125
|
+
num = _poly_sub(_poly_mul(A[i][j], pivot, tol),
|
|
126
|
+
_poly_mul(A[i][k], A[k][j], tol),
|
|
127
|
+
tol)
|
|
128
|
+
if k > 0:
|
|
129
|
+
A[i][j] = _poly_div_exact(num, denom, tol)
|
|
130
|
+
else:
|
|
131
|
+
A[i][j] = _poly_trim(num, tol)
|
|
132
|
+
|
|
133
|
+
denom = pivot
|
|
134
|
+
|
|
135
|
+
return _poly_trim(sign * A[n - 1][n - 1], tol)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
# ===================
|
|
139
|
+
# cluster real points
|
|
140
|
+
# ===================
|
|
141
|
+
|
|
142
|
+
def _cluster_real_points(x, eps):
|
|
143
|
+
|
|
144
|
+
x = np.asarray(x, dtype=float).ravel()
|
|
145
|
+
if x.size == 0:
|
|
146
|
+
return x
|
|
147
|
+
x = np.sort(x)
|
|
148
|
+
uniq = []
|
|
149
|
+
for v in x:
|
|
150
|
+
if (len(uniq) == 0) or (abs(v - uniq[-1]) > eps):
|
|
151
|
+
uniq.append(float(v))
|
|
152
|
+
else:
|
|
153
|
+
uniq[-1] = 0.5 * (uniq[-1] + float(v))
|
|
154
|
+
return np.asarray(uniq, dtype=float)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
# =======================
|
|
158
|
+
# compute singular points
|
|
159
|
+
# =======================
|
|
160
|
+
|
|
161
|
+
def compute_singular_points(a_coeffs, tol=1e-12, real_tol=None):
|
|
162
|
+
"""
|
|
163
|
+
a_coeffs[i,j] is coefficient of z^i m^j, shape (deg_z+1, s+1).
|
|
164
|
+
|
|
165
|
+
Returns
|
|
166
|
+
-------
|
|
167
|
+
|
|
168
|
+
z_bp : complex array, roots of Disc_m(P)(z)
|
|
169
|
+
a_s_zero : complex array, roots of leading coefficient a_s(z)
|
|
170
|
+
support : list of (a,b) from real-ish branch points paired consecutively
|
|
171
|
+
"""
|
|
172
|
+
|
|
173
|
+
a_coeffs = np.asarray(a_coeffs)
|
|
174
|
+
s = a_coeffs.shape[1] - 1
|
|
175
|
+
if s < 1:
|
|
176
|
+
return (np.array([], dtype=complex),
|
|
177
|
+
np.array([], dtype=complex),
|
|
178
|
+
[])
|
|
179
|
+
|
|
180
|
+
if real_tol is None:
|
|
181
|
+
real_tol = 1e3 * tol
|
|
182
|
+
|
|
183
|
+
a = [_poly_trim(a_coeffs[:, j], tol) for j in range(s + 1)]
|
|
184
|
+
|
|
185
|
+
a_s = a[s]
|
|
186
|
+
a_s_zero = np.roots(a_s[::-1]) if a_s.size > 1 else \
|
|
187
|
+
np.array([], dtype=complex)
|
|
188
|
+
|
|
189
|
+
b = []
|
|
190
|
+
for j in range(s):
|
|
191
|
+
b.append(_poly_trim((j + 1) * a[j + 1], tol))
|
|
192
|
+
|
|
193
|
+
mdeg = s
|
|
194
|
+
ndeg = s - 1
|
|
195
|
+
N = mdeg + ndeg # 2s-1
|
|
196
|
+
|
|
197
|
+
z0 = np.array([0.0], dtype=complex)
|
|
198
|
+
M = [[z0 for _ in range(N)] for __ in range(N)]
|
|
199
|
+
|
|
200
|
+
for r in range(ndeg):
|
|
201
|
+
for j in range(mdeg + 1):
|
|
202
|
+
M[r][r + j] = a[j]
|
|
203
|
+
|
|
204
|
+
for r in range(mdeg):
|
|
205
|
+
rr = ndeg + r
|
|
206
|
+
for j in range(ndeg + 1):
|
|
207
|
+
M[rr][r + j] = b[j]
|
|
208
|
+
|
|
209
|
+
res = _det_bareiss_poly(M, tol)
|
|
210
|
+
if res.size <= 1:
|
|
211
|
+
z_bp = np.array([], dtype=complex)
|
|
212
|
+
else:
|
|
213
|
+
z_bp = np.roots(res[::-1])
|
|
214
|
+
|
|
215
|
+
support = []
|
|
216
|
+
if z_bp.size > 0:
|
|
217
|
+
zr = z_bp[np.abs(z_bp.imag) <= real_tol].real
|
|
218
|
+
zr = _cluster_real_points(zr, eps=1e2 * real_tol)
|
|
219
|
+
m2 = (zr.size // 2) * 2
|
|
220
|
+
for k in range(0, m2, 2):
|
|
221
|
+
a0 = float(zr[k])
|
|
222
|
+
b0 = float(zr[k + 1])
|
|
223
|
+
if b0 > a0:
|
|
224
|
+
support.append((a0, b0))
|
|
225
|
+
|
|
226
|
+
return z_bp, a_s_zero, support
|
|
@@ -21,6 +21,7 @@ from ._edge import evolve_edges, merge_edges
|
|
|
21
21
|
from ._decompress import decompress_newton
|
|
22
22
|
from ._decompress2 import decompress_coeffs
|
|
23
23
|
from ._homotopy import stieltjes_poly
|
|
24
|
+
from ._discriminant import compute_singular_points
|
|
24
25
|
from .._free_form._support import supp
|
|
25
26
|
from .._free_form._plot_util import plot_density
|
|
26
27
|
|
|
@@ -194,6 +195,7 @@ class AlgebraicForm(object):
|
|
|
194
195
|
|
|
195
196
|
# Initialize
|
|
196
197
|
self.a_coeffs = None # Polynomial coefficients
|
|
198
|
+
self.status = None # Fitting status
|
|
197
199
|
self.cache = {} # Cache inner-computations
|
|
198
200
|
|
|
199
201
|
# ===
|
|
@@ -207,10 +209,38 @@ class AlgebraicForm(object):
|
|
|
207
209
|
y_eps=2e-2,
|
|
208
210
|
x_pad=0.0,
|
|
209
211
|
triangular=None,
|
|
212
|
+
mu=None,
|
|
213
|
+
mu_reg=None,
|
|
210
214
|
normalize=False,
|
|
211
215
|
verbose=False):
|
|
212
216
|
"""
|
|
213
|
-
|
|
217
|
+
Fit polynomial.
|
|
218
|
+
|
|
219
|
+
Parameters
|
|
220
|
+
----------
|
|
221
|
+
|
|
222
|
+
deg_m : int
|
|
223
|
+
Degree :math:`\\deg_m(P)`
|
|
224
|
+
|
|
225
|
+
deg_z : int
|
|
226
|
+
Degree :math:`\\deg_z(P)`
|
|
227
|
+
|
|
228
|
+
mu : array_like, default=None
|
|
229
|
+
If an array :math:`[\\mu_0, \\mu_`, \\dots, \\mu_r]` is given,
|
|
230
|
+
it enforces the first :math:`r+1` moments. Note that :math:`\\mu_0`
|
|
231
|
+
should be :math:`1` to ensure unit mass. See also ``mu_reg`.
|
|
232
|
+
|
|
233
|
+
mu_reg: float, default=None
|
|
234
|
+
If `None`, the constraints ``mu`` are applied as hard constraint.
|
|
235
|
+
If a positive number, the constraints are applied as a soft
|
|
236
|
+
constraints with regularisation ``mu_reg``.
|
|
237
|
+
|
|
238
|
+
Notes
|
|
239
|
+
-----
|
|
240
|
+
|
|
241
|
+
When the input data are from an exact model, hard moment constraint is
|
|
242
|
+
preferred over soft constraint as the latter can hurt an already a good
|
|
243
|
+
fit.
|
|
214
244
|
"""
|
|
215
245
|
|
|
216
246
|
# Very important: reset cache whenever this function is called. This
|
|
@@ -231,11 +261,15 @@ class AlgebraicForm(object):
|
|
|
231
261
|
z_fit = filter_z_away_from_cuts(z_fit, self.support, y_eps=y_eps,
|
|
232
262
|
x_pad=x_pad)
|
|
233
263
|
|
|
264
|
+
# Fitting (w_inf = None means adaptive weight selection)
|
|
234
265
|
m1_fit = self.stieltjes(z_fit)
|
|
235
|
-
a_coeffs = fit_polynomial_relation(
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
266
|
+
a_coeffs, fit_metrics = fit_polynomial_relation(
|
|
267
|
+
z_fit, m1_fit, s=deg_m, deg_z=deg_z, ridge_lambda=reg,
|
|
268
|
+
triangular=triangular, normalize=normalize, mu=mu,
|
|
269
|
+
mu_reg=mu_reg)
|
|
270
|
+
|
|
271
|
+
# Compute global branhc points, zeros of leading a_j, and support
|
|
272
|
+
branch_points, a_s_zero, support = compute_singular_points(a_coeffs)
|
|
239
273
|
|
|
240
274
|
self.a_coeffs = a_coeffs
|
|
241
275
|
|
|
@@ -251,8 +285,12 @@ class AlgebraicForm(object):
|
|
|
251
285
|
eta=max(y_eps, 1e-2), n_x=128,
|
|
252
286
|
max_bad_frac=0.05)
|
|
253
287
|
|
|
254
|
-
status['
|
|
255
|
-
status['
|
|
288
|
+
status['branch_points'] = branch_points
|
|
289
|
+
status['a_s_zero'] = a_s_zero
|
|
290
|
+
status['res_max'] = float(res_max)
|
|
291
|
+
status['res_99_9'] = float(res_99_9)
|
|
292
|
+
status['fit_metrics'] = fit_metrics
|
|
293
|
+
self.status = status
|
|
256
294
|
|
|
257
295
|
if verbose:
|
|
258
296
|
print(f'fit residual max : {res_max:>0.4e}')
|
|
@@ -277,7 +315,7 @@ class AlgebraicForm(object):
|
|
|
277
315
|
else:
|
|
278
316
|
print('\nStieltjes sanity check: OK')
|
|
279
317
|
|
|
280
|
-
return a_coeffs, status
|
|
318
|
+
return a_coeffs, support, status
|
|
281
319
|
|
|
282
320
|
# =============
|
|
283
321
|
# generate grid
|
|
@@ -657,11 +695,16 @@ class AlgebraicForm(object):
|
|
|
657
695
|
z_query = x + 1j * self.delta
|
|
658
696
|
|
|
659
697
|
# Initial condition at t=0 (physical branch)
|
|
660
|
-
w0_list = self.stieltjes(z_query)
|
|
698
|
+
# w0_list = self.stieltjes(z_query)
|
|
699
|
+
stieltjes = numpy.vectorize(m_fn)
|
|
700
|
+
w0_list = stieltjes(z_query)
|
|
661
701
|
|
|
662
702
|
# Times
|
|
663
703
|
t = numpy.log(alpha)
|
|
664
704
|
|
|
705
|
+
# Ensure it starts from t = 0
|
|
706
|
+
t = numpy.concatenate([numpy.zeros(1), t])
|
|
707
|
+
|
|
665
708
|
# Evolve
|
|
666
709
|
W, ok = decompress_newton(
|
|
667
710
|
z_query, t, self.a_coeffs,
|
|
@@ -669,6 +712,9 @@ class AlgebraicForm(object):
|
|
|
669
712
|
|
|
670
713
|
rho = W.imag / numpy.pi
|
|
671
714
|
|
|
715
|
+
# Remove time zero
|
|
716
|
+
rho = rho[1:, :]
|
|
717
|
+
|
|
672
718
|
if verbose:
|
|
673
719
|
print("success rate per t:", ok.mean(axis=1))
|
|
674
720
|
|
|
@@ -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,9 +17,11 @@ 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
|
|
24
|
+
freealg/_algebraic_form/_discriminant.py
|
|
23
25
|
freealg/_algebraic_form/_edge.py
|
|
24
26
|
freealg/_algebraic_form/_homotopy.py
|
|
25
27
|
freealg/_algebraic_form/_sheets_util.py
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.7.5"
|
|
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
|
|
File without changes
|