freealg 0.7.7__py3-none-any.whl → 0.7.9__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.7"
1
+ __version__ = "0.7.9"
@@ -3,17 +3,20 @@
3
3
  # =======
4
4
 
5
5
  import numpy
6
+ from ._moments import AlgebraicStieltjesMoments
7
+ from tqdm import tqdm
6
8
 
7
9
  __all__ = ['stieltjes_poly']
8
10
 
9
11
 
10
12
  # =====================
11
- # stieltjes select root
13
+ # select root
12
14
  # =====================
13
15
 
14
- def stieltjes_select_root(roots, z, w_prev=None):
16
+ def select_root(roots, z, target):
15
17
  """
16
- Select the Stieltjes-branch root among candidates at a given z.
18
+ Select the root among Herglotz candidates at a given z closest to a
19
+ given target
17
20
 
18
21
  Parameters
19
22
  ----------
@@ -22,9 +25,9 @@ def stieltjes_select_root(roots, z, w_prev=None):
22
25
  z : complex
23
26
  Evaluation point. The Stieltjes/Herglotz branch satisfies
24
27
  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.
28
+ target : complex
29
+ Previous continuation value used to enforce continuity, or
30
+ target value.
28
31
 
29
32
  Returns
30
33
  -------
@@ -40,11 +43,6 @@ def stieltjes_select_root(roots, z, w_prev=None):
40
43
 
41
44
  desired_sign = numpy.sign(z.imag)
42
45
 
43
- if w_prev is None:
44
- target = -1.0 / z
45
- else:
46
- target = complex(w_prev)
47
-
48
46
  # Apply a soft Herglotz sign filter: prefer roots with Im(w) having the
49
47
  # same sign as Im(z), allowing tiny numerical violations near the axis.
50
48
  imag_roots = numpy.imag(roots)
@@ -62,77 +60,232 @@ def stieltjes_select_root(roots, z, w_prev=None):
62
60
  # stieltjes poly
63
61
  # ==============
64
62
 
65
- def stieltjes_poly(z, a, eps=None, height=1e+4, steps=100):
63
+ class StieltjesPoly(object):
66
64
  """
67
- Evaluate the Stieltjes-branch solution m(z) of an algebraic equation.
65
+ Stieltjes-branch evaluator for an algebraic equation P(z, m) = 0.
66
+
67
+ This class represents the Stieltjes-branch solution m(z) of an algebraic
68
+ equation defined by a polynomial relation
68
69
 
69
- The coefficients `a` define a polynomial relation
70
70
  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.
71
+
72
+ where P is a polynomial in z and m with monomial-basis coefficients.
73
+ The coefficient matrix ``a`` is fixed at construction time, and all
74
+ quantities depending only on ``a`` are precomputed. Evaluation at a
75
+ complex point ``z`` is performed via :meth:`evaluate`. The instance is
76
+ also callable; :meth:`__call__` supports scalar or vector inputs and
77
+ applies :meth:`evaluate` elementwise.
78
+
79
+ The Stieltjes branch is selected by initializing in the appropriate
80
+ half-plane using an asymptotic Stieltjes estimate and then performing
81
+ homotopy continuation along a straight-line path in the complex plane.
74
82
 
75
83
  Parameters
76
84
  ----------
77
- z : complex
78
- Evaluation point. Must be a single value.
79
85
  a : ndarray, shape (L, K)
80
- Coefficient matrix defining P(z, m) in the monomial basis.
86
+ Coefficient matrix defining P(z, m) in the monomial basis. For fixed
87
+ z, the coefficients of the polynomial in m are assembled from powers
88
+ of z.
81
89
  eps : float or None, optional
82
90
  If Im(z) == 0, use z + i*eps as the boundary evaluation point.
83
91
  If None and Im(z) == 0, eps is set to 1e-8 * max(1, |z|).
84
- height : float, optional
92
+ height : float, default = 2.0
85
93
  Imaginary height used for the starting point z0 in the same
