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.
Files changed (36) hide show
  1. freealg/__init__.py +2 -2
  2. freealg/__version__.py +1 -1
  3. freealg/_algebraic_form/__init__.py +2 -1
  4. freealg/_algebraic_form/_constraints.py +53 -12
  5. freealg/_algebraic_form/_cusp.py +357 -0
  6. freealg/_algebraic_form/_cusp_wrap.py +268 -0
  7. freealg/_algebraic_form/_decompress.py +330 -381
  8. freealg/_algebraic_form/_decompress2.py +120 -0
  9. freealg/_algebraic_form/_decompress4.py +739 -0
  10. freealg/_algebraic_form/_decompress5.py +738 -0
  11. freealg/_algebraic_form/_decompress6.py +492 -0
  12. freealg/_algebraic_form/_decompress7.py +355 -0
  13. freealg/_algebraic_form/_decompress8.py +369 -0
  14. freealg/_algebraic_form/_decompress9.py +363 -0
  15. freealg/_algebraic_form/_decompress_new.py +431 -0
  16. freealg/_algebraic_form/_decompress_new_2.py +1631 -0
  17. freealg/_algebraic_form/_decompress_util.py +172 -0
  18. freealg/_algebraic_form/_edge.py +46 -68
  19. freealg/_algebraic_form/_homotopy.py +62 -30
  20. freealg/_algebraic_form/_homotopy2.py +289 -0
  21. freealg/_algebraic_form/_homotopy3.py +215 -0
  22. freealg/_algebraic_form/_homotopy4.py +320 -0
  23. freealg/_algebraic_form/_homotopy5.py +185 -0
  24. freealg/_algebraic_form/_moments.py +43 -57
  25. freealg/_algebraic_form/_support.py +132 -177
  26. freealg/_algebraic_form/algebraic_form.py +163 -30
  27. freealg/distributions/__init__.py +3 -1
  28. freealg/distributions/_compound_poisson.py +464 -0
  29. freealg/distributions/_deformed_marchenko_pastur.py +51 -0
  30. freealg/distributions/_deformed_wigner.py +44 -0
  31. {freealg-0.7.11.dist-info → freealg-0.7.14.dist-info}/METADATA +2 -1
  32. {freealg-0.7.11.dist-info → freealg-0.7.14.dist-info}/RECORD +36 -20
  33. {freealg-0.7.11.dist-info → freealg-0.7.14.dist-info}/WHEEL +1 -1
  34. {freealg-0.7.11.dist-info → freealg-0.7.14.dist-info}/licenses/AUTHORS.txt +0 -0
  35. {freealg-0.7.11.dist-info → freealg-0.7.14.dist-info}/licenses/LICENSE.txt +0 -0
  36. {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)}