freealg 0.1.11__py3-none-any.whl → 0.7.12__py3-none-any.whl
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/__init__.py +8 -2
- freealg/__version__.py +1 -1
- freealg/_algebraic_form/__init__.py +12 -0
- freealg/_algebraic_form/_branch_points.py +288 -0
- freealg/_algebraic_form/_constraints.py +139 -0
- freealg/_algebraic_form/_continuation_algebraic.py +706 -0
- freealg/_algebraic_form/_decompress.py +641 -0
- freealg/_algebraic_form/_decompress2.py +204 -0
- freealg/_algebraic_form/_edge.py +330 -0
- freealg/_algebraic_form/_homotopy.py +323 -0
- freealg/_algebraic_form/_moments.py +448 -0
- freealg/_algebraic_form/_sheets_util.py +145 -0
- freealg/_algebraic_form/_support.py +309 -0
- freealg/_algebraic_form/algebraic_form.py +1232 -0
- freealg/_free_form/__init__.py +16 -0
- freealg/{_chebyshev.py → _free_form/_chebyshev.py} +75 -43
- freealg/_free_form/_decompress.py +993 -0
- freealg/_free_form/_density_util.py +243 -0
- freealg/_free_form/_jacobi.py +359 -0
- freealg/_free_form/_linalg.py +508 -0
- freealg/{_pade.py → _free_form/_pade.py} +42 -208
- freealg/{_plot_util.py → _free_form/_plot_util.py} +37 -22
- freealg/{_sample.py → _free_form/_sample.py} +58 -22
- freealg/_free_form/_series.py +454 -0
- freealg/_free_form/_support.py +214 -0
- freealg/_free_form/free_form.py +1362 -0
- freealg/_geometric_form/__init__.py +13 -0
- freealg/_geometric_form/_continuation_genus0.py +175 -0
- freealg/_geometric_form/_continuation_genus1.py +275 -0
- freealg/_geometric_form/_elliptic_functions.py +174 -0
- freealg/_geometric_form/_sphere_maps.py +63 -0
- freealg/_geometric_form/_torus_maps.py +118 -0
- freealg/_geometric_form/geometric_form.py +1094 -0
- freealg/_util.py +56 -110
- freealg/distributions/__init__.py +7 -1
- freealg/distributions/_chiral_block.py +494 -0
- freealg/distributions/_deformed_marchenko_pastur.py +726 -0
- freealg/distributions/_deformed_wigner.py +386 -0
- freealg/distributions/_kesten_mckay.py +29 -15
- freealg/distributions/_marchenko_pastur.py +224 -95
- freealg/distributions/_meixner.py +47 -37
- freealg/distributions/_wachter.py +29 -17
- freealg/distributions/_wigner.py +27 -14
- freealg/visualization/__init__.py +12 -0
- freealg/visualization/_glue_util.py +32 -0
- freealg/visualization/_rgb_hsv.py +125 -0
- freealg-0.7.12.dist-info/METADATA +172 -0
- freealg-0.7.12.dist-info/RECORD +53 -0
- {freealg-0.1.11.dist-info → freealg-0.7.12.dist-info}/WHEEL +1 -1
- freealg/_decompress.py +0 -180
- freealg/_jacobi.py +0 -218
- freealg/_support.py +0 -85
- freealg/freeform.py +0 -967
- freealg-0.1.11.dist-info/METADATA +0 -140
- freealg-0.1.11.dist-info/RECORD +0 -24
- /freealg/{_damp.py → _free_form/_damp.py} +0 -0
- {freealg-0.1.11.dist-info → freealg-0.7.12.dist-info}/licenses/AUTHORS.txt +0 -0
- {freealg-0.1.11.dist-info → freealg-0.7.12.dist-info}/licenses/LICENSE.txt +0 -0
- {freealg-0.1.11.dist-info → freealg-0.7.12.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,309 @@
|
|
|
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
|
|
15
|
+
import numpy.polynomial.polynomial as poly
|
|
16
|
+
|
|
17
|
+
__all__ = ['compute_support']
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# ======================
|
|
21
|
+
# poly coeffs in m and z
|
|
22
|
+
# ======================
|
|
23
|
+
|
|
24
|
+
def _poly_coeffs_in_m_at_z(a_coeffs, z):
|
|
25
|
+
|
|
26
|
+
s = a_coeffs.shape[1] - 1
|
|
27
|
+
a = numpy.empty(s + 1, dtype=numpy.complex128)
|
|
28
|
+
for j in range(s + 1):
|
|
29
|
+
a[j] = poly.polyval(z, a_coeffs[:, j])
|
|
30
|
+
return a
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# ===============
|
|
34
|
+
# roots poly in m
|
|
35
|
+
# ===============
|
|
36
|
+
|
|
37
|
+
def _roots_poly_in_m(c_asc, tol=0.0):
|
|
38
|
+
|
|
39
|
+
c = numpy.asarray(c_asc, dtype=numpy.complex128).ravel()
|
|
40
|
+
if c.size <= 1:
|
|
41
|
+
return numpy.array([], dtype=numpy.complex128)
|
|
42
|
+
|
|
43
|
+
k = c.size - 1
|
|
44
|
+
while k > 0 and abs(c[k]) <= tol:
|
|
45
|
+
k -= 1
|
|
46
|
+
c = c[:k + 1]
|
|
47
|
+
if c.size <= 1:
|
|
48
|
+
return numpy.array([], dtype=numpy.complex128)
|
|
49
|
+
|
|
50
|
+
return numpy.roots(c[::-1])
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# ================
|
|
54
|
+
# dPdm coeffs at z
|
|
55
|
+
# ================
|
|
56
|
+
|
|
57
|
+
def _dPdm_coeffs_at_z(a_coeffs, z):
|
|
58
|
+
|
|
59
|
+
a = _poly_coeffs_in_m_at_z(a_coeffs, z)
|
|
60
|
+
s = a.size - 1
|
|
61
|
+
if s <= 0:
|
|
62
|
+
return numpy.array([0.0 + 0.0j], dtype=numpy.complex128)
|
|
63
|
+
d = numpy.empty(s, dtype=numpy.complex128)
|
|
64
|
+
for j in range(1, s + 1):
|
|
65
|
+
d[j - 1] = j * a[j]
|
|
66
|
+
return d
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# ==============
|
|
70
|
+
# P and partials
|
|
71
|
+
# ==============
|
|
72
|
+
|
|
73
|
+
def _P_and_partials(a_coeffs, z, m):
|
|
74
|
+
|
|
75
|
+
s = a_coeffs.shape[1] - 1
|
|
76
|
+
|
|
77
|
+
a = numpy.empty(s + 1, dtype=numpy.complex128)
|
|
78
|
+
da = numpy.empty(s + 1, dtype=numpy.complex128)
|
|
79
|
+
for j in range(s + 1):
|
|
80
|
+
a[j] = poly.polyval(z, a_coeffs[:, j])
|
|
81
|
+
da[j] = poly.polyval(z, poly.polyder(a_coeffs[:, j]))
|
|
82
|
+
|
|
83
|
+
mpow = 1.0 + 0.0j
|
|
84
|
+
P = 0.0 + 0.0j
|
|
85
|
+
Pz = 0.0 + 0.0j
|
|
86
|
+
for j in range(s + 1):
|
|
87
|
+
P += a[j] * mpow
|
|
88
|
+
Pz += da[j] * mpow
|
|
89
|
+
mpow *= m
|
|
90
|
+
|
|
91
|
+
Pm = 0.0 + 0.0j
|
|
92
|
+
Pmm = 0.0 + 0.0j
|
|
93
|
+
Pzm = 0.0 + 0.0j
|
|
94
|
+
for j in range(1, s + 1):
|
|
95
|
+
Pm += j * a[j] * (m ** (j - 1))
|
|
96
|
+
Pzm += j * da[j] * (m ** (j - 1))
|
|
97
|
+
for j in range(2, s + 1):
|
|
98
|
+
Pmm += j * (j - 1) * a[j] * (m ** (j - 2))
|
|
99
|
+
|
|
100
|
+
return P, Pz, Pm, Pzm, Pmm, a
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
# ===========
|
|
104
|
+
# newton edge
|
|
105
|
+
# ===========
|
|
106
|
+
|
|
107
|
+
def _newton_edge(a_coeffs, x0, m0, tol=1e-12, max_iter=50):
|
|
108
|
+
|
|
109
|
+
x = float(x0)
|
|
110
|
+
m = float(m0)
|
|
111
|
+
|
|
112
|
+
for _ in range(max_iter):
|
|
113
|
+
z = x + 0.0j
|
|
114
|
+
P, Pz, Pm, Pzm, Pmm, _ = _P_and_partials(a_coeffs, z, m)
|
|
115
|
+
|
|
116
|
+
f0 = float(numpy.real(P))
|
|
117
|
+
f1 = float(numpy.real(Pm))
|
|
118
|
+
|
|
119
|
+
j00 = float(numpy.real(Pz))
|
|
120
|
+
j01 = float(numpy.real(Pm))
|
|
121
|
+
j10 = float(numpy.real(Pzm))
|
|
122
|
+
j11 = float(numpy.real(Pmm))
|
|
123
|
+
|
|
124
|
+
det = j00 * j11 - j01 * j10
|
|
125
|
+
if det == 0.0 or (not numpy.isfinite(det)):
|
|
126
|
+
return x, m, False
|
|
127
|
+
|
|
128
|
+
dx = (-f0 * j11 + f1 * j01) / det
|
|
129
|
+
dm = (-j00 * f1 + j10 * f0) / det
|
|
130
|
+
|
|
131
|
+
x += dx
|
|
132
|
+
m += dm
|
|
133
|
+
|
|
134
|
+
if abs(dx) + abs(dm) < tol:
|
|
135
|
+
return x, m, True
|
|
136
|
+
|
|
137
|
+
return x, m, False
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
# =============
|
|
141
|
+
# cluster edges
|
|
142
|
+
# =============
|
|
143
|
+
|
|
144
|
+
def _cluster_edges(edges, x_tol):
|
|
145
|
+
|
|
146
|
+
if len(edges) == 0:
|
|
147
|
+
return numpy.array([], dtype=float)
|
|
148
|
+
|
|
149
|
+
edges = numpy.array(sorted(edges), dtype=float)
|
|
150
|
+
out = [edges[0]]
|
|
151
|
+
for e in edges[1:]:
|
|
152
|
+
if abs(e - out[-1]) > x_tol:
|
|
153
|
+
out.append(e)
|
|
154
|
+
return numpy.array(out, dtype=float)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
# =======================
|
|
158
|
+
# pick physical root at z
|
|
159
|
+
# =======================
|
|
160
|
+
|
|
161
|
+
def _pick_physical_root_at_z(a_coeffs, z, im_sign=+1):
|
|
162
|
+
|
|
163
|
+
a = _poly_coeffs_in_m_at_z(a_coeffs, z)
|
|
164
|
+
r = _roots_poly_in_m(a)
|
|
165
|
+
if r.size == 0:
|
|
166
|
+
return numpy.nan + 1j * numpy.nan
|
|
167
|
+
|
|
168
|
+
w_ref = -1.0 / z
|
|
169
|
+
idx = int(numpy.argmin(numpy.abs(r - w_ref)))
|
|
170
|
+
w = r[idx]
|
|
171
|
+
|
|
172
|
+
# optional strictness: if it violates Herglotz, declare failure
|
|
173
|
+
if not numpy.isfinite(w.real) or not numpy.isfinite(w.imag):
|
|
174
|
+
return w
|
|
175
|
+
if (im_sign * w.imag) <= 0.0:
|
|
176
|
+
return w
|
|
177
|
+
|
|
178
|
+
return w
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
# ===============
|
|
182
|
+
# compute support
|
|
183
|
+
# ===============
|
|
184
|
+
|
|
185
|
+
def compute_support(a_coeffs,
|
|
186
|
+
x_min,
|
|
187
|
+
x_max,
|
|
188
|
+
n_scan=4000,
|
|
189
|
+
y_eps=1e-3,
|
|
190
|
+
im_sign=+1,
|
|
191
|
+
root_tol=0.0,
|
|
192
|
+
edge_rel_tol=1e-6,
|
|
193
|
+
edge_x_cluster_tol=1e-3,
|
|
194
|
+
newton_tol=1e-12):
|
|
195
|
+
"""
|
|
196
|
+
Fast support from fitted polynomial using branch-point system P=0, Pm=0.
|
|
197
|
+
|
|
198
|
+
Returns
|
|
199
|
+
-------
|
|
200
|
+
support : list of (a,b)
|
|
201
|
+
info : dict (edges, rel_res_curve, etc.)
|
|
202
|
+
"""
|
|
203
|
+
|
|
204
|
+
a_coeffs = numpy.asarray(a_coeffs)
|
|
205
|
+
x_grid = numpy.linspace(float(x_min), float(x_max), int(n_scan))
|
|
206
|
+
|
|
207
|
+
# For each x, find best real critical point m (Pm=0) minimizing rel
|
|
208
|
+
# residual.
|
|
209
|
+
rel = numpy.full(x_grid.size, numpy.inf, dtype=float)
|
|
210
|
+
m_star = numpy.full(x_grid.size, numpy.nan, dtype=float)
|
|
211
|
+
|
|
212
|
+
for i, x in enumerate(x_grid):
|
|
213
|
+
z = x + 0.0j
|
|
214
|
+
dcoef = _dPdm_coeffs_at_z(a_coeffs, z)
|
|
215
|
+
mr = _roots_poly_in_m(dcoef, tol=root_tol)
|
|
216
|
+
|
|
217
|
+
best = numpy.inf
|
|
218
|
+
best_m = numpy.nan
|
|
219
|
+
|
|
220
|
+
for w in mr:
|
|
221
|
+
# accept nearly-real roots; numerical roots can have small imag
|
|
222
|
+
# part
|
|
223
|
+
if abs(w.imag) > 1e-6 * (1.0 + abs(w.real)):
|
|
224
|
+
continue
|
|
225
|
+
m = float(w.real)
|
|
226
|
+
P, _, _, _, _, a = _P_and_partials(a_coeffs, z, m)
|
|
227
|
+
|
|
228
|
+
denom = 1.0
|
|
229
|
+
am = 1.0
|
|
230
|
+
for j in range(a.size):
|
|
231
|
+
denom += abs(a[j]) * abs(am)
|
|
232
|
+
am *= m
|
|
233
|
+
|
|
234
|
+
r = abs(numpy.real(P)) / denom
|
|
235
|
+
if numpy.isfinite(r) and r < best:
|
|
236
|
+
best = float(r)
|
|
237
|
+
best_m = m
|
|
238
|
+
|
|
239
|
+
rel[i] = best
|
|
240
|
+
m_star[i] = best_m
|
|
241
|
+
|
|
242
|
+
# Pick candidate edges as local minima of rel(x), below an automatic scale.
|
|
243
|
+
rel_f = rel[numpy.isfinite(rel)]
|
|
244
|
+
if rel_f.size == 0:
|
|
245
|
+
return [], {"edges": numpy.array([], dtype=float), "n_edges": 0}
|
|
246
|
+
|
|
247
|
+
med = float(numpy.median(rel_f))
|
|
248
|
+
min_rel = float(numpy.min(rel_f))
|
|
249
|
+
|
|
250
|
+
# accept local minima up to a factor above the best one, but never abov
|
|
251
|
+
# background scale
|
|
252
|
+
thr = min(0.1 * med, max(float(edge_rel_tol), 1e4 * min_rel))
|
|
253
|
+
|
|
254
|
+
edges0 = []
|
|
255
|
+
seeds = []
|
|
256
|
+
|
|
257
|
+
for i in range(1, x_grid.size - 1):
|
|
258
|
+
if not numpy.isfinite(rel[i]):
|
|
259
|
+
continue
|
|
260
|
+
if rel[i] <= rel[i - 1] and rel[i] <= rel[i + 1] and rel[i] < thr and \
|
|
261
|
+
numpy.isfinite(m_star[i]):
|
|
262
|
+
edges0.append(float(x_grid[i]))
|
|
263
|
+
seeds.append((float(x_grid[i]), float(m_star[i])))
|
|
264
|
+
|
|
265
|
+
# Refine each seed by 2D Newton (x,m)
|
|
266
|
+
edges = []
|
|
267
|
+
for x0, m0 in seeds:
|
|
268
|
+
xe, me, ok = _newton_edge(a_coeffs, x0, m0, tol=newton_tol)
|
|
269
|
+
if ok and numpy.isfinite(xe) and numpy.isfinite(me):
|
|
270
|
+
edges.append(float(xe))
|
|
271
|
+
|
|
272
|
+
edges = _cluster_edges(edges, edge_x_cluster_tol)
|
|
273
|
+
edges.sort()
|
|
274
|
+
|
|
275
|
+
# Build support by testing midpoints between consecutive real edges
|
|
276
|
+
support = []
|
|
277
|
+
m_im_tol = 1e-10
|
|
278
|
+
|
|
279
|
+
for i in range(edges.size - 1):
|
|
280
|
+
a = float(edges[i])
|
|
281
|
+
b = float(edges[i + 1])
|
|
282
|
+
if b <= a:
|
|
283
|
+
continue
|
|
284
|
+
|
|
285
|
+
xmid = 0.5 * (a + b)
|
|
286
|
+
|
|
287
|
+
# roots of P(xmid, m) with real coefficients
|
|
288
|
+
a_m = _poly_coeffs_in_m_at_z(a_coeffs, xmid + 0.0j)
|
|
289
|
+
r = _roots_poly_in_m(a_m, tol=root_tol)
|
|
290
|
+
|
|
291
|
+
# interval is support iff there exists a non-real root (complex pair)
|
|
292
|
+
if numpy.any(numpy.abs(numpy.imag(r)) > m_im_tol):
|
|
293
|
+
support.append((a, b))
|
|
294
|
+
|
|
295
|
+
info = {
|
|
296
|
+
"edges": edges,
|
|
297
|
+
"n_edges": int(edges.size),
|
|
298
|
+
"support": support,
|
|
299
|
+
"n_support": int(len(support)),
|
|
300
|
+
"x_grid": x_grid,
|
|
301
|
+
"rel": rel,
|
|
302
|
+
"thr": float(thr),
|
|
303
|
+
"x_min": float(x_min),
|
|
304
|
+
"x_max": float(x_max),
|
|
305
|
+
"n_scan": int(n_scan),
|
|
306
|
+
"y_eps": float(y_eps),
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return support, info
|