86
94
  half-plane as the evaluation point.
87
- steps : int, optional
95
+ steps : int, default = 100
88
96
  Number of continuation steps along the homotopy path.
97
+ order : int, default = 15
98
+ Number of moments in Stieltjes estimate
89
99
 
90
- Returns
100
+ Methods
91
101
  -------
92
- w : complex
93
- Value of the Stieltjes-branch solution m(z) (or m(z+i*eps) if z is
94
- real).
102
+ evaluate(z)
103
+ Evaluate the Stieltjes-branch solution m(z) at a single complex point.
104
+
105
+ __call__(z)
106
+ If ``z`` is scalar, returns ``evaluate(z, ...)``.
107
+ If ``z`` is array-like, returns an array of the same shape, where each
108
+ entry is computed by calling ``evaluate`` on the corresponding element.
109
+
110
+ Notes
111
+ -----
112
+ If an input ``z`` value is real (Im(z) == 0), the evaluation is interpreted
113
+ as a boundary value by replacing that element with z + i*eps. If ``eps`` is
114
+ None, eps is chosen per element as 1e-8 * max(1, |z|).
95
115
  """
96
116
 
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)
117
+ def __init__(self, a, eps=None, height=2.0, steps=100, order=15):
118
+ a = numpy.asarray(a)
119
+ if a.ndim != 2:
120
+ raise ValueError("a must be a 2D array.")
121
+
122
+ self.a = a
123
+ self.a_l, _ = a.shape
124
+ self.eps = eps
125
+ self.height = height
126
+ self.steps = steps
127
+ self.order = order
128
+
129
+ self.mom = AlgebraicStieltjesMoments(a)
130
+ self.rad = 1.0 + self.height * self.mom.radius(self.order)
131
+ self.z0_p = 1j * self.rad
132
+ self.m0_p = self.mom.stieltjes(self.z0_p, self.order)
133
+ self.z0_m = -1j * self.rad
134
+ self.m0_m = self.mom.stieltjes(self.z0_m, self.order)
135
+
136
+ def _poly_coeffs_m(self, z_val):
137
+ z_powers = z_val ** numpy.arange(self.a_l)
138
+ return (z_powers @ self.a)[::-1]
139
+
140
+ def _poly_roots(self, z_val):
141
+ coeffs = numpy.asarray(self._poly_coeffs_m(z_val),
142
+ dtype=numpy.complex128)
114
143
  return numpy.roots(coeffs)
115
144
 
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
145
+ def evaluate(self, z, eps=None, height=2.0, steps=100, order=15):
146
+ """
147
+ Evaluate the Stieltjes-branch solution m(z) at a single point.
148
+
149
+ Parameters are as in the original function, except ``a`` is fixed at
150
+ construction time.
151
+ """
152
+ z = complex(z)
153
+
154
+ if steps < 1:
155
+ raise ValueError("steps must be a positive integer.")
156
+
157
+ # Boundary-value interpretation on the real axis
158
+ if z.imag == 0.0:
159
+ if self.eps is None:
160
+ eps_loc = 1e-8 * max(1.0, abs(z))
161
+ else:
162
+ eps_loc = float(self.eps)
163
+ z_eval = z + 1j * eps_loc
164
+ else:
165
+ z_eval = z
166
+
167
+ half_sign = numpy.sign(z_eval.imag)
168
+ if half_sign == 0.0:
169
+ half_sign = 1.0
170
+
171
+ # # If z is outside radius of convergence, no homotopy
172
+ # # necessary
173
+ # if numpy.abs(z) > self.rad:
174
+ # target = self.mom.stieltjes(z, self.order)
175
+ # return select_root(self._poly_roots(z), z, target)
176
+
177
+ if half_sign > 0.0:
178
+ z0 = self.z0_p
179
+ target = self.m0_p
180
+ else:
181
+ z0 = self.z0_m
182
+ target = self.m0_m
183
+
184
+ # Initialize at z0
185
+ w_prev = select_root(self._poly_roots(z0), z0, target)
186
+
187
+ # Straight-line homotopy continuation
188
+ for tau in numpy.linspace(0.0, 1.0, int(self.steps) + 1)[1:]:
189
+ z_tau = z0 + tau * (z_eval - z0)
190
+ w_prev = select_root(self._poly_roots(z_tau), z_tau, w_prev)
191
+
192
+ return w_prev
193
+
194
+ def __call__(self, z, progress=False):
195
+ # Scalar fast-path
196
+ if numpy.isscalar(z):
197
+ return self.evaluate(z)
198
+
199
+ # Array-like: evaluate elementwise, preserving shape
200
+ z_arr = numpy.asarray(z)
201
+ out = numpy.empty(z_arr.shape, dtype=numpy.complex128)
202
+
203
+ # Iterate over indices so we can pass Python scalars into evaluate()
204
+ if progress:
205
+ indices = tqdm(numpy.ndindex(z_arr.shape),total=z_arr.size)
206
+ else:
207
+ indices = numpy.ndindex(z_arr.shape)
208
+ for idx in indices:
209
+ out[idx] = self.evaluate(z_arr[idx])
210
+
211
+ return out
212
+
213
+
214
+ # def stieltjes_poly(z, a, eps=None, height=2., steps=100, order=15):
215
+ # """
216
+ # Evaluate the Stieltjes-branch solution m(z) of an algebraic equation.
217
+
218
+ # The coefficients `a` define a polynomial relation
219
+ # P(z, m) = 0,
220
+ # where P is a polynomial in z and m with monomial-basis coefficients
221
+ # arranged so that for fixed z, the coefficients of the polynomial in m
222
+ # can be assembled from powers of z.
223
+
224
+ # Parameters
225
+ # ----------
226
+ # z : complex
227
+ # Evaluation point. Must be a single value.
228
+ # a : ndarray, shape (L, K)
229
+ # Coefficient matrix defining P(z, m) in the monomial basis.
230
+ # eps : float or None, optional
231
+ # If Im(z) == 0, use z + i*eps as the boundary evaluation point.
232
+ # If None and Im(z) == 0, eps is set to 1e-8 * max(1, |z|).
233
+ # height : float, default = 2.0
234
+ # Imaginary height used for the starting point z0 in the same
235
+ # half-plane as the evaluation point.
236
+ # steps : int, default = 100
237
+ # Number of continuation steps along the homotopy path.
238
+ # order : int, default = 15
239
+ # Number of moments in Stieltjes estimate
240
+
241
+ # Returns
242
+ # -------
243
+ # w : complex
244
+ # Value of the Stieltjes-branch solution m(z) (or m(z+i*eps) if z is
245
+ # real).
246
+ # """
247
+
248
+ # z = complex(z)
249
+ # a = numpy.asarray(a)
250
+
251
+ # if a.ndim != 2:
252
+ # raise ValueError('a must be a 2D array.')
253
+
254
+ # if steps < 1:
255
+ # raise ValueError("steps must be a positive integer.")
256
+
257
+ # a_l, _ = a.shape
258
+ # mom = AlgebraicStieltjesMoments(a)
259
+
260
+ # def poly_coeffs_m(z_val):
261
+ # z_powers = z_val ** numpy.arange(a_l)
262
+ # return (z_powers @ a)[::-1]
263
+
264
+ # def poly_roots(z_val):
265
+ # coeffs = numpy.asarray(poly_coeffs_m(z_val), dtype=numpy.complex128)
266
+ # return numpy.roots(coeffs)
267
+
268
+ # # If user asked for a real-axis value, interpret as boundary value from C+.
269
+ # if z.imag == 0.0:
270
+ # if eps is None:
271
+ # eps = 1e-8 * max(1.0, abs(z))
272
+ # z_eval = z + 1j * float(eps)
273
+ # else:
274
+ # z_eval = z
275
+
276
+ # half_sign = numpy.sign(z_eval.imag)
277
+ # if half_sign == 0.0:
278
+ # half_sign = 1.0
279
+
280
+ # z0 = 1j * float(half_sign) * (1. + height * mom.radius(order))
281
+ # target = mom.stieltjes(z0, order)
282
+
283
+ # # Initialize at z0 via asymptotic / Im-sign selection.
284
+ # w_prev = select_root(poly_roots(z0), z0, target)
285
+
286
+ # # Straight-line homotopy from z0 to z_eval.
287
+ # for tau in numpy.linspace(0.0, 1.0, int(steps) + 1)[1:]:
288
+ # z_tau = z0 + tau * (z_eval - z0)
289
+ # w_prev = select_root(poly_roots(z_tau), z_tau, w_prev)
290
+
291
+ # return w_prev
@@ -0,0 +1,443 @@
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)
@@ -20,10 +20,11 @@ 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
24
  from ._discriminant import compute_singular_points
