freealg 0.7.1__tar.gz → 0.7.4__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.1 → freealg-0.7.4}/PKG-INFO +1 -1
- {freealg-0.7.1 → freealg-0.7.4}/freealg/__init__.py +1 -1
- freealg-0.7.4/freealg/__version__.py +1 -0
- {freealg-0.7.1 → freealg-0.7.4}/freealg/_algebraic_form/_continuation_algebraic.py +48 -5
- freealg-0.7.4/freealg/_algebraic_form/_decompress2.py +86 -0
- freealg-0.7.4/freealg/_algebraic_form/_homotopy.py +138 -0
- {freealg-0.7.1 → freealg-0.7.4}/freealg/_algebraic_form/algebraic_form.py +171 -114
- {freealg-0.7.1/freealg/_freeform → freealg-0.7.4/freealg/_free_form}/__init__.py +1 -1
- {freealg-0.7.1/freealg/_freeform → freealg-0.7.4/freealg/_free_form}/_linalg.py +1 -1
- {freealg-0.7.1/freealg/_freeform → freealg-0.7.4/freealg/_free_form}/_support.py +5 -5
- {freealg-0.7.1 → freealg-0.7.4}/freealg/distributions/_chiral_block.py +69 -15
- {freealg-0.7.1 → freealg-0.7.4}/freealg/distributions/_deformed_marchenko_pastur.py +71 -13
- {freealg-0.7.1 → freealg-0.7.4}/freealg/distributions/_deformed_wigner.py +46 -16
- {freealg-0.7.1 → freealg-0.7.4}/freealg/distributions/_kesten_mckay.py +1 -1
- {freealg-0.7.1 → freealg-0.7.4}/freealg/distributions/_marchenko_pastur.py +1 -1
- {freealg-0.7.1 → freealg-0.7.4}/freealg/distributions/_meixner.py +1 -1
- {freealg-0.7.1 → freealg-0.7.4}/freealg/distributions/_wachter.py +1 -1
- {freealg-0.7.1 → freealg-0.7.4}/freealg/distributions/_wigner.py +1 -1
- {freealg-0.7.1 → freealg-0.7.4}/freealg.egg-info/PKG-INFO +1 -1
- {freealg-0.7.1 → freealg-0.7.4}/freealg.egg-info/SOURCES.txt +15 -13
- freealg-0.7.1/freealg/__version__.py +0 -1
- {freealg-0.7.1 → freealg-0.7.4}/AUTHORS.txt +0 -0
- {freealg-0.7.1 → freealg-0.7.4}/CHANGELOG.rst +0 -0
- {freealg-0.7.1 → freealg-0.7.4}/LICENSE.txt +0 -0
- {freealg-0.7.1 → freealg-0.7.4}/MANIFEST.in +0 -0
- {freealg-0.7.1 → freealg-0.7.4}/README.rst +0 -0
- {freealg-0.7.1 → freealg-0.7.4}/freealg/_algebraic_form/__init__.py +0 -0
- {freealg-0.7.1 → freealg-0.7.4}/freealg/_algebraic_form/_decompress.py +0 -0
- {freealg-0.7.1 → freealg-0.7.4}/freealg/_algebraic_form/_edge.py +0 -0
- {freealg-0.7.1 → freealg-0.7.4}/freealg/_algebraic_form/_sheets_util.py +0 -0
- {freealg-0.7.1/freealg/_freeform → freealg-0.7.4/freealg/_free_form}/_chebyshev.py +0 -0
- {freealg-0.7.1/freealg/_freeform → freealg-0.7.4/freealg/_free_form}/_damp.py +0 -0
- {freealg-0.7.1/freealg/_freeform → freealg-0.7.4/freealg/_free_form}/_decompress.py +0 -0
- {freealg-0.7.1/freealg/_freeform → freealg-0.7.4/freealg/_free_form}/_density_util.py +0 -0
- {freealg-0.7.1/freealg/_freeform → freealg-0.7.4/freealg/_free_form}/_jacobi.py +0 -0
- {freealg-0.7.1/freealg/_freeform → freealg-0.7.4/freealg/_free_form}/_pade.py +0 -0
- {freealg-0.7.1/freealg/_freeform → freealg-0.7.4/freealg/_free_form}/_plot_util.py +0 -0
- {freealg-0.7.1/freealg/_freeform → freealg-0.7.4/freealg/_free_form}/_sample.py +0 -0
- {freealg-0.7.1/freealg/_freeform → freealg-0.7.4/freealg/_free_form}/_series.py +0 -0
- /freealg-0.7.1/freealg/_freeform/freeform.py → /freealg-0.7.4/freealg/_free_form/free_form.py +0 -0
- {freealg-0.7.1 → freealg-0.7.4}/freealg/_geometric_form/__init__.py +0 -0
- {freealg-0.7.1 → freealg-0.7.4}/freealg/_geometric_form/_continuation_genus0.py +0 -0
- {freealg-0.7.1 → freealg-0.7.4}/freealg/_geometric_form/_continuation_genus1.py +0 -0
- {freealg-0.7.1 → freealg-0.7.4}/freealg/_geometric_form/_elliptic_functions.py +0 -0
- {freealg-0.7.1 → freealg-0.7.4}/freealg/_geometric_form/_sphere_maps.py +0 -0
- {freealg-0.7.1 → freealg-0.7.4}/freealg/_geometric_form/_torus_maps.py +0 -0
- {freealg-0.7.1 → freealg-0.7.4}/freealg/_geometric_form/geometric_form.py +0 -0
- {freealg-0.7.1 → freealg-0.7.4}/freealg/_util.py +0 -0
- {freealg-0.7.1 → freealg-0.7.4}/freealg/distributions/__init__.py +0 -0
- {freealg-0.7.1 → freealg-0.7.4}/freealg/visualization/__init__.py +0 -0
- {freealg-0.7.1 → freealg-0.7.4}/freealg/visualization/_glue_util.py +0 -0
- {freealg-0.7.1 → freealg-0.7.4}/freealg/visualization/_rgb_hsv.py +0 -0
- {freealg-0.7.1 → freealg-0.7.4}/freealg.egg-info/dependency_links.txt +0 -0
- {freealg-0.7.1 → freealg-0.7.4}/freealg.egg-info/not-zip-safe +0 -0
- {freealg-0.7.1 → freealg-0.7.4}/freealg.egg-info/requires.txt +0 -0
- {freealg-0.7.1 → freealg-0.7.4}/freealg.egg-info/top_level.txt +0 -0
- {freealg-0.7.1 → freealg-0.7.4}/pyproject.toml +0 -0
- {freealg-0.7.1 → freealg-0.7.4}/requirements.txt +0 -0
- {freealg-0.7.1 → freealg-0.7.4}/setup.cfg +0 -0
- {freealg-0.7.1 → freealg-0.7.4}/setup.py +0 -0
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
# under the terms of the license found in the LICENSE.txt file in the root
|
|
7
7
|
# directory of this source tree.
|
|
8
8
|
|
|
9
|
-
from .
|
|
9
|
+
from ._free_form import FreeForm, eigvalsh, cond, norm, trace, slogdet, supp, \
|
|
10
10
|
sample, kde
|
|
11
11
|
from ._algebraic_form import AlgebraicForm
|
|
12
12
|
from ._geometric_form import GeometricForm
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.7.4"
|
|
@@ -14,10 +14,50 @@
|
|
|
14
14
|
import numpy
|
|
15
15
|
from .._geometric_form._continuation_genus0 import joukowski_z
|
|
16
16
|
|
|
17
|
-
__all__ = [
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
__all__ = ['sample_z_joukowski', 'filter_z_away_from_cuts', 'powers',
|
|
18
|
+
'fit_polynomial_relation', 'eval_P', 'eval_roots',
|
|
19
|
+
'build_sheets_from_roots']
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# ======================
|
|
23
|
+
# normalize coefficients
|
|
24
|
+
# ======================
|
|
25
|
+
|
|
26
|
+
def _normalize_coefficients(arr):
|
|
27
|
+
"""
|
|
28
|
+
Trim rows and columns on the sides (equivalent to factorizing or reducing
|
|
29
|
+
degree) and normalize so that the sum of the first column is one.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
a = numpy.asarray(arr).copy()
|
|
33
|
+
|
|
34
|
+
if a.size == 0:
|
|
35
|
+
return a
|
|
36
|
+
|
|
37
|
+
# --- Trim zero rows (top and bottom) ---
|
|
38
|
+
non_zero_rows = numpy.any(a != 0, axis=1)
|
|
39
|
+
if not numpy.any(non_zero_rows):
|
|
40
|
+
return a[:0, :0]
|
|
41
|
+
|
|
42
|
+
first_row = numpy.argmax(non_zero_rows)
|
|
43
|
+
last_row = len(non_zero_rows) - numpy.argmax(non_zero_rows[::-1])
|
|
44
|
+
a = a[first_row:last_row, :]
|
|
45
|
+
|
|
46
|
+
# --- Trim zero columns (left and right) ---
|
|
47
|
+
non_zero_cols = numpy.any(a != 0, axis=0)
|
|
48
|
+
if not numpy.any(non_zero_cols):
|
|
49
|
+
return a[:, :0]
|
|
50
|
+
|
|
51
|
+
first_col = numpy.argmax(non_zero_cols)
|
|
52
|
+
last_col = len(non_zero_cols) - numpy.argmax(non_zero_cols[::-1])
|
|
53
|
+
a = a[:, first_col:last_col]
|
|
54
|
+
|
|
55
|
+
# --- Normalize so first column sums to 1 ---
|
|
56
|
+
col_sum = numpy.sum(numpy.abs(a[:, 0]))
|
|
57
|
+
if col_sum != 0:
|
|
58
|
+
a = a / col_sum
|
|
59
|
+
|
|
60
|
+
return a
|
|
21
61
|
|
|
22
62
|
|
|
23
63
|
# ==================
|
|
@@ -91,7 +131,7 @@ def powers(x, deg):
|
|
|
91
131
|
# =======================
|
|
92
132
|
|
|
93
133
|
def fit_polynomial_relation(z, m, s, deg_z, ridge_lambda=0.0, weights=None,
|
|
94
|
-
triangular=None):
|
|
134
|
+
triangular=None, normalize=False):
|
|
95
135
|
|
|
96
136
|
z = numpy.asarray(z, dtype=complex).ravel()
|
|
97
137
|
m = numpy.asarray(m, dtype=complex).ravel()
|
|
@@ -167,6 +207,9 @@ def fit_polynomial_relation(z, m, s, deg_z, ridge_lambda=0.0, weights=None,
|
|
|
167
207
|
for k, (i, j) in enumerate(pairs):
|
|
168
208
|
full[i, j] = coef[k]
|
|
169
209
|
|
|
210
|
+
if normalize:
|
|
211
|
+
full = _normalize_coefficients(full)
|
|
212
|
+
|
|
170
213
|
return full
|
|
171
214
|
|
|
172
215
|
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# =======
|
|
2
|
+
# Imports
|
|
3
|
+
# =======
|
|
4
|
+
|
|
5
|
+
import numpy
|
|
6
|
+
from scipy.special import comb
|
|
7
|
+
from ._continuation_algebraic import _normalize_coefficients
|
|
8
|
+
|
|
9
|
+
__all__ = ['decompress_coeffs']
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# =================
|
|
13
|
+
# decompress_coeffs
|
|
14
|
+
# =================
|
|
15
|
+
|
|
16
|
+
def decompress_coeffs(a, t, normalize=True):
|
|
17
|
+
"""
|
|
18
|
+
Compute the decompressed coefficients A[r, s](t) induced by
|
|
19
|
+
the transform Q_t(z, m) = m^L P(z + (1 - e^{-t}) / m, e^t m).
|
|
20
|
+
|
|
21
|
+
Parameters
|
|
22
|
+
----------
|
|
23
|
+
a : array_like of float, shape (L+1, K+1)
|
|
24
|
+
Coefficients defining P(z, m) in the monomial basis:
|
|
25
|
+
P(z, m) = sum_{j=0..L} sum_{k=0..K} a[j, k] z^j m^k.
|
|
26
|
+
t : float
|
|
27
|
+
Time parameter.
|
|
28
|
+
|
|
29
|
+
Returns
|
|
30
|
+
-------
|
|
31
|
+
A : ndarray, shape (L+1, L+K+1)
|
|
32
|
+
Coefficients A[r, s](t) such that
|
|
33
|
+
sum_{r=0..L} sum_{s=0..L+K} A[r, s](t) z^r m^s = 0,
|
|
34
|
+
normalized by normalize_coefficients.
|
|
35
|
+
"""
|
|
36
|
+
a = numpy.asarray(a)
|
|
37
|
+
if a.ndim != 2:
|
|
38
|
+
raise ValueError("a must be a 2D array-like of shape (L+1, K+1).")
|
|
39
|
+
|
|
40
|
+
l_degree = a.shape[0] - 1
|
|
41
|
+
k_degree = a.shape[1] - 1
|
|
42
|
+
|
|
43
|
+
c = 1.0 - numpy.exp(-t)
|
|
44
|
+
|
|
45
|
+
# Scale columns of a by e^{t k}: scaled[j, k] = a[j, k] e^{t k}.
|
|
46
|
+
exp_factors = numpy.exp(numpy.arange(k_degree + 1) * t)
|
|
47
|
+
scaled = a * exp_factors
|
|
48
|
+
|
|
49
|
+
# Output coefficients.
|
|
50
|
+
out_dtype = numpy.result_type(a, float)
|
|
51
|
+
a_out = numpy.zeros((l_degree + 1, l_degree + k_degree + 1),
|
|
52
|
+
dtype=out_dtype)
|
|
53
|
+
|
|
54
|
+
# Precompute binomial(j, r) * c^{j-r} for all j, r (lower-triangular).
|
|
55
|
+
j_inds = numpy.arange(l_degree + 1)[:, None]
|
|
56
|
+
r_inds = numpy.arange(l_degree + 1)[None, :]
|
|
57
|
+
mask = r_inds <= j_inds
|
|
58
|
+
|
|
59
|
+
binom_weights = numpy.zeros((l_degree + 1, l_degree + 1), dtype=float)
|
|
60
|
+
binom_weights[mask] = comb(j_inds, r_inds, exact=False)[mask]
|
|
61
|
+
binom_weights[mask] *= (c ** (j_inds - r_inds))[mask]
|
|
62
|
+
|
|
63
|
+
# Main accumulation:
|
|
64
|
+
# For fixed j and r, add:
|
|
65
|
+
# A[r, (L - j + r) + k] += binom_weights[j, r] * scaled[j, k],
|
|
66
|
+
# for k = 0..K.
|
|
67
|
+
for j in range(l_degree + 1):
|
|
68
|
+
row_scaled = scaled[j]
|
|
69
|
+
if numpy.all(row_scaled == 0):
|
|
70
|
+
continue
|
|
71
|
+
|
|
72
|
+
base0 = l_degree - j
|
|
73
|
+
row_b = binom_weights[j]
|
|
74
|
+
|
|
75
|
+
for r in range(j + 1):
|
|
76
|
+
coeff = row_b[r]
|
|
77
|
+
if coeff == 0:
|
|
78
|
+
continue
|
|
79
|
+
|
|
80
|
+
start = base0 + r
|
|
81
|
+
a_out[r, start:start + (k_degree + 1)] += coeff * row_scaled
|
|
82
|
+
|
|
83
|
+
if normalize:
|
|
84
|
+
return _normalize_coefficients(a_out)
|
|
85
|
+
|
|
86
|
+
return a_out
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# =======
|
|
2
|
+
# Imports
|
|
3
|
+
# =======
|
|
4
|
+
|
|
5
|
+
import numpy
|
|
6
|
+
|
|
7
|
+
__all__ = ['stieltjes_poly']
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# =====================
|
|
11
|
+
# stieltjes select root
|
|
12
|
+
# =====================
|
|
13
|
+
|
|
14
|
+
def stieltjes_select_root(roots, z, w_prev=None):
|
|
15
|
+
"""
|
|
16
|
+
Select the Stieltjes-branch root among candidates at a given z.
|
|
17
|
+
|
|
18
|
+
Parameters
|
|
19
|
+
----------
|
|
20
|
+
roots : array_like of complex
|
|
21
|
+
Candidate roots for m at the given z.
|
|
22
|
+
z : complex
|
|
23
|
+
Evaluation point. The Stieltjes/Herglotz branch satisfies
|
|
24
|
+
sign(Im(m)) = sign(Im(z)) away from the real axis.
|
|
25
|
+
w_prev : complex or None, optional
|
|
26
|
+
Previous continuation value used to enforce continuity. If None,
|
|
27
|
+
the asymptotic target -1/z is used.
|
|
28
|
+
|
|
29
|
+
Returns
|
|
30
|
+
-------
|
|
31
|
+
w : complex
|
|
32
|
+
Selected root corresponding to the Stieltjes branch.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
z = complex(z)
|
|
36
|
+
roots = numpy.asarray(roots, dtype=numpy.complex128).ravel()
|
|
37
|
+
|
|
38
|
+
if roots.size == 0:
|
|
39
|
+
raise ValueError("roots must contain at least one candidate root.")
|
|
40
|
+
|
|
41
|
+
desired_sign = numpy.sign(z.imag)
|
|
42
|
+
|
|
43
|
+
if w_prev is None:
|
|
44
|
+
target = -1.0 / z
|
|
45
|
+
else:
|
|
46
|
+
target = complex(w_prev)
|
|
47
|
+
|
|
48
|
+
# Apply a soft Herglotz sign filter: prefer roots with Im(w) having the
|
|
49
|
+
# same sign as Im(z), allowing tiny numerical violations near the axis.
|
|
50
|
+
imag_roots = numpy.imag(roots)
|
|
51
|
+
|
|
52
|
+
good = roots[numpy.sign(imag_roots) == desired_sign]
|
|
53
|
+
if good.size == 0:
|
|
54
|
+
good = roots[(imag_roots * desired_sign) > -1e-12]
|
|
55
|
+
|
|
56
|
+
candidates = good if good.size > 0 else roots
|
|
57
|
+
idx = int(numpy.argmin(numpy.abs(candidates - target)))
|
|
58
|
+
return candidates[idx]
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
# ==============
|
|
62
|
+
# stieltjes poly
|
|
63
|
+
# ==============
|
|
64
|
+
|
|
65
|
+
def stieltjes_poly(z, a, eps=None, height=1e4, steps=100):
|
|
66
|
+
"""
|
|
67
|
+
Evaluate the Stieltjes-branch solution m(z) of an algebraic equation.
|
|
68
|
+
|
|
69
|
+
The coefficients `a` define a polynomial relation
|
|
70
|
+
P(z, m) = 0,
|
|
71
|
+
where P is a polynomial in z and m with monomial-basis coefficients
|
|
72
|
+
arranged so that for fixed z, the coefficients of the polynomial in m
|
|
73
|
+
can be assembled from powers of z.
|
|
74
|
+
|
|
75
|
+
Parameters
|
|
76
|
+
----------
|
|
77
|
+
z : complex
|
|
78
|
+
Evaluation point. Must be a single value.
|
|
79
|
+
a : ndarray, shape (L, K)
|
|
80
|
+
Coefficient matrix defining P(z, m) in the monomial basis.
|
|
81
|
+
eps : float or None, optional
|
|
82
|
+
If Im(z) == 0, use z + i*eps as the boundary evaluation point.
|
|
83
|
+
If None and Im(z) == 0, eps is set to 1e-8 * max(1, |z|).
|
|
84
|
+
height : float, optional
|
|
85
|
+
Imaginary height used for the starting point z0 in the same
|
|
86
|
+
half-plane as the evaluation point.
|
|
87
|
+
steps : int, optional
|
|
88
|
+
Number of continuation steps along the homotopy path.
|
|
89
|
+
|
|
90
|
+
Returns
|
|
91
|
+
-------
|
|
92
|
+
w : complex
|
|
93
|
+
Value of the Stieltjes-branch solution m(z) (or m(z+i*eps) if z is
|
|
94
|
+
real).
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
z = complex(z)
|
|
98
|
+
a = numpy.asarray(a)
|
|
99
|
+
|
|
100
|
+
if a.ndim != 2:
|
|
101
|
+
raise ValueError('a must be a 2D array.')
|
|
102
|
+
|
|
103
|
+
if steps < 1:
|
|
104
|
+
raise ValueError("steps must be a positive integer.")
|
|
105
|
+
|
|
106
|
+
a_l, _ = a.shape
|
|
107
|
+
|
|
108
|
+
def poly_coeffs_m(z_val):
|
|
109
|
+
z_powers = z_val ** numpy.arange(a_l)
|
|
110
|
+
return (z_powers @ a)[::-1]
|
|
111
|
+
|
|
112
|
+
def poly_roots(z_val):
|
|
113
|
+
coeffs = numpy.asarray(poly_coeffs_m(z_val), dtype=numpy.complex128)
|
|
114
|
+
return numpy.roots(coeffs)
|
|
115
|
+
|
|
116
|
+
# If user asked for a real-axis value, interpret as boundary value from C+.
|
|
117
|
+
if z.imag == 0.0:
|
|
118
|
+
if eps is None:
|
|
119
|
+
eps = 1e-8 * max(1.0, abs(z))
|
|
120
|
+
z_eval = z + 1j * float(eps)
|
|
121
|
+
else:
|
|
122
|
+
z_eval = z
|
|
123
|
+
|
|
124
|
+
half_sign = numpy.sign(z_eval.imag)
|
|
125
|
+
if half_sign == 0.0:
|
|
126
|
+
half_sign = 1.0
|
|
127
|
+
|
|
128
|
+
z0 = 1j * float(half_sign) * float(height)
|
|
129
|
+
|
|
130
|
+
# Initialize at z0 via asymptotic / Im-sign selection.
|
|
131
|
+
w_prev = stieltjes_select_root(poly_roots(z0), z0, w_prev=None)
|
|
132
|
+
|
|
133
|
+
# Straight-line homotopy from z0 to z_eval.
|
|
134
|
+
for tau in numpy.linspace(0.0, 1.0, int(steps) + 1)[1:]:
|
|
135
|
+
z_tau = z0 + tau * (z_eval - z0)
|
|
136
|
+
w_prev = stieltjes_select_root(poly_roots(z_tau), z_tau, w_prev=w_prev)
|
|
137
|
+
|
|
138
|
+
return w_prev
|