freealg 0.7.11__py3-none-any.whl → 0.7.14__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 +2 -2
- freealg/__version__.py +1 -1
- freealg/_algebraic_form/__init__.py +2 -1
- freealg/_algebraic_form/_constraints.py +53 -12
- freealg/_algebraic_form/_cusp.py +357 -0
- freealg/_algebraic_form/_cusp_wrap.py +268 -0
- freealg/_algebraic_form/_decompress.py +330 -381
- freealg/_algebraic_form/_decompress2.py +120 -0
- freealg/_algebraic_form/_decompress4.py +739 -0
- freealg/_algebraic_form/_decompress5.py +738 -0
- freealg/_algebraic_form/_decompress6.py +492 -0
- freealg/_algebraic_form/_decompress7.py +355 -0
- freealg/_algebraic_form/_decompress8.py +369 -0
- freealg/_algebraic_form/_decompress9.py +363 -0
- freealg/_algebraic_form/_decompress_new.py +431 -0
- freealg/_algebraic_form/_decompress_new_2.py +1631 -0
- freealg/_algebraic_form/_decompress_util.py +172 -0
- freealg/_algebraic_form/_edge.py +46 -68
- freealg/_algebraic_form/_homotopy.py +62 -30
- freealg/_algebraic_form/_homotopy2.py +289 -0
- freealg/_algebraic_form/_homotopy3.py +215 -0
- freealg/_algebraic_form/_homotopy4.py +320 -0
- freealg/_algebraic_form/_homotopy5.py +185 -0
- freealg/_algebraic_form/_moments.py +43 -57
- freealg/_algebraic_form/_support.py +132 -177
- freealg/_algebraic_form/algebraic_form.py +163 -30
- freealg/distributions/__init__.py +3 -1
- freealg/distributions/_compound_poisson.py +464 -0
- freealg/distributions/_deformed_marchenko_pastur.py +51 -0
- freealg/distributions/_deformed_wigner.py +44 -0
- {freealg-0.7.11.dist-info → freealg-0.7.14.dist-info}/METADATA +2 -1
- {freealg-0.7.11.dist-info → freealg-0.7.14.dist-info}/RECORD +36 -20
- {freealg-0.7.11.dist-info → freealg-0.7.14.dist-info}/WHEEL +1 -1
- {freealg-0.7.11.dist-info → freealg-0.7.14.dist-info}/licenses/AUTHORS.txt +0 -0
- {freealg-0.7.11.dist-info → freealg-0.7.14.dist-info}/licenses/LICENSE.txt +0 -0
- {freealg-0.7.11.dist-info → freealg-0.7.14.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,268 @@
|
|
|
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 scipy.optimize as opt
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# ================
|
|
19
|
+
# poly coeffs in y
|
|
20
|
+
# ================
|
|
21
|
+
|
|
22
|
+
def _poly_coeffs_in_y(a_coeffs, zeta):
|
|
23
|
+
"""
|
|
24
|
+
Build coefficients c_j(zeta) so that P(zeta, y) = sum_j c_j(zeta) y^j.
|
|
25
|
+
|
|
26
|
+
Assumes a_coeffs[i, j] multiplies z^i y^j (same layout as eval_P in
|
|
27
|
+
_continuation_algebraic). Returns coefficients in ascending powers of y.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
a = numpy.asarray(a_coeffs)
|
|
31
|
+
deg_z = a.shape[0] - 1
|
|
32
|
+
deg_y = a.shape[1] - 1
|
|
33
|
+
|
|
34
|
+
# c_j(zeta) = sum_i a[i,j] zeta^i
|
|
35
|
+
z_pows = numpy.power(zeta, numpy.arange(deg_z + 1, dtype=numpy.int64))
|
|
36
|
+
c = numpy.empty((deg_y + 1,), dtype=numpy.complex128)
|
|
37
|
+
for j in range(deg_y + 1):
|
|
38
|
+
c[j] = numpy.dot(a[:, j], z_pows)
|
|
39
|
+
|
|
40
|
+
return c
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# ===================
|
|
44
|
+
# pick realish root y
|
|
45
|
+
# ===================
|
|
46
|
+
|
|
47
|
+
def _pick_realish_root_y(a_coeffs, zeta):
|
|
48
|
+
"""
|
|
49
|
+
Pick a reasonable real-ish root y of P(zeta, y)=0 to seed Newton.
|
|
50
|
+
|
|
51
|
+
Returns a float (real part of the selected root).
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
c_asc = _poly_coeffs_in_y(a_coeffs, zeta) # ascending in y
|
|
55
|
+
# numpy.roots wants descending order
|
|
56
|
+
c_desc = c_asc[::-1]
|
|
57
|
+
# strip leading ~0 coefficients
|
|
58
|
+
k = 0
|
|
59
|
+
while k < len(c_desc) and abs(c_desc[k]) == 0:
|
|
60
|
+
k += 1
|
|
61
|
+
c_desc = c_desc[k:] if k < len(c_desc) else c_desc
|
|
62
|
+
|
|
63
|
+
if len(c_desc) <= 1:
|
|
64
|
+
return 0.0
|
|
65
|
+
|
|
66
|
+
roots = numpy.roots(c_desc)
|
|
67
|
+
# choose the root closest to the real axis
|
|
68
|
+
j = int(numpy.argmin(numpy.abs(numpy.imag(roots))))
|
|
69
|
+
return float(numpy.real(roots[j]))
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# =========
|
|
73
|
+
# cusp wrap
|
|
74
|
+
# =========
|
|
75
|
+
|
|
76
|
+
def cusp_wrap(self, t_grid, edge_kwargs=None, max_iter=80, tol=1e-12,
|
|
77
|
+
verbose=False):
|
|
78
|
+
|
|
79
|
+
if edge_kwargs is None:
|
|
80
|
+
edge_kwargs = {}
|
|
81
|
+
|
|
82
|
+
t_grid = numpy.asarray(t_grid, dtype=float).ravel()
|
|
83
|
+
|
|
84
|
+
# allow scalar / len-1 input
|
|
85
|
+
if t_grid.size == 1:
|
|
86
|
+
t0 = float(t_grid[0])
|
|
87
|
+
dt = 0.25
|
|
88
|
+
t_grid = numpy.linspace(max(0.0, t0 - dt), t0 + dt, 21)
|
|
89
|
+
|
|
90
|
+
if t_grid.size < 5:
|
|
91
|
+
raise ValueError("t_grid too small")
|
|
92
|
+
|
|
93
|
+
def gap_at(tt):
|
|
94
|
+
ce, _, _ = self.edge(numpy.array([float(tt)]), verbose=False,
|
|
95
|
+
**edge_kwargs)
|
|
96
|
+
return float(ce[0, 2].real - ce[0, 1].real)
|
|
97
|
+
|
|
98
|
+
# coarse grid gap
|
|
99
|
+
ce, _, _ = self.edge(t_grid, verbose=False, **edge_kwargs)
|
|
100
|
+
gap = ce[:, 2].real - ce[:, 1].real
|
|
101
|
+
m = numpy.isfinite(gap)
|
|
102
|
+
|
|
103
|
+
if numpy.count_nonzero(m) < 2:
|
|
104
|
+
return {"success": False, "reason": "gap is not finite on grid"}
|
|
105
|
+
|
|
106
|
+
tg = t_grid[m]
|
|
107
|
+
gg = gap[m]
|
|
108
|
+
|
|
109
|
+
# candidate bracket indices from coarse grid
|
|
110
|
+
s = numpy.sign(gg)
|
|
111
|
+
idx = numpy.where(s[:-1] * s[1:] < 0)[0]
|
|
112
|
+
|
|
113
|
+
bracketed = False
|
|
114
|
+
t_star = None
|
|
115
|
+
|
|
116
|
+
# robust: verify sign change using the true gap_at before calling brentq
|
|
117
|
+
if idx.size > 0:
|
|
118
|
+
for ii in idx[:5]: # try a few brackets
|
|
119
|
+
tL, tR = float(tg[ii]), float(tg[ii + 1])
|
|
120
|
+
gL = gap_at(tL)
|
|
121
|
+
gR = gap_at(tR)
|
|
122
|
+
if numpy.isfinite(gL) and numpy.isfinite(gR) and (gL * gR < 0.0):
|
|
123
|
+
t_star = float(opt.brentq(gap_at, tL, tR, xtol=1e-12,
|
|
124
|
+
rtol=1e-12, maxiter=200))
|
|
125
|
+
bracketed = True
|
|
126
|
+
break
|
|
127
|
+
|
|
128
|
+
# fallback: minimizer of |gap| on the coarse grid
|
|
129
|
+
if t_star is None:
|
|
130
|
+
i0 = int(numpy.argmin(numpy.abs(gg)))
|
|
131
|
+
t_star = float(tg[i0])
|
|
132
|
+
bracketed = False
|
|
133
|
+
|
|
134
|
+
# --- seed (zeta,y) correctly using zeta = x + (tau-1)/y ---
|
|
135
|
+
ce_star, _, _ = self.edge(numpy.array([t_star]), verbose=False,
|
|
136
|
+
**edge_kwargs)
|
|
137
|
+
x_seed = float(ce_star[0, 1].real) # inner edge b1
|
|
138
|
+
tau = float(numpy.exp(t_star))
|
|
139
|
+
c = tau - 1.0
|
|
140
|
+
|
|
141
|
+
a = numpy.asarray(self.a_coeffs, dtype=numpy.complex128)
|
|
142
|
+
deg_z = a.shape[0] - 1
|
|
143
|
+
deg_y = a.shape[1] - 1
|
|
144
|
+
|
|
145
|
+
def poly_in_y(zeta):
|
|
146
|
+
zi = numpy.power(zeta, numpy.arange(deg_z + 1, dtype=numpy.int64))
|
|
147
|
+
c_asc = numpy.array([numpy.dot(a[:, j], zi) for j in range(deg_y + 1)],
|
|
148
|
+
dtype=numpy.complex128)
|
|
149
|
+
return c_asc
|
|
150
|
+
|
|
151
|
+
zeta0 = float(x_seed)
|
|
152
|
+
c_asc = poly_in_y(zeta0)
|
|
153
|
+
roots = numpy.roots(c_asc[::-1])
|
|
154
|
+
j = int(numpy.argmin(numpy.abs(numpy.imag(roots))))
|
|
155
|
+
y0 = float(numpy.real(roots[j]))
|
|
156
|
+
if abs(y0) < 1e-12:
|
|
157
|
+
jj = numpy.argsort(numpy.abs(numpy.imag(roots)))
|
|
158
|
+
for k in jj:
|
|
159
|
+
if abs(numpy.real(roots[k])) > 1e-8:
|
|
160
|
+
y0 = float(numpy.real(roots[k]))
|
|
161
|
+
break
|
|
162
|
+
|
|
163
|
+
zeta_seed = float(x_seed + c / y0)
|
|
164
|
+
y_seed = float(y0)
|
|
165
|
+
|
|
166
|
+
def P_all(zeta, y):
|
|
167
|
+
zeta = numpy.complex128(zeta)
|
|
168
|
+
y = numpy.complex128(y)
|
|
169
|
+
zi = numpy.power(zeta, numpy.arange(deg_z + 1))
|
|
170
|
+
yj = numpy.power(y, numpy.arange(deg_y + 1))
|
|
171
|
+
P = numpy.sum(a * zi[:, None] * yj[None, :])
|
|
172
|
+
|
|
173
|
+
if deg_z >= 1:
|
|
174
|
+
iz = numpy.arange(1, deg_z + 1)
|
|
175
|
+
Pz = numpy.sum((a[iz, :] * iz[:, None]) *
|
|
176
|
+
numpy.power(zeta, iz - 1)[:, None] * yj[None, :])
|
|
177
|
+
else:
|
|
178
|
+
Pz = 0.0 + 0.0j
|
|
179
|
+
|
|
180
|
+
if deg_y >= 1:
|
|
181
|
+
jy = numpy.arange(1, deg_y + 1)
|
|
182
|
+
Py = numpy.sum((a[:, jy] * jy[None, :]) * zi[:, None] *
|
|
183
|
+
numpy.power(y, jy - 1)[None, :])
|
|
184
|
+
else:
|
|
185
|
+
Py = 0.0 + 0.0j
|
|
186
|
+
|
|
187
|
+
if deg_z >= 2:
|
|
188
|
+
iz = numpy.arange(2, deg_z + 1)
|
|
189
|
+
Pzz = numpy.sum((a[iz, :] * (iz * (iz - 1))[:, None]) *
|
|
190
|
+
numpy.power(zeta, iz - 2)[:, None] * yj[None, :])
|
|
191
|
+
else:
|
|
192
|
+
Pzz = 0.0 + 0.0j
|
|
193
|
+
|
|
194
|
+
if deg_y >= 2:
|
|
195
|
+
jy = numpy.arange(2, deg_y + 1)
|
|
196
|
+
Pyy = numpy.sum((a[:, jy] * (jy * (jy - 1))[None, :]) *
|
|
197
|
+
zi[:, None] * numpy.power(y, jy - 2)[None, :])
|
|
198
|
+
else:
|
|
199
|
+
Pyy = 0.0 + 0.0j
|
|
200
|
+
|
|
201
|
+
if (deg_z >= 1) and (deg_y >= 1):
|
|
202
|
+
iz = numpy.arange(1, deg_z + 1)
|
|
203
|
+
jy = numpy.arange(1, deg_y + 1)
|
|
204
|
+
coeff = a[numpy.ix_(iz, jy)] * (iz[:, None] * jy[None, :])
|
|
205
|
+
Pzy = numpy.sum(coeff * numpy.power(zeta, iz - 1)[:, None] *
|
|
206
|
+
numpy.power(y, jy - 1)[None, :])
|
|
207
|
+
else:
|
|
208
|
+
Pzy = 0.0 + 0.0j
|
|
209
|
+
|
|
210
|
+
return P, Pz, Py, Pzz, Pzy, Pyy
|
|
211
|
+
|
|
212
|
+
def G(v):
|
|
213
|
+
zeta, y = float(v[0]), float(v[1])
|
|
214
|
+
P, Pz, Py, _, _, _ = P_all(zeta, y)
|
|
215
|
+
P = float(numpy.real(P))
|
|
216
|
+
Pz = float(numpy.real(Pz))
|
|
217
|
+
Py = float(numpy.real(Py))
|
|
218
|
+
F2 = (y * y) * Py - c * Pz
|
|
219
|
+
return numpy.array([P, F2], dtype=float)
|
|
220
|
+
|
|
221
|
+
z_rad = 0.5
|
|
222
|
+
y_rad = 5.0 * (1.0 + abs(y_seed))
|
|
223
|
+
lb = numpy.array([zeta_seed - z_rad, y_seed - y_rad], dtype=float)
|
|
224
|
+
ub = numpy.array([zeta_seed + z_rad, y_seed + y_rad], dtype=float)
|
|
225
|
+
|
|
226
|
+
res = opt.least_squares(
|
|
227
|
+
G, numpy.array([zeta_seed, y_seed], dtype=float),
|
|
228
|
+
bounds=(lb, ub), method="trf",
|
|
229
|
+
max_nfev=8000, ftol=tol, xtol=tol, gtol=tol, x_scale="jac"
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
zeta_star = float(res.x[0])
|
|
233
|
+
y_star = float(res.x[1])
|
|
234
|
+
x_star = float(zeta_star - c / y_star)
|
|
235
|
+
|
|
236
|
+
P, Pz, Py, Pzz, Pzy, Pyy = P_all(zeta_star, y_star)
|
|
237
|
+
P = float(numpy.real(P))
|
|
238
|
+
Pz = float(numpy.real(Pz))
|
|
239
|
+
Py = float(numpy.real(Py))
|
|
240
|
+
Pzz = float(numpy.real(Pzz))
|
|
241
|
+
Pzy = float(numpy.real(Pzy))
|
|
242
|
+
Pyy = float(numpy.real(Pyy))
|
|
243
|
+
|
|
244
|
+
F2 = (y_star * y_star) * Py - c * Pz
|
|
245
|
+
F3 = y_star * (Pzz * (Py * Py) - 2.0 * Pzy * Pz * Py + Pyy * (Pz * Pz)) + \
|
|
246
|
+
2.0 * (Pz * Pz) * Py
|
|
247
|
+
F = numpy.array([P, float(F2), float(F3)], dtype=float)
|
|
248
|
+
|
|
249
|
+
ok = bool(numpy.max(numpy.abs(F)) < 1e-8)
|
|
250
|
+
|
|
251
|
+
return {
|
|
252
|
+
"ok": ok,
|
|
253
|
+
"t": float(t_star),
|
|
254
|
+
"tau": float(tau),
|
|
255
|
+
"zeta": float(zeta_star),
|
|
256
|
+
"y": float(y_star),
|
|
257
|
+
"x": float(x_star),
|
|
258
|
+
"F": F,
|
|
259
|
+
"success": True,
|
|
260
|
+
"seed": {
|
|
261
|
+
"t": float(t_star),
|
|
262
|
+
"x": float(x_seed),
|
|
263
|
+
"zeta": float(zeta_seed),
|
|
264
|
+
"y": float(y_seed)
|
|
265
|
+
},
|
|
266
|
+
"merge": {"bracketed": bool(bracketed)},
|
|
267
|
+
"gap_at_t": float(gap_at(t_star)),
|
|
268
|
+
"lsq_success": bool(res.success)}
|