25
+ from ._moments import MomentsESD
25
26
  from .._free_form._support import supp
26
- from .._free_form._plot_util import plot_density
27
+ from .._free_form._plot_util import plot_density, plot_hilbert, plot_stieltjes
27
28
 
28
29
  # Fallback to previous numpy API
29
30
  if not hasattr(numpy, 'trapezoid'):
@@ -143,7 +144,8 @@ class AlgebraicForm(object):
143
144
 
144
145
  self.A = None
145
146
  self.eig = None
146
- self.stieltjes = None
147
+ self._stieltjes = None
148
+ self.moments = None
147
149
  self.support = support
148
150
  self.delta = delta # Offset above real axis to apply Plemelj formula
149
151
 
@@ -152,12 +154,12 @@ class AlgebraicForm(object):
152
154
 
153
155
  if hasattr(A, 'stieltjes') and callable(getattr(A, 'stieltjes', None)):
154
156
  # This is one of the distribution objects, like MarchenkoPastur
155
- self.stieltjes = A.stieltjes
157
+ self._stieltjes = A.stieltjes
156
158
  self.n = 1
157
159
 
158
160
  elif callable(A):
159
161
  # This is a custom function
160
- self.stieltjes = A
162
+ self._stieltjes = A
161
163
  self.n = 1
