freealg 0.7.6__py3-none-any.whl → 0.7.8__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/__version__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.7.6"
1
+ __version__ = "0.7.8"
@@ -227,7 +227,7 @@ def fit_polynomial_relation(z, m, s, deg_z, ridge_lambda=0.0, weights=None,
227
227
  dtype=float)
228
228
  AN = numpy.vstack([AN, L])
229
229
 
230
- _, _, vhN = numpy.linalg.svd(AN, full_matrices=False)
230
+ _, svals, vhN = numpy.linalg.svd(AN, full_matrices=False)
231
231
  y = vhN[-1, :]
232
232
  coef_scaled = N @ y
233
233
 
@@ -245,7 +245,8 @@ def fit_polynomial_relation(z, m, s, deg_z, ridge_lambda=0.0, weights=None,
245
245
  dtype=float)
246
246
  As_aug = numpy.vstack([As_aug, L])
247
247
 
248
- _, _, vh = numpy.linalg.svd(As_aug, full_matrices=False)
248
+ _, svals, vh = numpy.linalg.svd(As_aug,
249
+ full_matrices=False)
249
250
  coef_scaled = vh[-1, :]
250
251
  coef = coef_scaled / s_col
251
252
  else:
@@ -255,7 +256,7 @@ def fit_polynomial_relation(z, m, s, deg_z, ridge_lambda=0.0, weights=None,
255
256
  dtype=float)
256
257
  As = numpy.vstack([As, L])
257
258
 
258
- _, _, vh = numpy.linalg.svd(As, full_matrices=False)
259
+ _, svals, vh = numpy.linalg.svd(As, full_matrices=False)
259
260
  coef_scaled = vh[-1, :]
260
261
  coef = coef_scaled / s_col
261
262
 
