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
freealg/__init__.py
CHANGED
|
@@ -8,13 +8,13 @@
|
|
|
8
8
|
|
|
9
9
|
from ._free_form import FreeForm, eigvalsh, cond, norm, trace, slogdet, supp, \
|
|
10
10
|
sample, kde
|
|
11
|
-
from ._algebraic_form import AlgebraicForm
|
|
11
|
+
from ._algebraic_form import AlgebraicForm, decompress_newton
|
|
12
12
|
from ._geometric_form import GeometricForm
|
|
13
13
|
from . import visualization
|
|
14
14
|
from . import distributions
|
|
15
15
|
|
|
16
16
|
__all__ = ['FreeForm', 'distributions', 'visualization', 'eigvalsh', 'cond',
|
|
17
17
|
'norm', 'trace', 'slogdet', 'supp', 'sample', 'kde',
|
|
18
|
-
'AlgebraicForm', 'GeometricForm']
|
|
18
|
+
'AlgebraicForm', 'GeometricForm', 'decompress_newton']
|
|
19
19
|
|
|
20
20
|
from .__version__ import __version__ # noqa: F401 E402
|
freealg/__version__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.7.
|
|
1
|
+
__version__ = "0.7.14"
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
# SPDX-FileCopyrightText: Copyright 2025, Siavash Ameli <sameli@berkeley.edu>
|
|
3
2
|
# SPDX-License-Identifier: BSD-3-Clause
|
|
4
3
|
# SPDX-FileType: SOURCE
|
|
@@ -54,15 +53,61 @@ def _series_pow(mser, j, q_max):
|
|
|
54
53
|
# build moment constraints matrix
|
|
55
54
|
# ===============================
|
|
56
55
|
|
|
56
|
+
# def build_moment_constraint_matrix(pairs, deg_z, s, mu):
|
|
57
|
+
#
|
|
58
|
+
# mu = numpy.asarray(mu, dtype=float).ravel()
|
|
59
|
+
# if mu.size == 0:
|
|
60
|
+
# return numpy.zeros((0, len(pairs)), dtype=float)
|
|
61
|
+
#
|
|
62
|
+
# # m(z) = -sum_{p>=0} mu_p / z^{p+1}; t = 1/z so m(t) = -sum mu_p t^{p+1}
|
|
63
|
+
# r = mu.size - 1
|
|
64
|
+
# q_max = r
|
|
65
|
+
#
|
|
66
|
+
# mser = numpy.zeros(q_max + 1, dtype=float)
|
|
67
|
+
# for p in range(mu.size):
|
|
68
|
+
# q = p + 1
|
|
69
|
+
# if q <= q_max:
|
|
70
|
+
# mser[q] = -float(mu[p])
|
|
71
|
+
#
|
|
72
|
+
# # Precompute (m(t))^j coefficients up to t^{q_max}
|
|
73
|
+
# mpow = []
|
|
74
|
+
# for j in range(s + 1):
|
|
75
|
+
# mpow.append(_series_pow(mser, j, q_max))
|
|
76
|
+
#
|
|
77
|
+
# # Constraints: coeff of t^q in Q(t) := t^{deg_z} P(1/t, m(t)) must be 0
|
|
78
|
+
# # Q(t) = sum_{i,j} c_{i,j} * t^{deg_z - i} * (m(t))^j
|
|
79
|
+
# n_coef = len(pairs)
|
|
80
|
+
# B = numpy.zeros((q_max + 1, n_coef), dtype=float)
|
|
81
|
+
#
|
|
82
|
+
# for k, (i, j) in enumerate(pairs):
|
|
83
|
+
# shift = deg_z - i
|
|
84
|
+
# if shift < 0:
|
|
85
|
+
# continue
|
|
86
|
+
# mj = mpow[j]
|
|
87
|
+
# for q in range(q_max + 1):
|
|
88
|
+
# qq = q - shift
|
|
89
|
+
# if 0 <= qq <= q_max:
|
|
90
|
+
# B[q, k] = mj[qq]
|
|
91
|
+
#
|
|
92
|
+
# # Drop all-zero rows (can happen if index-set can't support higher
|
|
93
|
+
# # moments)
|
|
94
|
+
# row_norm = numpy.linalg.norm(B, axis=1)
|
|
95
|
+
# keep = row_norm > 0.0
|
|
96
|
+
# B = B[keep, :]
|
|
97
|
+
#
|
|
98
|
+
# return B
|
|
99
|
+
|
|
57
100
|
def build_moment_constraint_matrix(pairs, deg_z, s, mu):
|
|
58
101
|
|
|
59
102
|
mu = numpy.asarray(mu, dtype=float).ravel()
|
|
60
103
|
if mu.size == 0:
|
|
61
104
|
return numpy.zeros((0, len(pairs)), dtype=float)
|
|
62
105
|
|
|
63
|
-
#
|
|
106
|
+
# mu has entries mu_0..mu_r
|
|
64
107
|
r = mu.size - 1
|
|
65
|
-
|
|
108
|
+
|
|
109
|
+
# Need t^{r+1} in m(t) = -sum mu_p t^{p+1}, otherwise mu_0 is dropped.
|
|
110
|
+
q_max = r + 1
|
|
66
111
|
|
|
67
112
|
mser = numpy.zeros(q_max + 1, dtype=float)
|
|
68
113
|
for p in range(mu.size):
|
|
@@ -70,29 +115,25 @@ def build_moment_constraint_matrix(pairs, deg_z, s, mu):
|
|
|
70
115
|
if q <= q_max:
|
|
71
116
|
mser[q] = -float(mu[p])
|
|
72
117
|
|
|
73
|
-
# Precompute (m(t))^j coefficients up to t^{q_max}
|
|
74
118
|
mpow = []
|
|
75
119
|
for j in range(s + 1):
|
|
76
120
|
mpow.append(_series_pow(mser, j, q_max))
|
|
77
121
|
|
|
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
122
|
n_coef = len(pairs)
|
|
81
|
-
|
|
123
|
+
|
|
124
|
+
# We only want constraints for l=0..r -> that's q = 0..r in Q(t)
|
|
125
|
+
B = numpy.zeros((r + 1, n_coef), dtype=float)
|
|
82
126
|
|
|
83
127
|
for k, (i, j) in enumerate(pairs):
|
|
84
128
|
shift = deg_z - i
|
|
85
129
|
if shift < 0:
|
|
86
130
|
continue
|
|
87
131
|
mj = mpow[j]
|
|
88
|
-
for q in range(
|
|
132
|
+
for q in range(r + 1):
|
|
89
133
|
qq = q - shift
|
|
90
134
|
if 0 <= qq <= q_max:
|
|
91
135
|
B[q, k] = mj[qq]
|
|
92
136
|
|
|
93
|
-
# Drop all-zero rows (can happen if index-set can't support higher moments)
|
|
94
137
|
row_norm = numpy.linalg.norm(B, axis=1)
|
|
95
138
|
keep = row_norm > 0.0
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
return B
|
|
139
|
+
return B[keep, :]
|
|
@@ -0,0 +1,357 @@
|
|
|
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
|
|
16
|
+
from ._decompress import eval_P_partials
|
|
17
|
+
|
|
18
|
+
__all__ = ["solve_cusp"]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# ==========
|
|
22
|
+
# newton 3x3
|
|
23
|
+
# ==========
|
|
24
|
+
|
|
25
|
+
def _newton_3x3(F, x0, max_iter=60, tol=1e-12, bounds=None, max_step=None):
|
|
26
|
+
x = numpy.array(x0, dtype=float)
|
|
27
|
+
|
|
28
|
+
# bounds: list/tuple of (lo, hi) per component (None means unbounded)
|
|
29
|
+
if bounds is not None:
|
|
30
|
+
b = []
|
|
31
|
+
for lo, hi in bounds:
|
|
32
|
+
b.append((None if lo is None else float(lo),
|
|
33
|
+
None if hi is None else float(hi)))
|
|
34
|
+
bounds = b
|
|
35
|
+
|
|
36
|
+
if max_step is not None:
|
|
37
|
+
max_step = numpy.asarray(max_step, dtype=float)
|
|
38
|
+
if max_step.shape != (3,):
|
|
39
|
+
raise ValueError("max_step must have shape (3,)")
|
|
40
|
+
|
|
41
|
+
def _apply_bounds(xv):
|
|
42
|
+
if bounds is None:
|
|
43
|
+
return xv
|
|
44
|
+
for i, (lo, hi) in enumerate(bounds):
|
|
45
|
+
if lo is not None and xv[i] < lo:
|
|
46
|
+
xv[i] = lo
|
|
47
|
+
if hi is not None and xv[i] > hi:
|
|
48
|
+
xv[i] = hi
|
|
49
|
+
return xv
|
|
50
|
+
|
|
51
|
+
x = _apply_bounds(x.copy())
|
|
52
|
+
|
|
53
|
+
fx = F(x)
|
|
54
|
+
if numpy.linalg.norm(fx) <= tol:
|
|
55
|
+
return x, True, fx
|
|
56
|
+
|
|
57
|
+
for _ in range(max_iter):
|
|
58
|
+
J = numpy.zeros((3, 3), dtype=float)
|
|
59
|
+
eps = 1e-6
|
|
60
|
+
for j in range(3):
|
|
61
|
+
xp = x.copy()
|
|
62
|
+
xp[j] += eps
|
|
63
|
+
xp = _apply_bounds(xp)
|
|
64
|
+
J[:, j] = (F(xp) - fx) / eps
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
dx = numpy.linalg.solve(J, -fx)
|
|
68
|
+
except numpy.linalg.LinAlgError:
|
|
69
|
+
return x, False, fx
|
|
70
|
+
|
|
71
|
+
if max_step is not None:
|
|
72
|
+
dx = numpy.clip(dx, -max_step, max_step)
|
|
73
|
+
|
|
74
|
+
lam = 1.0
|
|
75
|
+
improved = False
|
|
76
|
+
for _ls in range(12):
|
|
77
|
+
x_try = x + lam * dx
|
|
78
|
+
x_try = _apply_bounds(x_try)
|
|
79
|
+
f_try = F(x_try)
|
|
80
|
+
if numpy.linalg.norm(f_try) < numpy.linalg.norm(fx):
|
|
81
|
+
x, fx = x_try, f_try
|
|
82
|
+
improved = True
|
|
83
|
+
break
|
|
84
|
+
lam *= 0.5
|
|
85
|
+
|
|
86
|
+
if not improved:
|
|
87
|
+
return x, False, fx
|
|
88
|
+
|
|
89
|
+
if numpy.linalg.norm(fx) <= tol:
|
|
90
|
+
return x, True, fx
|
|
91
|
+
|
|
92
|
+
return x, False, fx
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
__all__ = ["solve_cusp"]
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _second_partials_fd(zeta, y, a_coeffs, eps_z=None, eps_y=None):
|
|
99
|
+
zeta = float(zeta)
|
|
100
|
+
y = float(y)
|
|
101
|
+
|
|
102
|
+
if eps_z is None:
|
|
103
|
+
eps_z = 1e-7 * (1.0 + abs(zeta))
|
|
104
|
+
if eps_y is None:
|
|
105
|
+
eps_y = 1e-7 * (1.0 + abs(y))
|
|
106
|
+
|
|
107
|
+
_, Pz_p, Py_p = eval_P_partials(zeta + eps_z, y, a_coeffs)
|
|
108
|
+
_, Pz_m, Py_m = eval_P_partials(zeta - eps_z, y, a_coeffs)
|
|
109
|
+
Pzz = (Pz_p - Pz_m) / (2.0 * eps_z)
|
|
110
|
+
Pzy1 = (Py_p - Py_m) / (2.0 * eps_z)
|
|
111
|
+
|
|
112
|
+
_, Pz_p, Py_p = eval_P_partials(zeta, y + eps_y, a_coeffs)
|
|
113
|
+
_, Pz_m, Py_m = eval_P_partials(zeta, y - eps_y, a_coeffs)
|
|
114
|
+
Pzy2 = (Pz_p - Pz_m) / (2.0 * eps_y)
|
|
115
|
+
Pyy = (Py_p - Py_m) / (2.0 * eps_y)
|
|
116
|
+
|
|
117
|
+
Pzy = 0.5 * (Pzy1 + Pzy2)
|
|
118
|
+
return float(Pzz), float(Pzy), float(Pyy)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _cusp_F_real(zeta, y, s, a_coeffs):
|
|
122
|
+
# tau = 1 + exp(s) => c = tau-1 = exp(s) > 0
|
|
123
|
+
c = float(numpy.exp(float(s)))
|
|
124
|
+
|
|
125
|
+
P, Pz, Py = eval_P_partials(float(zeta), float(y), a_coeffs)
|
|
126
|
+
P = float(numpy.real(P))
|
|
127
|
+
Pz = float(numpy.real(Pz))
|
|
128
|
+
Py = float(numpy.real(Py))
|
|
129
|
+
|
|
130
|
+
F1 = P
|
|
131
|
+
F2 = (y * y) * Py - c * Pz
|
|
132
|
+
|
|
133
|
+
Pzz, Pzy, Pyy = _second_partials_fd(zeta, y, a_coeffs)
|
|
134
|
+
F3 = y * (Pzz * (Py * Py) - 2.0 * Pzy * Pz * Py + Pyy * (Pz * Pz)) + \
|
|
135
|
+
2.0 * (Pz * Pz) * Py
|
|
136
|
+
|
|
137
|
+
return numpy.array([F1, F2, F3], dtype=float)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
# ================
|
|
141
|
+
# poly coeffs in y
|
|
142
|
+
# ================
|
|
143
|
+
|
|
144
|
+
def _poly_coeffs_in_y(a_coeffs, zeta):
|
|
145
|
+
a = numpy.asarray(a_coeffs)
|
|
146
|
+
deg_z = a.shape[0] - 1
|
|
147
|
+
deg_y = a.shape[1] - 1
|
|
148
|
+
z_pows = numpy.power(zeta, numpy.arange(deg_z + 1, dtype=numpy.int64))
|
|
149
|
+
c = numpy.empty((deg_y + 1,), dtype=numpy.complex128)
|
|
150
|
+
for j in range(deg_y + 1):
|
|
151
|
+
c[j] = numpy.dot(a[:, j], z_pows)
|
|
152
|
+
return c # ascending in y
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
# ===================
|
|
156
|
+
# pick realish root y
|
|
157
|
+
# ===================
|
|
158
|
+
|
|
159
|
+
def _pick_realish_root_y(a_coeffs, zeta):
|
|
160
|
+
|
|
161
|
+
c_asc = _poly_coeffs_in_y(a_coeffs, zeta)
|
|
162
|
+
c_desc = c_asc[::-1] # descending for numpy.roots
|
|
163
|
+
|
|
164
|
+
k = 0
|
|
165
|
+
while k < len(c_desc) and abs(c_desc[k]) == 0:
|
|
166
|
+
k += 1
|
|
167
|
+
c_desc = c_desc[k:] if k < len(c_desc) else c_desc
|
|
168
|
+
|
|
169
|
+
if len(c_desc) <= 1:
|
|
170
|
+
return 0.0
|
|
171
|
+
|
|
172
|
+
roots = numpy.roots(c_desc)
|
|
173
|
+
j = int(numpy.argmin(numpy.abs(numpy.imag(roots))))
|
|
174
|
+
return float(numpy.real(roots[j]))
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
# ==========
|
|
178
|
+
# solve cusp
|
|
179
|
+
# ==========
|
|
180
|
+
|
|
181
|
+
def solve_cusp(
|
|
182
|
+
a_coeffs,
|
|
183
|
+
t_init,
|
|
184
|
+
zeta_init,
|
|
185
|
+
y_init=None,
|
|
186
|
+
max_iter=80,
|
|
187
|
+
tol=1e-12,
|
|
188
|
+
t_bounds=None,
|
|
189
|
+
zeta_bounds=None):
|
|
190
|
+
"""
|
|
191
|
+
Exact-derivative cusp solve for (zeta, y, t) with unknowns (zeta, y, s),
|
|
192
|
+
where tau = 1 + exp(s), t = log(tau), x = zeta - (tau-1)/y.
|
|
193
|
+
|
|
194
|
+
a_coeffs: array shape (deg_z+1, deg_y+1), P(zeta,y)=
|
|
195
|
+
sum_{i,j} a[i,j]*zeta^i*y^j
|
|
196
|
+
"""
|
|
197
|
+
|
|
198
|
+
a = numpy.asarray(a_coeffs, dtype=numpy.complex128)
|
|
199
|
+
deg_z = a.shape[0] - 1
|
|
200
|
+
deg_y = a.shape[1] - 1
|
|
201
|
+
|
|
202
|
+
def _P_partials_all(zeta, y):
|
|
203
|
+
# returns (P, Pz, Py, Pzz, Pzy, Pyy) as complex
|
|
204
|
+
zeta = numpy.complex128(zeta)
|
|
205
|
+
y = numpy.complex128(y)
|
|
206
|
+
|
|
207
|
+
zi = numpy.power(zeta, numpy.arange(deg_z + 1, dtype=numpy.int64))
|
|
208
|
+
yj = numpy.power(y, numpy.arange(deg_y + 1, dtype=numpy.int64))
|
|
209
|
+
|
|
210
|
+
P = numpy.sum(a * zi[:, None] * yj[None, :])
|
|
211
|
+
|
|
212
|
+
# Pz
|
|
213
|
+
if deg_z >= 1:
|
|
214
|
+
iz = numpy.arange(1, deg_z + 1, dtype=numpy.int64)
|
|
215
|
+
zi_m1 = numpy.power(zeta, iz - 1)
|
|
216
|
+
Pz = numpy.sum(
|
|
217
|
+
(a[iz, :] * iz[:, None]) * zi_m1[:, None] * yj[None, :])
|
|
218
|
+
else:
|
|
219
|
+
Pz = 0.0 + 0.0j
|
|
220
|
+
|
|
221
|
+
# Py
|
|
222
|
+
if deg_y >= 1:
|
|
223
|
+
jy = numpy.arange(1, deg_y + 1, dtype=numpy.int64)
|
|
224
|
+
yj_m1 = numpy.power(y, jy - 1)
|
|
225
|
+
Py = numpy.sum(
|
|
226
|
+
(a[:, jy] * jy[None, :]) * zi[:, None] * yj_m1[None, :])
|
|
227
|
+
else:
|
|
228
|
+
Py = 0.0 + 0.0j
|
|
229
|
+
|
|
230
|
+
# Pzz
|
|
231
|
+
if deg_z >= 2:
|
|
232
|
+
iz = numpy.arange(2, deg_z + 1, dtype=numpy.int64)
|
|
233
|
+
zi_m2 = numpy.power(zeta, iz - 2)
|
|
234
|
+
Pzz = numpy.sum((a[iz, :] * (iz * (iz - 1))[:, None]) *
|
|
235
|
+
zi_m2[:, None] * yj[None, :])
|
|
236
|
+
else:
|
|
237
|
+
Pzz = 0.0 + 0.0j
|
|
238
|
+
|
|
239
|
+
# Pyy
|
|
240
|
+
if deg_y >= 2:
|
|
241
|
+
jy = numpy.arange(2, deg_y + 1, dtype=numpy.int64)
|
|
242
|
+
yj_m2 = numpy.power(y, jy - 2)
|
|
243
|
+
Pyy = numpy.sum((a[:, jy] * (jy * (jy - 1))[None, :]) *
|
|
244
|
+
zi[:, None] * yj_m2[None, :])
|
|
245
|
+
else:
|
|
246
|
+
Pyy = 0.0 + 0.0j
|
|
247
|
+
|
|
248
|
+
# Pzy
|
|
249
|
+
if (deg_z >= 1) and (deg_y >= 1):
|
|
250
|
+
iz = numpy.arange(1, deg_z + 1, dtype=numpy.int64)
|
|
251
|
+
jy = numpy.arange(1, deg_y + 1, dtype=numpy.int64)
|
|
252
|
+
zi_m1 = numpy.power(zeta, iz - 1)
|
|
253
|
+
yj_m1 = numpy.power(y, jy - 1)
|
|
254
|
+
coeff = a[numpy.ix_(iz, jy)] * (iz[:, None] * jy[None, :])
|
|
255
|
+
Pzy = numpy.sum(coeff * zi_m1[:, None] * yj_m1[None, :])
|
|
256
|
+
else:
|
|
257
|
+
Pzy = 0.0 + 0.0j
|
|
258
|
+
|
|
259
|
+
return P, Pz, Py, Pzz, Pzy, Pyy
|
|
260
|
+
|
|
261
|
+
def _F(vec):
|
|
262
|
+
zeta, y, s = float(vec[0]), float(vec[1]), float(vec[2])
|
|
263
|
+
c = float(numpy.exp(s)) # c = tau - 1 > 0
|
|
264
|
+
P, Pz, Py, Pzz, Pzy, Pyy = _P_partials_all(zeta, y)
|
|
265
|
+
|
|
266
|
+
# Work in reals: cusp lives on real zeta,y for real cusp
|
|
267
|
+
P = float(numpy.real(P))
|
|
268
|
+
Pz = float(numpy.real(Pz))
|
|
269
|
+
Py = float(numpy.real(Py))
|
|
270
|
+
Pzz = float(numpy.real(Pzz))
|
|
271
|
+
Pzy = float(numpy.real(Pzy))
|
|
272
|
+
Pyy = float(numpy.real(Pyy))
|
|
273
|
+
|
|
274
|
+
F1 = P
|
|
275
|
+
F2 = (y * y) * Py - c * Pz
|
|
276
|
+
F3 = y * (Pzz * (Py * Py) - 2.0 * Pzy * Pz * Py + Pyy * (Pz * Pz)) + \
|
|
277
|
+
2.0 * (Pz * Pz) * Py
|
|
278
|
+
return numpy.array([F1, F2, F3], dtype=float)
|
|
279
|
+
|
|
280
|
+
z0 = float(zeta_init)
|
|
281
|
+
|
|
282
|
+
# seed y: keep your provided seed; else pick a real-ish root at z0
|
|
283
|
+
if y_init is None:
|
|
284
|
+
# build polynomial in y at fixed z0 and pick root with smallest imag
|
|
285
|
+
zi = numpy.power(z0, numpy.arange(deg_z + 1, dtype=numpy.int64))
|
|
286
|
+
c_asc = numpy.array([numpy.dot(a[:, j], zi) for j in range(deg_y + 1)],
|
|
287
|
+
dtype=numpy.complex128)
|
|
288
|
+
c_desc = c_asc[::-1]
|
|
289
|
+
kk = 0
|
|
290
|
+
while kk < len(c_desc) and abs(c_desc[kk]) == 0:
|
|
291
|
+
kk += 1
|
|
292
|
+
c_desc = c_desc[kk:] if kk < len(c_desc) else c_desc
|
|
293
|
+
roots = numpy.roots(c_desc) if len(c_desc) > 1 else numpy.array([0.0])
|
|
294
|
+
j = int(numpy.argmin(numpy.abs(numpy.imag(roots))))
|
|
295
|
+
y0 = float(numpy.real(roots[j]))
|
|
296
|
+
else:
|
|
297
|
+
y0 = float(y_init)
|
|
298
|
+
|
|
299
|
+
tau0 = float(numpy.exp(float(t_init)))
|
|
300
|
+
c0 = max(tau0 - 1.0, 1e-14)
|
|
301
|
+
s0 = float(numpy.log(c0))
|
|
302
|
+
|
|
303
|
+
# bounds for zeta, y, s
|
|
304
|
+
z_lo, z_hi = -numpy.inf, numpy.inf
|
|
305
|
+
if zeta_bounds is not None:
|
|
306
|
+
z_lo, z_hi = float(zeta_bounds[0]), float(zeta_bounds[1])
|
|
307
|
+
if z_hi < z_lo:
|
|
308
|
+
z_lo, z_hi = z_hi, z_lo
|
|
309
|
+
|
|
310
|
+
s_lo, s_hi = -numpy.inf, numpy.inf
|
|
311
|
+
if t_bounds is not None:
|
|
312
|
+
t_lo, t_hi = float(t_bounds[0]), float(t_bounds[1])
|
|
313
|
+
if t_hi < t_lo:
|
|
314
|
+
t_lo, t_hi = t_hi, t_lo
|
|
315
|
+
c_lo = max(float(numpy.expm1(t_lo)), 1e-14)
|
|
316
|
+
c_hi = max(float(numpy.expm1(t_hi)), 1e-14)
|
|
317
|
+
s_lo, s_hi = float(numpy.log(c_lo)), float(numpy.log(c_hi))
|
|
318
|
+
|
|
319
|
+
# keep y on the seeded sheet (this is crucial)
|
|
320
|
+
y_rad = 4.0 * (1.0 + abs(y0))
|
|
321
|
+
y_lo, y_hi = float(y0 - y_rad), float(y0 + y_rad)
|
|
322
|
+
|
|
323
|
+
lb = numpy.array([z_lo, y_lo, s_lo], dtype=float)
|
|
324
|
+
ub = numpy.array([z_hi, y_hi, s_hi], dtype=float)
|
|
325
|
+
x0 = numpy.array([z0, y0, s0], dtype=float)
|
|
326
|
+
x0 = numpy.minimum(numpy.maximum(x0, lb), ub)
|
|
327
|
+
|
|
328
|
+
res = scipy.optimize.least_squares(
|
|
329
|
+
_F,
|
|
330
|
+
x0,
|
|
331
|
+
bounds=(lb, ub),
|
|
332
|
+
method="trf",
|
|
333
|
+
max_nfev=int(max_iter) * 100,
|
|
334
|
+
ftol=tol,
|
|
335
|
+
xtol=tol,
|
|
336
|
+
gtol=tol,
|
|
337
|
+
x_scale="jac")
|
|
338
|
+
|
|
339
|
+
zeta, y, s = res.x
|
|
340
|
+
c = float(numpy.exp(float(s)))
|
|
341
|
+
tau = 1.0 + c
|
|
342
|
+
t = float(numpy.log(tau))
|
|
343
|
+
x = float(zeta - (tau - 1.0) / y)
|
|
344
|
+
|
|
345
|
+
F_final = _F(res.x)
|
|
346
|
+
ok = bool(res.success and
|
|
347
|
+
(numpy.max(numpy.abs(F_final)) <= max(1e-9, 50.0 * tol)))
|
|
348
|
+
|
|
349
|
+
return {
|
|
350
|
+
"ok": ok,
|
|
351
|
+
"t": t,
|
|
352
|
+
"tau": float(tau),
|
|
353
|
+
"zeta": float(zeta),
|
|
354
|
+
"y": float(y),
|
|
355
|
+
"x": x,
|
|
356
|
+
"F": F_final,
|
|
357
|
+
"success": bool(res.success)}
|