162
164
 
163
165
  else:
@@ -176,8 +178,9 @@ class AlgebraicForm(object):
176
178
  self.eig = compute_eig(A)
177
179
 
178
180
  # Use empirical Stieltjes function
179
- self.stieltjes = lambda z: \
181
+ self._stieltjes = lambda z: \
180
182
  numpy.mean(1.0/(self.eig-z[:, numpy.newaxis]), axis=-1)
183
+ self.moments = MomentsESD(self.eig)
181
184
 
182
185
  # Support
183
186
  if support is None:
@@ -262,7 +265,7 @@ class AlgebraicForm(object):
262
265
  x_pad=x_pad)
263
266
 
264
267
  # Fitting (w_inf = None means adaptive weight selection)
265
- m1_fit = self.stieltjes(z_fit)
268
+ m1_fit = self._stieltjes(z_fit)
266
269
  a_coeffs, fit_metrics = fit_polynomial_relation(
267
270
  z_fit, m1_fit, s=deg_m, deg_z=deg_z, ridge_lambda=reg,
268
271
  triangular=triangular, normalize=normalize, mu=mu,
@@ -291,6 +294,7 @@ class AlgebraicForm(object):
291
294
  status['res_99_9'] = float(res_99_9)
292
295
  status['fit_metrics'] = fit_metrics
293
296
  self.status = status
297
+ self._stieltjes = StieltjesPoly(self.a_coeffs)
294
298
 
295
299
  if verbose:
296
300
  print(f'fit residual max : {res_max:>0.4e}')
@@ -384,8 +388,6 @@ class AlgebraicForm(object):
384
388
  >>> from freealg import FreeForm
385
389
  """
386
390
 
387
- pass
388
-
389
391
  if self.a_coeffs is None:
390
392
  raise RuntimeError('The model needs to be fit using the .fit() ' +
391
393
  'function.')
@@ -395,33 +397,7 @@ class AlgebraicForm(object):
395
397
  x = self._generate_grid(1.25)
396
398
 
397
399
  # Preallocate density to zero
398
- rho = numpy.zeros_like(x)
399
-
400
- for idx, x_i in enumerate(x):
401
- m_i = stieltjes_poly(x_i, self.a_coeffs)
402
- rho[idx] = m_i.imag
403
-
404
- rho = rho / numpy.pi
405
-
406
- # if self.method == 'jacobi':
407
- # rho[mask] = jacobi_density(x[mask], self.psi, self.support,
408
- # self.alpha, self.beta)
409
- # elif self.method == 'chebyshev':
410
- # rho[mask] = chebyshev_density(x[mask], self.psi, self.support)
411
- # else:
412
- # raise RuntimeError('"method" is invalid.')
413
- #
414
- # # Check density is unit mass
415
- # mass = numpy.trapezoid(rho, x)
416
- # if not numpy.isclose(mass, 1.0, atol=1e-2):
417
- # print(f'"rho" is not unit mass. mass: {mass:>0.3f}. Set ' +
418
- # r'"force=True".')
419
- #
420
- # # Check density is positive
421
- # min_rho = numpy.min(rho)
422
- # if min_rho < 0.0 - 1e-3:
423
- # print(f'"rho" is not positive. min_rho: {min_rho:>0.3f}. Set ' +
424
- # r'"force=True".')
400
+ rho = self._stieltjes(x).imag / numpy.pi
425
401
 
426
402
  if plot:
427
403
  plot_density(x, rho, eig=self.eig, support=self.broad_support,
@@ -433,7 +409,7 @@ class AlgebraicForm(object):
433
409
  # hilbert
434
410
  # =======
435
411
 
436
- def hilbert(self, x=None, rho=None, plot=False, latex=False, save=False):
412
+ def hilbert(self, x=None, plot=False, latex=False, save=False):
437
413
  """
438
414
  Compute Hilbert transform of the spectral density.
439
415
 
@@ -445,9 +421,6 @@ class AlgebraicForm(object):
445
421
  an interval slightly larger than the support interval of the
446
422
  spectral density is used.
447
423
 
448
- rho : numpy.array, default=None
449
- Density. If `None`, it will be computed.
450
-
451
424
  plot : bool, default=False
452
425
  If `True`, density is plotted.
453
426
 
@@ -479,49 +452,22 @@ class AlgebraicForm(object):
479
452
  >>> from freealg import FreeForm
480
453
  """
481
454
 
482
- pass
483
-
484
455
  if self.a_coeffs is None:
485
456
  raise RuntimeError('The model needs to be fit using the .fit() ' +
486
457
  'function.')
487
458
 
488
- # # Create x if not given
489
- # if x is None:
490
- # x = self._generate_grid(1.25)
491
- #
492
- # # if (numpy.min(x) > self.lam_m) or (numpy.max(x) < self.lam_p):
493
- # # raise ValueError('"x" does not encompass support interval.')
494
- #
495
- # # Preallocate density to zero
496
- # if rho is None:
497
- # rho = self.density(x)
498
- #
499
- # # mask of support [lam_m, lam_p]
500
- # mask = numpy.logical_and(x >= self.lam_m, x <= self.lam_p)
501
- # x_s = x[mask]
502
- # rho_s = rho[mask]
503
- #
504
- # # Form the matrix of integrands: rho_s / (t - x_i)
505
- # # Here, we have diff[i,j] = x[i] - x_s[j]
506
- # diff = x[:, None] - x_s[None, :]
507
- # D = rho_s[None, :] / diff
508
- #
509
- # # Principal-value: wherever t == x_i, then diff == 0, zero that entry
510
- # # (numpy.isclose handles floating-point exactly)
511
- # D[numpy.isclose(diff, 0.0)] = 0.0
512
- #
513
- # # Integrate each row over t using trapezoid rule on x_s
514
- # # Namely, hilb[i] = int rho_s(t)/(t - x[i]) dt
515
- # hilb = numpy.trapezoid(D, x_s, axis=1) / numpy.pi
516
- #
517
- # # We use negative sign convention
518
- # hilb = -hilb
519
- #
520
- # if plot:
521
- # plot_hilbert(x, hilb, support=self.support, latex=latex,
522
- # save=save)
523
- #
524
- # return hilb
459
+ # Create x if not given
460
+ if x is None:
461
+ x = self._generate_grid(1.25)
462
+
463
+ # Preallocate density to zero
464
+ hilb = -self._stieltjes(x).real / numpy.pi
465
+
466
+ if plot:
467
+ plot_hilbert(x, hilb, support=self.support, latex=latex,
468
+ save=save)
469
+
470
+ return hilb
525
471
 
526
472
  # =========
527
473
  # stieltjes
@@ -561,12 +507,9 @@ class AlgebraicForm(object):
561
507
  Returns
562
508
  -------
563
509
 
564
- m_p : numpy.ndarray
510
+ m : numpy.ndarray
565
511
  The Stieltjes transform on the principal branch.
566
512
 
567
- m_m : numpy.ndarray
568
- The Stieltjes transform continued to the secondary branch.
569
-
570
513
  See Also
571
514
  --------
572
515
 
@@ -581,36 +524,34 @@ class AlgebraicForm(object):
581
524
  >>> from freealg import FreeForm
582
525
  """
583
526
 
584
- pass
585
-
586
527
  if self.a_coeffs is None:
587
528
  raise RuntimeError('The model needs to be fit using the .fit() ' +
588
529
  'function.')
589
530
 
590
- # # Create x if not given
591
- # if x is None:
592
- # x = self._generate_grid(2.0, extend=2.0)
593
- #
594
- # # Create y if not given
595
- # if (plot is False) and (y is None):
596
- # # Do not use a Cartesian grid. Create a 1D array z slightly above
597
- # # the real line.
598
- # y = self.delta * 1j
599
- # z = x.astype(complex) + y # shape (Nx,)
600
- # else:
601
- # # Use a Cartesian grid
602
- # if y is None:
603
- # y = numpy.linspace(-1, 1, 400)
604
- # x_grid, y_grid = numpy.meshgrid(x.real, y.real)
605
- # z = x_grid + 1j * y_grid # shape (Ny, Nx)
606
- #
607
- # m1, m2 = self._eval_stieltjes(z, branches=True)
608
- #
609
- # if plot:
610
- # plot_stieltjes(x, y, m1, m2, self.support, latex=latex,
611
- # save=save)
612
- #
613
- # return m1, m2
531
+ # Create x if not given
532
+ if x is None:
533
+ x = self._generate_grid(2.0, extend=2.0)[::2]
534
+
535
+ # Create y if not given
536
+ if (plot is False) and (y is None):
537
+ # Do not use a Cartesian grid. Create a 1D array z slightly above
538
+ # the real line.
539
+ y = self.delta * 1j
540
+ z = x.astype(complex) + y # shape (Nx,)
541
+ else:
542
+ # Use a Cartesian grid
543
+ if y is None:
544
+ y = numpy.linspace(-1, 1, 200)
545
+ x_grid, y_grid = numpy.meshgrid(x.real, y.real)
546
+ z = x_grid + 1j * y_grid # shape (Ny, Nx)
547
+
548
+ m = self._stieltjes(z, progress=True)
549
+
550
+ if plot:
551
+ plot_stieltjes(x, y, m, m, self.broad_support, latex=latex,
552
+ save=save)
553
+
554
+ return m
614
555
 
615
556
  # ==============
616
557
  # eval stieltjes
@@ -669,12 +610,9 @@ class AlgebraicForm(object):
669
610
  # Decompression ratio equal to e^{t}.
670
611
  alpha = numpy.atleast_1d(size) / self.n
671
612
 
672
- def m_fn(z):
673
- return stieltjes_poly(z, self.a_coeffs)
674
-
675
613
  # Lower and upper bound on new support
676
- hilb_lb = (1.0 / m_fn(self.lam_m + self.delta * 1j).item()).real
677
- hilb_ub = (1.0 / m_fn(self.lam_p + self.delta * 1j).item()).real
614
+ hilb_lb = (1.0 / self._stieltjes(self.lam_m + self.delta * 1j).item()).real
615
+ hilb_ub = (1.0 / self._stieltjes(self.lam_p + self.delta * 1j).item()).real
678
616
  lb = self.lam_m - (numpy.max(alpha) - 1) * hilb_lb
679
617
  ub = self.lam_p - (numpy.max(alpha) - 1) * hilb_ub
680
618
 
@@ -695,9 +633,7 @@ class AlgebraicForm(object):
695
633
  z_query = x + 1j * self.delta
696
634
 
697
635
  # Initial condition at t=0 (physical branch)
698
- # w0_list = self.stieltjes(z_query)
699
- stieltjes = numpy.vectorize(m_fn)
700
- w0_list = stieltjes(z_query)
636
+ w0_list = self._stieltjes(z_query)
701
637
 
702
638
  # Times
703
639
  t = numpy.log(alpha)
@@ -727,9 +663,8 @@ class AlgebraicForm(object):
727
663
  for i in range(alpha.size):
728
664
  coeffs_i = decompress_coeffs(self.a_coeffs,
729
665
  numpy.log(alpha[i]))
730
- for j, x_j in enumerate(x):
731
- m_j = stieltjes_poly(x_j, coeffs_i)
732
- rho[i, j] = m_j.imag
666
+ stieltjes_i = StieltjesPoly(coeffs_i)
667
+ rho[i, :] = stieltjes_i(x).imag
733
668
 
734
669
  rho = rho / numpy.pi
735
670
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: freealg
3
- Version: 0.7.7
3
+ Version: 0.7.9
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,5 +1,5 @@
1
1
  freealg/__init__.py,sha256=SjcYb6HWmaclnnM-m1eC1honZRyfNBWYDYBx23kSdjo,833
2
- freealg/__version__.py,sha256=eOm8myGPtPLNpkuxL0xhVmstPQbwXv3Ok7FbH0re-TA,22
2
+ freealg/__version__.py,sha256=Plj3sh67uLqQt2X1uejJ0B3ggLdj4Fr1HPHcgyqtneU,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
@@ -8,9 +8,10 @@ freealg/_algebraic_form/_decompress.py,sha256=gGtixLOVxlMy5S-NsXgoA7lIrB7u7nUZIm
8
8
  freealg/_algebraic_form/_decompress2.py,sha256=Ng9w9xmGe9M-DApp35IeNeQlvszfzT4NZx5BQn0lQ3I,2459
9
9
  freealg/_algebraic_form/_discriminant.py,sha256=755pproom6-xThFARaH20m4GuBwwZS2rc0Y80Yg6NzY,5331
10
10
  freealg/_algebraic_form/_edge.py,sha256=7l9QyLJDxaEY4WB6MCUFtfEZSf04wyHwH7YPHFJXSbM,10690
11
- freealg/_algebraic_form/_homotopy.py,sha256=2oMcqJ2VJGzG7WKGM6FUS3923GT8Adtq_hLPEGgzqoU,3990
11
+ freealg/_algebraic_form/_homotopy.py,sha256=LXDd30MNy8BdKr5-_1TCcL1OW74EIhqWWr3JomVSh2M,9532
12
+ freealg/_algebraic_form/_moments.py,sha256=5lS0I7dFd15rE00FVi01ferOS77RHJdgLYf5pIzOiaw,12171
12
13
  freealg/_algebraic_form/_sheets_util.py,sha256=6OLzWQKu-gN8rxM2rbpbN8TjNZFmD8UJ-8t9kcZdkCo,4174
13
- freealg/_algebraic_form/algebraic_form.py,sha256=4WmxHlYytgvq1JXlWuoFQQF-ziLsTN2THzQrk3N94xE,32938
14
+ freealg/_algebraic_form/algebraic_form.py,sha256=us0cAzSy2CiESgJ1sse0Cw8n5m1f9VYaQilCHA1Aq3s,30620
14
15
  freealg/_free_form/__init__.py,sha256=5cnSX7kHci3wKx6-BEFhmVY_NjjmQAq1JjWPTEqETTg,611
15
16
  freealg/_free_form/_chebyshev.py,sha256=zkyVA8NLf7uUKlJdLz4ijd_SurdsqUgkA5nHGWSybaE,6916
16
17
  freealg/_free_form/_damp.py,sha256=k2vtBtWOxQBf4qXaWu_En81lQBXbEO4QbxxWpvuVhdE,1802
@@ -43,9 +44,9 @@ freealg/distributions/_wigner.py,sha256=epgx6ne6R_7to5j6-QsWIAVFJQFquWMmYgnZYMN4
43
44
  freealg/visualization/__init__.py,sha256=NLq_zwueF7ytZ8sl8zLPqm-AODxxXNvfMozHGmmklcE,435
44
45
  freealg/visualization/_glue_util.py,sha256=2oKnEYjUOS4OZfivmciVLauVr53kyHMwi6c2zRKilTQ,693
45
46
  freealg/visualization/_rgb_hsv.py,sha256=rEskxXxSlKKxIrHRslVkgxHtD010L3ge9YtcVsOPl8E,3650
46
- freealg-0.7.7.dist-info/licenses/AUTHORS.txt,sha256=0b67Nz4_JgIzUupHJTAZxu5QdSUM_HRM_X_w4xCb17o,30
47
- freealg-0.7.7.dist-info/licenses/LICENSE.txt,sha256=J-EEYEtxb3VVf_Bn1TYfWnpY5lMFIM15iLDDcnaDTPA,1443
48
- freealg-0.7.7.dist-info/METADATA,sha256=_TJ_P1TYStsudQUUv5jFi90Pmvi4blc3dFq27rHUqg8,5516
49
- freealg-0.7.7.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
50
- freealg-0.7.7.dist-info/top_level.txt,sha256=eR2wrgYwDdnnJ9Zf5PruPqe4kQav0GMvRsqct6y00Q8,8
51
- freealg-0.7.7.dist-info/RECORD,,
47
+ freealg-0.7.9.dist-info/licenses/AUTHORS.txt,sha256=0b67Nz4_JgIzUupHJTAZxu5QdSUM_HRM_X_w4xCb17o,30
48
+ freealg-0.7.9.dist-info/licenses/LICENSE.txt,sha256=J-EEYEtxb3VVf_Bn1TYfWnpY5lMFIM15iLDDcnaDTPA,1443
49
+ freealg-0.7.9.dist-info/METADATA,sha256=-Yx24ecrU6zjZ5XgN1HjMQPpGrU9PBrbaXq3idRoKC4,5516
50
+ freealg-0.7.9.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
51
+ freealg-0.7.9.dist-info/top_level.txt,sha256=eR2wrgYwDdnnJ9Zf5PruPqe4kQav0GMvRsqct6y00Q8,8
52
+ freealg-0.7.9.dist-info/RECORD,,