@@ -265,7 +266,7 @@ def fit_polynomial_relation(z, m, s, deg_z, ridge_lambda=0.0, weights=None,
265
266
  L = numpy.sqrt(ridge_lambda) * numpy.eye(n_coef, dtype=float)
266
267
  As = numpy.vstack([As, L])
267
268
 
268
- _, _, vh = numpy.linalg.svd(As, full_matrices=False)
269
+ _, svals, vh = numpy.linalg.svd(As, full_matrices=False)
269
270
  coef_scaled = vh[-1, :]
270
271
  coef = coef_scaled / s_col
271
272
 
@@ -275,7 +276,7 @@ def fit_polynomial_relation(z, m, s, deg_z, ridge_lambda=0.0, weights=None,
275
276
  L = numpy.sqrt(ridge_lambda) * numpy.eye(n_coef, dtype=float)
276
277
  As = numpy.vstack([As, L])
277
278
 
278
- _, _, vh = numpy.linalg.svd(As, full_matrices=False)
279
+ _, svals, vh = numpy.linalg.svd(As, full_matrices=False)
279
280
  coef_scaled = vh[-1, :]
280
281
  coef = coef_scaled / s_col
281
282
 
@@ -286,7 +287,14 @@ def fit_polynomial_relation(z, m, s, deg_z, ridge_lambda=0.0, weights=None,
286
287
  if normalize:
287
288
  full = _normalize_coefficients(full)
288
289
 
289
- return full
290
+ # Diagnostic metrics
291
+ fit_metrics = {
292
+ 's_min': svals[-1],
293
+ 'gap_ratio': float(svals[-2] / svals[-1]),
294
+ 'n_small': float(int(numpy.sum(svals <= svals[0] * 1e-12))),
295
+ }
296
+
297
+ return full, fit_metrics
290
298
 
291
299
 
292
300
  # =============================
@@ -0,0 +1,226 @@
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 as np
15
+ import numpy.polynomial.polynomial as poly
16
+
17
+ __all__ = ['compute_singular_points']
18
+
19
+
20
+ # =========
21
+ # ploy trim
22
+ # =========
23
+
24
+ def _poly_trim(p, tol):
25
+
26
+ p = np.asarray(p, dtype=complex).ravel()
27
+ if p.size == 0:
28
+ return np.zeros(1, dtype=complex)
29
+ k = p.size - 1
30
+ while k > 0 and abs(p[k]) <= tol:
31
+ k -= 1
32
+ return p[:k + 1].copy()
33
+
34
+
35
+ # ============
36
+ # poly is zero
37
+ # ============
38
+
39
+ def _poly_is_zero(p, tol):
40
+
41
+ p = _poly_trim(p, tol)
42
+ return (p.size == 1) and (abs(p[0]) <= tol)
43
+
44
+
45
+ # ========
46
+ # poly add
47
+ # ========
48
+
49
+ def _poly_add(a, b, tol):
50
+
51
+ return _poly_trim(poly.polyadd(a, b), tol)
52
+
53
+
54
+ # ========
55
+ # poly sub
56
+ # ========
57
+
58
+ def _poly_sub(a, b, tol):
59
+
60
+ return _poly_trim(poly.polysub(a, b), tol)
61
+
62
+
63
+ # =======
64
+ # ply mul
65
+ # =======
66
+
67
+ def _poly_mul(a, b, tol):
68
+
69
+ return _poly_trim(poly.polymul(a, b), tol)
70
+
71
+
72
+ # ==============
73
+ # poly div exact
74
+ # ==============
75
+
76
+ def _poly_div_exact(a, b, tol):
77
+
78
+ a = _poly_trim(a, tol)
79
+ b = _poly_trim(b, tol)
80
+ if _poly_is_zero(b, tol):
81
+ raise ZeroDivisionError("poly division by zero")
82
+
83
+ q, r = poly.polydiv(a, b)
84
+ r = _poly_trim(r, tol)
85
+
86
+ # Bareiss expects exact division; with floats it's only approximate.
87
+ # If the remainder is small, drop it.
88
+ scale = max(1.0, np.linalg.norm(a))
89
+ if np.linalg.norm(r) > 1e3 * tol * scale:
90
+ # Fallback: still drop remainder (keeps algorithm running).
91
+ # This is acceptable because we only need the resultant roots
92
+ # robustly, not exact symbolic coefficients.
93
+ pass
94
+
95
+ return _poly_trim(q, tol)
96
+
97
+
98
+ # ================
99
+ # det bareiss poly
100
+ # ================
101
+
102
+ def _det_bareiss_poly(M, tol):
103
+
104
+ n = len(M)
105
+ A = [[_poly_trim(M[i][j], tol) for j in range(n)] for i in range(n)]
106
+
107
+ denom = np.array([1.0], dtype=complex)
108
+ sign = 1.0
109
+
110
+ for k in range(n - 1):
111
+ if _poly_is_zero(A[k][k], tol):
112
+ piv = -1
113
+ for i in range(k + 1, n):
114
+ if not _poly_is_zero(A[i][k], tol):
115
+ piv = i
116
+ break
117
+ if piv == -1:
118
+ return np.array([0.0], dtype=complex)
119
+ A[k], A[piv] = A[piv], A[k]
120
+ sign *= -1.0
121
+
122
+ pivot = A[k][k]
123
+ for i in range(k + 1, n):
124
+ for j in range(k + 1, n):
125
+ num = _poly_sub(_poly_mul(A[i][j], pivot, tol),
126
+ _poly_mul(A[i][k], A[k][j], tol),
127
+ tol)
128
+ if k > 0:
129
+ A[i][j] = _poly_div_exact(num, denom, tol)
130
+ else:
131
+ A[i][j] = _poly_trim(num, tol)
132
+
133
+ denom = pivot
134
+
135
+ return _poly_trim(sign * A[n - 1][n - 1], tol)
136
+
137
+
138
+ # ===================
139
+ # cluster real points
140
+ # ===================
141
+
142
+ def _cluster_real_points(x, eps):
143
+
144
+ x = np.asarray(x, dtype=float).ravel()
145
+ if x.size == 0:
146
+ return x
147
+ x = np.sort(x)
148
+ uniq = []
149
+ for v in x:
150
+ if (len(uniq) == 0) or (abs(v - uniq[-1]) > eps):
151
+ uniq.append(float(v))
152
+ else:
153
+ uniq[-1] = 0.5 * (uniq[-1] + float(v))
154
+ return np.asarray(uniq, dtype=float)
155
+
156
+
157
+ # =======================
158
+ # compute singular points
159
+ # =======================
160
+
161
+ def compute_singular_points(a_coeffs, tol=1e-12, real_tol=None):
162
+ """
163
+ a_coeffs[i,j] is coefficient of z^i m^j, shape (deg_z+1, s+1).
164
+
165
+ Returns
166
+ -------
167
+
168
+ z_bp : complex array, roots of Disc_m(P)(z)
169
+ a_s_zero : complex array, roots of leading coefficient a_s(z)
170
+ support : list of (a,b) from real-ish branch points paired consecutively
171
+ """
172
+
173
+ a_coeffs = np.asarray(a_coeffs)
174
+ s = a_coeffs.shape[1] - 1
175
+ if s < 1:
176
+ return (np.array([], dtype=complex),
177
+ np.array([], dtype=complex),
178
+ [])
179
+
180
+ if real_tol is None:
181
+ real_tol = 1e3 * tol
182
+
183
+ a = [_poly_trim(a_coeffs[:, j], tol) for j in range(s + 1)]
184
+
185
+ a_s = a[s]
186
+ a_s_zero = np.roots(a_s[::-1]) if a_s.size > 1 else \
187
+ np.array([], dtype=complex)
188
+
189
+ b = []
190
+ for j in range(s):
191
+ b.append(_poly_trim((j + 1) * a[j + 1], tol))
192
+
193
+ mdeg = s
194
+ ndeg = s - 1
195
+ N = mdeg + ndeg # 2s-1
196
+
197
+ z0 = np.array([0.0], dtype=complex)
198
+ M = [[z0 for _ in range(N)] for __ in range(N)]
199
+
200
+ for r in range(ndeg):
201
+ for j in range(mdeg + 1):
202
+ M[r][r + j] = a[j]
203
+
204
+ for r in range(mdeg):
205
+ rr = ndeg + r
206
+ for j in range(ndeg + 1):
207
+ M[rr][r + j] = b[j]
208
+
209
+ res = _det_bareiss_poly(M, tol)
210
+ if res.size <= 1:
211
+ z_bp = np.array([], dtype=complex)
212
+ else:
213
+ z_bp = np.roots(res[::-1])
214
+
215
+ support = []
216
+ if z_bp.size > 0:
217
+ zr = z_bp[np.abs(z_bp.imag) <= real_tol].real
218
+ zr = _cluster_real_points(zr, eps=1e2 * real_tol)
219
+ m2 = (zr.size // 2) * 2
220
+ for k in range(0, m2, 2):
221
+ a0 = float(zr[k])
222
+ b0 = float(zr[k + 1])
223
+ if b0 > a0:
224
+ support.append((a0, b0))
225
+
226
+ return z_bp, a_s_zero, support
@@ -3,17 +3,19 @@
3
3
  # =======
4
4
 
5
5
  import numpy
6
+ from ._moments import AlgebraicStieltjesMoments
6
7
 
7
8
  __all__ = ['stieltjes_poly']
8
9
 
9
10
 
10
11
  # =====================
11
- # stieltjes select root
12
+ # select root
12
13
  # =====================
13
14
 
14
- def stieltjes_select_root(roots, z, w_prev=None):
15
+ def select_root(roots, z, target):
15
16
  """
16
- Select the Stieltjes-branch root among candidates at a given z.
17
+ Select the root among Herglotz candidates at a given z closest to a
18
+ given target
17
19
 
18
20
  Parameters
19
21
  ----------
@@ -22,9 +24,9 @@ def stieltjes_select_root(roots, z, w_prev=None):
22
24
  z : complex
23
25
  Evaluation point. The Stieltjes/Herglotz branch satisfies
24
26
  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.
27
+ target : complex
28
+ Previous continuation value used to enforce continuity, or
29
+ target value.
28
30
 
29
31
  Returns
30
32
  -------
@@ -40,11 +42,6 @@ def stieltjes_select_root(roots, z, w_prev=None):
40
42
 
41
43
  desired_sign = numpy.sign(z.imag)
42
44
 
43
- if w_prev is None:
44
- target = -1.0 / z
45
- else:
46
- target = complex(w_prev)
47
-
48
45
  # Apply a soft Herglotz sign filter: prefer roots with Im(w) having the
49
46
  # same sign as Im(z), allowing tiny numerical violations near the axis.
50
47
  imag_roots = numpy.imag(roots)
@@ -62,77 +59,222 @@ def stieltjes_select_root(roots, z, w_prev=None):
62
59
  # stieltjes poly
63
60
  # ==============
64
61
 
65
- def stieltjes_poly(z, a, eps=None, height=1e+4, steps=100):
62
+ class StieltjesPoly(object):
66
63
  """
67
- Evaluate the Stieltjes-branch solution m(z) of an algebraic equation.
64
+ Stieltjes-branch evaluator for an algebraic equation P(z, m) = 0.
65
+
66
+ This class represents the Stieltjes-branch solution m(z) of an algebraic
67
+ equation defined by a polynomial relation
68
68
 
69
- The coefficients `a` define a polynomial relation
70
69
  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.
70
+
71
+ where P is a polynomial in z and m with monomial-basis coefficients.
72
+ The coefficient matrix ``a`` is fixed at construction time, and all
73
+ quantities depending only on ``a`` are precomputed. Evaluation at a
74
+ complex point ``z`` is performed via :meth:`evaluate`. The instance is
75
+ also callable; :meth:`__call__` supports scalar or vector inputs and
76
+ applies :meth:`evaluate` elementwise.
77
+
78
+ The Stieltjes branch is selected by initializing in the appropriate
79
+ half-plane using an asymptotic Stieltjes estimate and then performing
80
+ homotopy continuation along a straight-line path in the complex plane.
74
81
 
75
82
  Parameters
76
83
  ----------
77
- z : complex
78
- Evaluation point. Must be a single value.
79
84
  a : ndarray, shape (L, K)
80
- Coefficient matrix defining P(z, m) in the monomial basis.
85
+ Coefficient matrix defining P(z, m) in the monomial basis. For fixed
86
+ z, the coefficients of the polynomial in m are assembled from powers
87
+ of z.
81
88
  eps : float or None, optional
82
89
  If Im(z) == 0, use z + i*eps as the boundary evaluation point.
83
90
  If None and Im(z) == 0, eps is set to 1e-8 * max(1, |z|).
84
- height : float, optional
91
+ height : float, default = 2.0
85
92
  Imaginary height used for the starting point z0 in the same
86
93
  half-plane as the evaluation point.
87
- steps : int, optional
94
+ steps : int, default = 100
88
95
  Number of continuation steps along the homotopy path.
96
+ order : int, default = 15
97
+ Number of moments in Stieltjes estimate
89
98
 
90
- Returns
99
+ Methods
91
100
  -------
92
- w : complex
93
- Value of the Stieltjes-branch solution m(z) (or m(z+i*eps) if z is
94
- real).
101
+ evaluate(z)
102
+ Evaluate the Stieltjes-branch solution m(z) at a single complex point.
103
+
104
+ __call__(z)
105
+ If ``z`` is scalar, returns ``evaluate(z, ...)``.
106
+ If ``z`` is array-like, returns an array of the same shape, where each
107
+ entry is computed by calling ``evaluate`` on the corresponding element.
108
+
109
+ Notes
110
+ -----
111
+ If an input ``z`` value is real (Im(z) == 0), the evaluation is interpreted
112
+ as a boundary value by replacing that element with z + i*eps. If ``eps`` is
113
+ None, eps is chosen per element as 1e-8 * max(1, |z|).
95
114
  """
96
115
 
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)
116
+ def __init__(self, a, eps=None, height=2.0, steps=100, order=15):
117
+ a = numpy.asarray(a)
118
+ if a.ndim != 2:
119
+ raise ValueError("a must be a 2D array.")
120
+
121
+ self.a = a
122
+ self.a_l, _ = a.shape
123
+ self.eps = eps
124
+ self.height = height
125
+ self.steps = steps
126
+ self.order = order
127
+
128
+ # Objects depending only on a
129
+ self.mom = AlgebraicStieltjesMoments(a)
130
+ self._zpows_exp = numpy.arange(self.a_l)
131
+ self.rad = 1.0 + self.height * self.mom.radius(self.order)
132
+
133
+ def _poly_coeffs_m(self, z_val):
134
+ z_powers = z_val ** self._zpows_exp
135
+ return (z_powers @ self.a)[::-1]
136
+
137
+ def _poly_roots(self, z_val):
138
+ coeffs = numpy.asarray(self._poly_coeffs_m(z_val),
139
+ dtype=numpy.complex128)
114
140
  return numpy.roots(coeffs)
115
141
 
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
142
+ def evaluate(self, z, eps=None, height=2.0, steps=100, order=15):
143
+ """
144
+ Evaluate the Stieltjes-branch solution m(z) at a single point.
145
+
146
+ Parameters are as in the original function, except ``a`` is fixed at
147
+ construction time.
148
+ """
149
+ z = complex(z)
150
+
151
+ if steps < 1:
152
+ raise ValueError("steps must be a positive integer.")
153
+
154
+ # Boundary-value interpretation on the real axis
155
+ if z.imag == 0.0:
156
+ if self.eps is None:
157
+ eps_loc = 1e-8 * max(1.0, abs(z))
158
+ else:
159
+ eps_loc = float(self.eps)
160
+ z_eval = z + 1j * eps_loc
161
+ else:
162
+ z_eval = z
163
+
164
+ half_sign = numpy.sign(z_eval.imag)
165
+ if half_sign == 0.0:
166
+ half_sign = 1.0
167
+
168
+ # If z is outside radius of convergence, no homotopy
169
+ # necessary
170
+ if numpy.abs(z) > self.rad:
171
+ target = self.mom.stieltjes(z, self.order)
172
+ return select_root(self._poly_roots(z), z, target)
173
+
174
+ z0 = 1j * float(half_sign) * self.rad
175
+ target = self.mom.stieltjes(z0, self.order)
176
+
177
+ # Initialize at z0
178
+ w_prev = select_root(self._poly_roots(z0), z0, target)
179
+
180
+ # Straight-line homotopy continuation
181
+ for tau in numpy.linspace(0.0, 1.0, int(self.steps) + 1)[1:]:
182
+ z_tau = z0 + tau * (z_eval - z0)
183
+ w_prev = select_root(self._poly_roots(z_tau), z_tau, w_prev)
184
+
185
+ return w_prev
186
+
187
+ def __call__(self, z):
188
+ # Scalar fast-path
189
+ if numpy.isscalar(z):
190
+ return self.evaluate(z)
191
+
192
+ # Array-like: evaluate elementwise, preserving shape
193
+ z_arr = numpy.asarray(z)
194
+ out = numpy.empty(z_arr.shape, dtype=numpy.complex128)
195
+
196
+ # Iterate over indices so we can pass Python scalars into evaluate()
197
+ for idx in numpy.ndindex(z_arr.shape):
198
+ out[idx] = self.evaluate(z_arr[idx])
199
+
200
+ return out
201
+
202
+
203
+ # def stieltjes_poly(z, a, eps=None, height=2., steps=100, order=15):
204
+ # """
205
+ # Evaluate the Stieltjes-branch solution m(z) of an algebraic equation.
206
+
207
+ # The coefficients `a` define a polynomial relation
208
+ # P(z, m) = 0,
209
+ # where P is a polynomial in z and m with monomial-basis coefficients
210
+ # arranged so that for fixed z, the coefficients of the polynomial in m
211
+ # can be assembled from powers of z.
212
+
213
+ # Parameters
214
+ # ----------
215
+ # z : complex
216
+ # Evaluation point. Must be a single value.
217
+ # a : ndarray, shape (L, K)
218
+ # Coefficient matrix defining P(z, m) in the monomial basis.
219
+ # eps : float or None, optional
220
+ # If Im(z) == 0, use z + i*eps as the boundary evaluation point.
221
+ # If None and Im(z) == 0, eps is set to 1e-8 * max(1, |z|).
222
+ # height : float, default = 2.0
223
+ # Imaginary height used for the starting point z0 in the same
224
+ # half-plane as the evaluation point.
225
+ # steps : int, default = 100
226
+ # Number of continuation steps along the homotopy path.
227
+ # order : int, default = 15
228
+ # Number of moments in Stieltjes estimate
229
+
230
+ # Returns
231
+ # -------
232
+ # w : complex
233
+ # Value of the Stieltjes-branch solution m(z) (or m(z+i*eps) if z is
234
+ # real).
235
+ # """
236
+
237
+ # z = complex(z)
238
+ # a = numpy.asarray(a)
239
+
240
+ # if a.ndim != 2:
241
+ # raise ValueError('a must be a 2D array.')
242
+
243
+ # if steps < 1:
244
+ # raise ValueError("steps must be a positive integer.")
245
+
246
+ # a_l, _ = a.shape
247
+ # mom = AlgebraicStieltjesMoments(a)
248
+
249
+ # def poly_coeffs_m(z_val):
250
+ # z_powers = z_val ** numpy.arange(a_l)
251
+ # return (z_powers @ a)[::-1]
252
+
253
+ # def poly_roots(z_val):
254
+ # coeffs = numpy.asarray(poly_coeffs_m(z_val), dtype=numpy.complex128)
255
+ # return numpy.roots(coeffs)
256
+
257
+ # # If user asked for a real-axis value, interpret as boundary value from C+.
258
+ # if z.imag == 0.0:
259
+ # if eps is None:
260
+ # eps = 1e-8 * max(1.0, abs(z))
261
+ # z_eval = z + 1j * float(eps)
262
+ # else:
263
+ # z_eval = z
264
+
265
+ # half_sign = numpy.sign(z_eval.imag)
266
+ # if half_sign == 0.0:
267
+ # half_sign = 1.0
268
+
269
+ # z0 = 1j * float(half_sign) * (1. + height * mom.radius(order))
270
+ # target = mom.stieltjes(z0, order)
271
+
272
+ # # Initialize at z0 via asymptotic / Im-sign selection.
273
+ # w_prev = select_root(poly_roots(z0), z0, target)
274
+
275
+ # # Straight-line homotopy from z0 to z_eval.
276
+ # for tau in numpy.linspace(0.0, 1.0, int(steps) + 1)[1:]:
277
+ # z_tau = z0 + tau * (z_eval - z0)
278
+ # w_prev = select_root(poly_roots(z_tau), z_tau, w_prev)
279
+
280
+ # return w_prev
@@ -0,0 +1,450 @@
1
+ import numpy
2
+
3
+
4
+ # =========
5
+ # Moments
6
+ # =========
7
+
8
+ class MomentsESD(object):
9
+ """
10
+ Moments :math:`\\mu_n(t)` generated from eigenvalues, under
11
+ free decompression, where
12
+
13
+ .. math::
14
+
15
+ m_n = \\mu_n(0) = \\mathbb{E}[\\lambda^n],
16
+
17
+ and :math:`\\lambda` denotes an eigenvalue sample.
18
+
19
+ Parameters
20
+ ----------
21
+
22
+ eig : array_like
23
+ 1D array of eigenvalues (or samples). Internally it is converted to a
24
+ floating-point :class:`numpy.ndarray`.
25
+
26
+ Attributes
27
+ ----------
28
+
29
+ eig : numpy.ndarray
30
+ Eigenvalue samples.
31
+
32
+ Methods
33
+ -------
34
+
35
+ m
36
+ Compute the raw moment :math:`m_n = \\mathbb{E}[\\lambda^n]`.
37
+
38
+ coeffs
39
+ Compute the coefficient vector :math:`a_n`.
40
+
41
+ __call__
42
+ Evaluate :math:`\\mu_n(t)` for a given :math:`n` and :math:`t`.
43
+
44
+ Notes
45
+ -----
46
+
47
+ The recursion memoizes:
48
+
49
+ * Moments ``_m[n] = m_n``.
50
+ * Coefficients ``_a[n] = a_n`` where ``a_n`` has length ``n`` and contains
51
+ :math:`(a_{n,0}, \\dots, a_{n,n-1})`.
52
+
53
+ The coefficient row :math:`a_n` is computed using an intermediate quantity
54
+ :math:`R_{n,k}` formed via discrete convolutions of previous rows.
55
+
56
+ Examples
57
+ --------
58
+
59
+ .. code-block:: python
60
+
61
+ >>> import numpy as np
62
+ >>> eig = np.array([1.0, 2.0, 3.0])
63
+ >>> mu = Moments(eig)
64
+ >>> mu(3, t=0.0) # equals m_3
65
+ 12.0
66
+ >>> mu(3, t=0.1)
67
+ 14.203...
68
+ """
69
+
70
+ # ====
71
+ # init
72
+ # ====
73
+
74
+ def __init__(self, eig):
75
+ """
76
+ Initialization.
77
+ """
78
+
79
+ self.eig = numpy.asarray(eig, dtype=float)
80
+
81
+ # Memoized moments m_n
82
+ self._m = {0: 1.0}
83
+
84
+ # Memoized coefficients a[n] = array of length n
85
+ # (a_{n,0},...,a_{n,n-1})
86
+ self._a = {0: numpy.array([1.0])}
87
+
88
+ # ----------
89
+ # moments
90
+ # ----------
91
+
92
+ def m(self, n):
93
+ """
94
+ Compute raw moment :math:`m_n`.
95
+
96
+ Parameters
97
+ ----------
98
+
99
+ n : int
100
+ Order of the moment.
101
+
102
+ Returns
103
+ -------
104
+
105
+ m_n : float
106
+ The raw moment :math:`m_n = \\mathbb{E}[\\lambda^n]`, estimated by
107
+ the sample mean of ``eig**n``.
108
+ """
109
+
110
+ if n not in self._m:
111
+ self._m[n] = numpy.mean(self.eig ** n)
112
+ return self._m[n]
113
+
114
+ # -------------
115
+ # coefficients
116
+ # -------------
117
+
118
+ def coeffs(self, n):
119
+ """
120
+ Get coefficients :math:`a_n` for :math:`\\mu_n(t)`.
121
+
122
+ Parameters
123
+ ----------
124
+
125
+ n : int
126
+ Order of :math:`\\mu_n(t)`.
127
+
128
+ Returns
129
+ -------
130
+
131
+ a_n : numpy.ndarray
132
+ Array of shape ``(n,)`` containing :math:`(a_{n,0}, \\dots, a_{n,n-1})`.
133
+ """
134
+
135
+ if n in self._a:
136
+ return self._a[n]
137
+
138
+ # Ensure previous rows exist
139
+ for r in range(1, n):
140
+ if r not in self._a:
141
+ self._compute_row(r)
142
+
143
+ self._compute_row(n)
144
+ return self._a[n]
145
+
146
+ def _compute_row(self, n):
147
+ """
148
+ Compute and memoize the coefficient row :math:`a_n`.
149
+
150
+ Parameters
151
+ ----------
152
+
153
+ n : int
154
+ Row index to compute.
155
+
156
+ Notes
157
+ -----
158
+
159
+ For :math:`n=1`, the row is
160
+
161
+ .. math::
162
+
163
+ a_{1,0} = m_1.
164
+
165
+ For :math:`n \\ge 2`, let :math:`R_n` be a length ``n-1`` array defined
166
+ by convolution of previous rows:
167
+
168
+ .. math::
169
+
170
+ R_n = \\sum_{i=1}^{n-1} (a_i * a_{n-i})\\big|_{0:(n-2)}.
171
+
172
+ Then for :math:`k = 0, \\dots, n-2`,
173
+
174
+ .. math::
175
+
176
+ a_{n,k} = \\frac{1 + k/2}{(n-1-k)} R_{n,k},
177
+
178
+ and the last coefficient is chosen so that :math:`\\mu_n(0)=m_n`:
179
+
180
+ .. math::
181
+
182
+ a_{n,n-1} = m_n - \\sum_{k=0}^{n-2} a_{n,k}.
183
+ """
184
+
185
+ if n in self._a:
186
+ return
187
+
188
+ if n == 1:
189
+ self._a[1] = numpy.array([self.m(1)])
190
+ return
191
+
192
+ # Ensure all smaller rows exist
193
+ for r in range(1, n):
194
+ if r not in self._a:
195
+ self._compute_row(r)
196
+
197
+ a_n = numpy.zeros(n, dtype=float)
198
+
199
+ # Compute R_{n,k} via convolutions:
200
+ # R_n = sum_{i=1}^{n-1} convolve(a[i], a[n-i]) truncated to length n-1
201
+ R = numpy.zeros(n - 1, dtype=float)
202
+ for i in range(1, n):
203
+ conv = numpy.convolve(self._a[i], self._a[n - i])
204
+ R += conv[: n - 1]
205
+
206
+ k = numpy.arange(n - 1, dtype=float)
207
+ factors = (1.0 + 0.5 * k) / (n - 1 - k)
208
+ a_n[: n - 1] = factors * R
209
+
210
+ # k = n-1 from the initial condition mu_n(0) = m_n
211
+ a_n[n - 1] = self.m(n) - a_n[: n - 1].sum()
212
+
213
+ self._a[n] = a_n
214
+
215
+ # ----------
216
+ # evaluate
217
+ # ----------
218
+
219
+ def __call__(self, n, t=0.0):
220
+ """
221
+ Evaluate :math:`\\mu_n(t)`.
222
+
223
+ Parameters
224
+ ----------
225
+
226
+ n : int
227
+ Order of :math:`\\mu_n(t)`.
228
+
229
+ t : float, default=0.0
230
+ Deformation parameter.
231
+
232
+ Returns
233
+ -------
234
+
235
+ mu_n : float
236
+ The value of :math:`\\mu_n(t)`.
237
+
238
+ Notes
239
+ -----
240
+
241
+ This function evaluates
242
+
243
+ .. math::
244
+
245
+ \\mu_n(t) = \\sum_{k=0}^{n-1} a_{n,k} \\, e^{k t}.
246
+
247
+ For ``n == 0``, it returns ``1.0``.
248
+ """
249
+
250
+ if n == 0:
251
+ return 1.0
252
+
253
+ a_n = self.coeffs(n)
254
+ k = numpy.arange(n, dtype=float)
255
+ return numpy.dot(a_n, numpy.exp(k * t))
256
+
257
+ # ===========================
258
+ # Algebraic Stieltjes Moments
259
+ # ===========================
260
+
261
+
262
+ class AlgebraicStieltjesMoments(object):
263
+ """
264
+ Given coefficients a[i,j] for P(z,m)=sum_{i,j} a[i,j] z^i m^j,
265
+ compute the large-|z| branch
266
+ m(z) = sum_{k>=0} mu_series[k] / z^{k+1}.
267
+
268
+ Convention here: choose mu0 (the leading coefficient) by solving the
269
+ leading-diagonal equation and (by default) picking the root closest
270
+ to -1, i.e. m(z) ~ -1/z.
271
+
272
+ The returned 'moments(N)' are normalized density moments:
273
+ mu_density[k] = mu_series[k] / mu_series[0]
274
+ so mu_density[0] = 1.
275
+ """
276
+
277
+ def __init__(self, a, mu0=None):
278
+ self.a = numpy.asarray(a)
279
+ # Ensure valid
280
+ self.a[-1, 0] = 0.0
281
+ if self.a.ndim != 2:
282
+ raise ValueError("a must be a 2D NumPy array with a[i,j]=a_{ij}.")
283
+
284
+ self.I = self.a.shape[0] - 1
285
+ self.J = self.a.shape[1] - 1
286
+
287
+ nz = numpy.argwhere(self.a != 0)
288
+ if nz.size == 0:
289
+ raise ValueError("All coefficients are zero.")
290
+
291
+ # r = max(i-j) over nonzero terms
292
+ self.r = int(numpy.max(nz[:, 0] - nz[:, 1]))
293
+
294
+ # Group coefficients by diagonal offset s = r - (i-j) >= 0
295
+ # diag[s] is list of (j, a_ij) for which i-j = r-s
296
+ self.diag = {}
297
+ for i, j in nz:
298
+ i = int(i)
299
+ j = int(j)
300
+ coeff = self.a[i, j]
301
+ s = self.r - (i - j)
302
+ if s >= 0:
303
+ self.diag.setdefault(int(s), []).append((j, coeff))
304
+
305
+ # Choose mu0 (series leading coefficient). This should be
306
+ # -1 for m(z) ~ -1/z, but it may only hold approximately.
307
+ if mu0 is None:
308
+ self.mu0 = self._solve_mu0()
309
+ else:
310
+ self.mu0 = mu0
311
+
312
+ # Precompute mu0^p up to p=J
313
+ self.mu0pow = [1]
314
+ for _ in range(self.J):
315
+ self.mu0pow.append(self.mu0pow[-1] * self.mu0)
316
+
317
+ # Linear coefficient A0 = sum_{i-j=r} j a_ij mu0^{j-1}
318
+ self.A0 = 0
319
+ for j, coeff in self.diag.get(0, []):
320
+ if j > 0:
321
+ self.A0 += j * coeff * self.mu0pow[j - 1]
322
+ if self.A0 == 0:
323
+ raise ValueError("A0 is zero for this mu0; the sequential recursion is degenerate.")
324
+
325
+ # Stored series moments mu_series[0..]
326
+ self._mu = [self.mu0]
327
+
328
+ # Convolution table c[j][n] = coefficient of w^n in (S(w))^j,
329
+ # where S(w) = sum_{t>=0} mu_series[t] w^t and m(z)=w S(w), w=1/z.
330
+ #
331
+ # We store c as lists growing in n: c[j][n] for j=0..J.
332
+ self._c = [[0] for _ in range(self.J + 1)]
333
+ self._c[0][0] = 1
334
+ for j in range(1, self.J + 1):
335
+ self._c[j][0] = self.mu0pow[j]
336
+
337
+ def _solve_mu0(self):
338
+ # Leading diagonal polynomial L(m) = sum_{i-j=r} a_ij m^j.
339
+ # That means i = j + r, so coefficient is a[j+r, j] if in bounds.
340
+ coeffs = numpy.zeros(self.J + 1, dtype=numpy.complex128)
341
+ for j in range(self.J + 1):
342
+ i = j + self.r
343
+ if 0 <= i <= self.I:
344
+ coeffs[j] = self.a[i, j]
345
+
346
+ if not numpy.any(coeffs != 0):
347
+ raise ValueError("Leading diagonal polynomial is identically zero; cannot determine mu0.")
348
+
349
+ deg = int(numpy.max(numpy.nonzero(coeffs)[0]))
350
+ roots = numpy.roots(coeffs[:deg + 1][::-1]) # descending powers for numpy.roots
351
+
352
+ # Targetting mu0 = -1 for ~ -1/z asymptotics
353
+ mu0 = roots[numpy.argmin(numpy.abs(roots + 1))]
354
+
355
+ if abs(mu0.imag) < 1e-12:
356
+ mu0 = mu0.real
357
+ return mu0
358
+
359
+ def _ensure(self, N):
360
+ # Compute mu_series up to index N (inclusive)
361
+ while len(self._mu) <= N:
362
+ k = len(self._mu) # compute mu_k
363
+
364
+ # Compute f[j] = coefficient of w^k in (S_trunc(w))^j,
365
+ # where S_trunc uses mu_0..mu_{k-1} only (i.e. mu_k treated as 0).
366
+ # Key fact: in the true c[j,k], mu_k can only appear linearly as j*mu_k*mu0^{j-1}.
367
+ f = [0] * (self.J + 1)
368
+ f[0] = 0
369
+ for j in range(1, self.J + 1):
370
+ ssum = 0
371
+ # sum_{t=1..k-1} mu_t * c[j-1, k-t]
372
+ for t in range(1, k):
373
+ ssum += self._mu[t] * self._c[j - 1][k - t]
374
+ # recurrence: c[j,k] = mu0*c[j-1,k] + sum_{t=1..k-1} mu_t*c[j-1,k-t] + mu_k*c[j-1,0]
375
+ # with mu_k=0 for f, and c[j-1,k]=f[j-1]
376
+ f[j] = self.mu0 * f[j - 1] + ssum
377
+
378
+ # Build the linear equation for mu_k:
379
+ # A0*mu_k + rest = 0
380
+ rest = 0
381
+
382
+ # s=0 diagonal contributes coeff*(f[j]) (the mu_k-free part)
383
+ for j, coeff in self.diag.get(0, []):
384
+ if j == 0:
385
+ # only affects k=0, but we never come here with k=0
386
+ continue
387
+ rest += coeff * f[j]
388
+
389
+ # lower diagonals s=1..k contribute coeff*c[j,k-s] (already known since k-s < k)
390
+ for s in range(1, k + 1):
391
+ entries = self.diag.get(s)
392
+ if not entries:
393
+ continue
394
+ n = k - s
395
+ for j, coeff in entries:
396
+ if j == 0:
397
+ if n == 0:
398
+ rest += coeff
399
+ else:
400
+ rest += coeff * self._c[j][n]
401
+
402
+ mu_k = -rest / self.A0
403
+ self._mu.append(mu_k)
404
+
405
+ # Now append the new column k to c using the full convolution recurrence:
406
+ # c[j,k] = sum_{t=0..k} mu_t * c[j-1,k-t]
407
+ for j in range(self.J + 1):
408
+ self._c[j].append(0)
409
+
410
+ self._c[0][k] = 0
411
+ for j in range(1, self.J + 1):
412
+ val = 0
413
+ for t in range(0, k + 1):
414
+ val += self._mu[t] * self._c[j - 1][k - t]
415
+ self._c[j][k] = val
416
+
417
+ # --- API ---
418
+
419
+ def __call__(self, k):
420
+ self._ensure(k)
421
+ return self._mu[k] / self._mu[0]
422
+
423
+ def moments(self, N):
424
+ # normalized density moments so moment 0 is 1
425
+ self._ensure(N)
426
+ mu0 = self._mu[0]
427
+ return numpy.array([self._mu[k] / mu0 for k in range(N + 1)])
428
+
429
+ def radius(self, N):
430
+ # Estimate the radius of convergence of the Stieltjes
431
+ # series
432
+ if N < 3:
433
+ raise RuntimeError("Order is too small, choose a larger value of N")
434
+ self._ensure(N)
435
+ return max([numpy.abs(self._mu[j] / self._mu[j-1]) for j in range(2,N+1)])
436
+
437
+ def stieltjes(self, z, N):
438
+ # Estimate Stieltjes transform (root) using moment
439
+ # expansion
440
+ z = numpy.asarray(z)
441
+ mu = self.moments(N)
442
+ return -numpy.sum(z[..., numpy.newaxis]**(-numpy.arange(N+1)-1) * mu,
443
+ axis=-1)
444
+
445
+ def target_pt(self, N=15):
446
+ # Obtain an estimate of the Stieltjes transform at a
447
+ # single point z where the estimate is likely reliable
448
+ z = 1j + 2j * self.radius(N)
449
+ return z, self.stieltjes(z, N)
450
+
@@ -20,7 +20,9 @@ from ._continuation_algebraic import sample_z_joukowski, \
20
20
  from ._edge import evolve_edges, merge_edges
21
21
  from ._decompress import decompress_newton
22
22
  from ._decompress2 import decompress_coeffs
23
- from ._homotopy import stieltjes_poly
23
+ from ._homotopy import StieltjesPoly
24
+ from ._discriminant import compute_singular_points
25
+ from ._moments import MomentsESD
24
26
  from .._free_form._support import supp
25
27
  from .._free_form._plot_util import plot_density
26
28
 
@@ -143,6 +145,7 @@ class AlgebraicForm(object):
143
145
  self.A = None
144
146
  self.eig = None
145
147
  self.stieltjes = None
148
+ self.moments = None
146
149
  self.support = support
147
150
  self.delta = delta # Offset above real axis to apply Plemelj formula
148
151
 
@@ -177,6 +180,7 @@ class AlgebraicForm(object):
177
180
  # Use empirical Stieltjes function
178
181
  self.stieltjes = lambda z: \
179
182
  numpy.mean(1.0/(self.eig-z[:, numpy.newaxis]), axis=-1)
183
+ self.moments = MomentsESD(self.eig)
180
184
 
181
185
  # Support
182
186
  if support is None:
@@ -262,11 +266,13 @@ class AlgebraicForm(object):
262
266
 
263
267
  # Fitting (w_inf = None means adaptive weight selection)
264
268
  m1_fit = self.stieltjes(z_fit)
265
- a_coeffs = fit_polynomial_relation(z_fit, m1_fit, s=deg_m, deg_z=deg_z,
266
- ridge_lambda=reg,
267
- triangular=triangular,
268
- normalize=normalize, mu=mu,
269
- mu_reg=mu_reg)
269
+ a_coeffs, fit_metrics = fit_polynomial_relation(
270
+ z_fit, m1_fit, s=deg_m, deg_z=deg_z, ridge_lambda=reg,
271
+ triangular=triangular, normalize=normalize, mu=mu,
272
+ mu_reg=mu_reg)
273
+
274
+ # Compute global branhc points, zeros of leading a_j, and support
275
+ branch_points, a_s_zero, support = compute_singular_points(a_coeffs)
270
276
 
271
277
  self.a_coeffs = a_coeffs
272
278
 
@@ -282,9 +288,13 @@ class AlgebraicForm(object):
282
288
  eta=max(y_eps, 1e-2), n_x=128,
283
289
  max_bad_frac=0.05)
284
290
 
291
+ status['branch_points'] = branch_points
292
+ status['a_s_zero'] = a_s_zero
285
293
  status['res_max'] = float(res_max)
286
294
  status['res_99_9'] = float(res_99_9)
295
+ status['fit_metrics'] = fit_metrics
287
296
  self.status = status
297
+ self.stieltjes = StieltjesPoly(self.a_coeffs)
288
298
 
289
299
  if verbose:
290
300
  print(f'fit residual max : {res_max:>0.4e}')
@@ -309,7 +319,7 @@ class AlgebraicForm(object):
309
319
  else:
310
320
  print('\nStieltjes sanity check: OK')
311
321
 
312
- return a_coeffs, status
322
+ return a_coeffs, support, status
313
323
 
314
324
  # =============
315
325
  # generate grid
@@ -389,13 +399,7 @@ class AlgebraicForm(object):
389
399
  x = self._generate_grid(1.25)
390
400
 
391
401
  # Preallocate density to zero
392
- rho = numpy.zeros_like(x)
393
-
394
- for idx, x_i in enumerate(x):
395
- m_i = stieltjes_poly(x_i, self.a_coeffs)
396
- rho[idx] = m_i.imag
397
-
398
- rho = rho / numpy.pi
402
+ rho = self.stieltjes(x).imag / numpy.pi
399
403
 
400
404
  # if self.method == 'jacobi':
401
405
  # rho[mask] = jacobi_density(x[mask], self.psi, self.support,
@@ -663,12 +667,9 @@ class AlgebraicForm(object):
663
667
  # Decompression ratio equal to e^{t}.
664
668
  alpha = numpy.atleast_1d(size) / self.n
665
669
 
666
- def m_fn(z):
667
- return stieltjes_poly(z, self.a_coeffs)
668
-
669
670
  # Lower and upper bound on new support
670
- hilb_lb = (1.0 / m_fn(self.lam_m + self.delta * 1j).item()).real
671
- hilb_ub = (1.0 / m_fn(self.lam_p + self.delta * 1j).item()).real
671
+ hilb_lb = (1.0 / self.stieltjes(self.lam_m + self.delta * 1j).item()).real
672
+ hilb_ub = (1.0 / self.stieltjes(self.lam_p + self.delta * 1j).item()).real
672
673
  lb = self.lam_m - (numpy.max(alpha) - 1) * hilb_lb
673
674
  ub = self.lam_p - (numpy.max(alpha) - 1) * hilb_ub
674
675
 
@@ -689,9 +690,7 @@ class AlgebraicForm(object):
689
690
  z_query = x + 1j * self.delta
690
691
 
691
692
  # Initial condition at t=0 (physical branch)
692
- # w0_list = self.stieltjes(z_query)
693
- stieltjes = numpy.vectorize(m_fn)
694
- w0_list = stieltjes(z_query)
693
+ w0_list = self.stieltjes(z_query)
695
694
 
696
695
  # Times
697
696
  t = numpy.log(alpha)
@@ -721,9 +720,8 @@ class AlgebraicForm(object):
721
720
  for i in range(alpha.size):
722
721
  coeffs_i = decompress_coeffs(self.a_coeffs,
723
722
  numpy.log(alpha[i]))
724
- for j, x_j in enumerate(x):
725
- m_j = stieltjes_poly(x_j, coeffs_i)
726
- rho[i, j] = m_j.imag
723
+ stieltjes_i = StieltjesPoly(coeffs_i)
724
+ rho[i, :] = stieltjes_i.imag
727
725
 
728
726
  rho = rho / numpy.pi
729
727
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: freealg
3
- Version: 0.7.6
3
+ Version: 0.7.8
4
4
  Summary: Free probability for large matrices
5
5
  Home-page: https://github.com/ameli/freealg
6
6
  Download-URL: https://github.com/ameli/freealg/archive/main.zip
@@ -1,15 +1,17 @@
1
1
  freealg/__init__.py,sha256=SjcYb6HWmaclnnM-m1eC1honZRyfNBWYDYBx23kSdjo,833
2
- freealg/__version__.py,sha256=wu65dmVM9fKR1rBHH263ls8Ca2FZzb0ejYcrP_Ld0iY,22
2
+ freealg/__version__.py,sha256=uC8wB9mRblQ0jUBAOUyCQLUQJ39MC2xybVLB_8ZsevU,22
3
3
  freealg/_util.py,sha256=RzccUCORgzrI9NdNqwMVugiHU0uDKkJFcIyjFMUOnv8,2518
4
4
  freealg/_algebraic_form/__init__.py,sha256=MIB_jVgw2qI-JW_ypqaFSeNAB6c4GvpjNySnap_a6hg,398
5
5
  freealg/_algebraic_form/_constraints.py,sha256=37U7nvtCTocuS7l_nfUznkPi195PY7eXFzeiikrv3B0,2448
6
- freealg/_algebraic_form/_continuation_algebraic.py,sha256=SyuWjw0jABlIst9RjHS13gG2M94KZwwrTa3eeJNfmRI,19098
6
+ freealg/_algebraic_form/_continuation_algebraic.py,sha256=KundB9VfX61a35VRxLFyuvB5A51QdT4PD2ffAMjrKR0,19383
7
7
  freealg/_algebraic_form/_decompress.py,sha256=gGtixLOVxlMy5S-NsXgoA7lIrB7u7nUZImQk1mIDo3s,21101
8
8
  freealg/_algebraic_form/_decompress2.py,sha256=Ng9w9xmGe9M-DApp35IeNeQlvszfzT4NZx5BQn0lQ3I,2459
9
+ freealg/_algebraic_form/_discriminant.py,sha256=755pproom6-xThFARaH20m4GuBwwZS2rc0Y80Yg6NzY,5331
9
10
  freealg/_algebraic_form/_edge.py,sha256=7l9QyLJDxaEY4WB6MCUFtfEZSf04wyHwH7YPHFJXSbM,10690
10
- freealg/_algebraic_form/_homotopy.py,sha256=2oMcqJ2VJGzG7WKGM6FUS3923GT8Adtq_hLPEGgzqoU,3990
11
+ freealg/_algebraic_form/_homotopy.py,sha256=YmNpRbKYKHQIYQpRsyFqUIQhaCr4bvxSQLyPwg61iO8,9174
12
+ freealg/_algebraic_form/_moments.py,sha256=xSQdJhPUtSnXOLrd2JDmz4O_soDCtmcDt5_iSeiNOZg,12404
11
13
  freealg/_algebraic_form/_sheets_util.py,sha256=6OLzWQKu-gN8rxM2rbpbN8TjNZFmD8UJ-8t9kcZdkCo,4174
12
- freealg/_algebraic_form/algebraic_form.py,sha256=N-R8cv7580p-iPW7oPlDZ9py7BVjEmPcCjs25UVtNV4,32706
14
+ freealg/_algebraic_form/algebraic_form.py,sha256=fmcyunM4fkRjuVjQpDtgwBBs5wTbShPyNGGSpqAynIM,32775
13
15
  freealg/_free_form/__init__.py,sha256=5cnSX7kHci3wKx6-BEFhmVY_NjjmQAq1JjWPTEqETTg,611
14
16
  freealg/_free_form/_chebyshev.py,sha256=zkyVA8NLf7uUKlJdLz4ijd_SurdsqUgkA5nHGWSybaE,6916
15
17
  freealg/_free_form/_damp.py,sha256=k2vtBtWOxQBf4qXaWu_En81lQBXbEO4QbxxWpvuVhdE,1802
@@ -42,9 +44,9 @@ freealg/distributions/_wigner.py,sha256=epgx6ne6R_7to5j6-QsWIAVFJQFquWMmYgnZYMN4
42
44
  freealg/visualization/__init__.py,sha256=NLq_zwueF7ytZ8sl8zLPqm-AODxxXNvfMozHGmmklcE,435
43
45
  freealg/visualization/_glue_util.py,sha256=2oKnEYjUOS4OZfivmciVLauVr53kyHMwi6c2zRKilTQ,693
44
46
  freealg/visualization/_rgb_hsv.py,sha256=rEskxXxSlKKxIrHRslVkgxHtD010L3ge9YtcVsOPl8E,3650
45
- freealg-0.7.6.dist-info/licenses/AUTHORS.txt,sha256=0b67Nz4_JgIzUupHJTAZxu5QdSUM_HRM_X_w4xCb17o,30
46
- freealg-0.7.6.dist-info/licenses/LICENSE.txt,sha256=J-EEYEtxb3VVf_Bn1TYfWnpY5lMFIM15iLDDcnaDTPA,1443
47
- freealg-0.7.6.dist-info/METADATA,sha256=cifScuAeI6gPbjzzCzBMMLqTYtxbqIIJhuICSIi-BkE,5516
48
- freealg-0.7.6.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
49
- freealg-0.7.6.dist-info/top_level.txt,sha256=eR2wrgYwDdnnJ9Zf5PruPqe4kQav0GMvRsqct6y00Q8,8
50
- freealg-0.7.6.dist-info/RECORD,,
47
+ freealg-0.7.8.dist-info/licenses/AUTHORS.txt,sha256=0b67Nz4_JgIzUupHJTAZxu5QdSUM_HRM_X_w4xCb17o,30
48
+ freealg-0.7.8.dist-info/licenses/LICENSE.txt,sha256=J-EEYEtxb3VVf_Bn1TYfWnpY5lMFIM15iLDDcnaDTPA,1443
49
+ freealg-0.7.8.dist-info/METADATA,sha256=b5v5ekXtGJh4o-fcwxpF58bMWc8oZs4qrgfVMDO79h0,5516
50
+ freealg-0.7.8.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
51
+ freealg-0.7.8.dist-info/top_level.txt,sha256=eR2wrgYwDdnnJ9Zf5PruPqe4kQav0GMvRsqct6y00Q8,8
52
+ freealg-0.7.8.dist-info/RECORD,,