freealg 0.7.12__py3-none-any.whl → 0.7.15__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. freealg/__version__.py +1 -1
  2. freealg/_algebraic_form/_cusp.py +357 -0
  3. freealg/_algebraic_form/_cusp_wrap.py +268 -0
  4. freealg/_algebraic_form/_decompress2.py +2 -0
  5. freealg/_algebraic_form/_decompress4.py +739 -0
  6. freealg/_algebraic_form/_decompress5.py +738 -0
  7. freealg/_algebraic_form/_decompress6.py +492 -0
  8. freealg/_algebraic_form/_decompress7.py +355 -0
  9. freealg/_algebraic_form/_decompress8.py +369 -0
  10. freealg/_algebraic_form/_decompress9.py +363 -0
  11. freealg/_algebraic_form/_decompress_new.py +431 -0
  12. freealg/_algebraic_form/_decompress_new_2.py +1631 -0
  13. freealg/_algebraic_form/_decompress_util.py +172 -0
  14. freealg/_algebraic_form/_homotopy2.py +289 -0
  15. freealg/_algebraic_form/_homotopy3.py +215 -0
  16. freealg/_algebraic_form/_homotopy4.py +320 -0
  17. freealg/_algebraic_form/_homotopy5.py +185 -0
  18. freealg/_algebraic_form/_moments.py +0 -1
  19. freealg/_algebraic_form/_support.py +132 -177
  20. freealg/_algebraic_form/algebraic_form.py +21 -2
  21. freealg/distributions/_compound_poisson.py +481 -0
  22. freealg/distributions/_deformed_marchenko_pastur.py +6 -7
  23. {freealg-0.7.12.dist-info → freealg-0.7.15.dist-info}/METADATA +1 -1
  24. {freealg-0.7.12.dist-info → freealg-0.7.15.dist-info}/RECORD +28 -12
  25. {freealg-0.7.12.dist-info → freealg-0.7.15.dist-info}/WHEEL +0 -0
  26. {freealg-0.7.12.dist-info → freealg-0.7.15.dist-info}/licenses/AUTHORS.txt +0 -0
  27. {freealg-0.7.12.dist-info → freealg-0.7.15.dist-info}/licenses/LICENSE.txt +0 -0
  28. {freealg-0.7.12.dist-info → freealg-0.7.15.dist-info}/top_level.txt +0 -0
@@ -13,16 +13,16 @@
13
13
 
14
14
  import numpy
15
15
  import numpy.polynomial.polynomial as poly
16
+ from ._homotopy5 import StieltjesPoly
16
17
 
17
18
  __all__ = ['compute_support']
18
19
 
19
20
 
20
- # ======================
21
- # poly coeffs in m and z
22
- # ======================
21
+ # =====================
22
+ # poly coeffs in m at z
23
+ # =====================
23
24
 
24
25
  def _poly_coeffs_in_m_at_z(a_coeffs, z):
25
-
26
26
  s = a_coeffs.shape[1] - 1
27
27
  a = numpy.empty(s + 1, dtype=numpy.complex128)
28
28
  for j in range(s + 1):
@@ -30,48 +30,11 @@ def _poly_coeffs_in_m_at_z(a_coeffs, z):
30
30
  return a
31
31
 
32
32
 
33
- # ===============
34
- # roots poly in m
35
- # ===============
36
-
37
- def _roots_poly_in_m(c_asc, tol=0.0):
38
-
39
- c = numpy.asarray(c_asc, dtype=numpy.complex128).ravel()
40
- if c.size <= 1:
41
- return numpy.array([], dtype=numpy.complex128)
42
-
43
- k = c.size - 1
44
- while k > 0 and abs(c[k]) <= tol:
45
- k -= 1
46
- c = c[:k + 1]
47
- if c.size <= 1:
48
- return numpy.array([], dtype=numpy.complex128)
49
-
50
- return numpy.roots(c[::-1])
51
-
52
-
53
- # ================
54
- # dPdm coeffs at z
55
- # ================
56
-
57
- def _dPdm_coeffs_at_z(a_coeffs, z):
58
-
59
- a = _poly_coeffs_in_m_at_z(a_coeffs, z)
60
- s = a.size - 1
61
- if s <= 0:
62
- return numpy.array([0.0 + 0.0j], dtype=numpy.complex128)
63
- d = numpy.empty(s, dtype=numpy.complex128)
64
- for j in range(1, s + 1):
65
- d[j - 1] = j * a[j]
66
- return d
67
-
68
-
69
33
  # ==============
70
34
  # P and partials
71
35
  # ==============
72
36
 
73
37
  def _P_and_partials(a_coeffs, z, m):
74
-
75
38
  s = a_coeffs.shape[1] - 1
76
39
 
77
40
  a = numpy.empty(s + 1, dtype=numpy.complex128)
@@ -97,7 +60,7 @@ def _P_and_partials(a_coeffs, z, m):
97
60
  for j in range(2, s + 1):
98
61
  Pmm += j * (j - 1) * a[j] * (m ** (j - 2))
99
62
 
100
- return P, Pz, Pm, Pzm, Pmm, a
63
+ return P, Pz, Pm, Pzm, Pmm
101
64
 
102
65
 
103
66
  # ===========
@@ -105,13 +68,12 @@ def _P_and_partials(a_coeffs, z, m):
105
68
  # ===========
106
69
 
107
70
  def _newton_edge(a_coeffs, x0, m0, tol=1e-12, max_iter=50):
108
-
109
71
  x = float(x0)
110
72
  m = float(m0)
111
73
 
112
74
  for _ in range(max_iter):
113
75
  z = x + 0.0j
114
- P, Pz, Pm, Pzm, Pmm, _ = _P_and_partials(a_coeffs, z, m)
76
+ P, Pz, Pm, Pzm, Pmm = _P_and_partials(a_coeffs, z, m)
115
77
 
116
78
  f0 = float(numpy.real(P))
117
79
  f1 = float(numpy.real(Pm))
@@ -142,10 +104,8 @@ def _newton_edge(a_coeffs, x0, m0, tol=1e-12, max_iter=50):
142
104
  # =============
143
105
 
144
106
  def _cluster_edges(edges, x_tol):
145
-
146
107
  if len(edges) == 0:
147
108
  return numpy.array([], dtype=float)
148
-
149
109
  edges = numpy.array(sorted(edges), dtype=float)
150
110
  out = [edges[0]]
151
111
  for e in edges[1:]:
@@ -154,156 +114,151 @@ def _cluster_edges(edges, x_tol):
154
114
  return numpy.array(out, dtype=float)
155
115
 
156
116
 
157
- # =======================
158
- # pick physical root at z
159
- # =======================
160
-
161
- def _pick_physical_root_at_z(a_coeffs, z, im_sign=+1):
162
-
163
- a = _poly_coeffs_in_m_at_z(a_coeffs, z)
164
- r = _roots_poly_in_m(a)
165
- if r.size == 0:
166
- return numpy.nan + 1j * numpy.nan
167
-
168
- w_ref = -1.0 / z
169
- idx = int(numpy.argmin(numpy.abs(r - w_ref)))
170
- w = r[idx]
117
+ # ===========
118
+ # bisect edge
119
+ # ===========
171
120
 
172
- # optional strictness: if it violates Herglotz, declare failure
173
- if not numpy.isfinite(w.real) or not numpy.isfinite(w.imag):
174
- return w
175
- if (im_sign * w.imag) <= 0.0:
176
- return w
121
+ def _bisect_edge(stieltjes_poly, x_lo, x_hi, eta, im_thr, max_iter=60):
122
+ z_lo = x_lo + 1j * eta
123
+ z_hi = x_hi + 1j * eta
124
+ f_lo = float(numpy.imag(stieltjes_poly.evaluate_scalar(z_lo)) - im_thr)
125
+ f_hi = float(numpy.imag(stieltjes_poly.evaluate_scalar(z_hi)) - im_thr)
126
+
127
+ if (not numpy.isfinite(f_lo)) or (not numpy.isfinite(f_hi)):
128
+ return 0.5 * (x_lo + x_hi)
129
+ if f_lo == 0.0:
130
+ return float(x_lo)
131
+ if f_hi == 0.0:
132
+ return float(x_hi)
133
+ if f_lo * f_hi > 0.0:
134
+ return 0.5 * (x_lo + x_hi)
135
+
136
+ a = float(x_lo)
137
+ b = float(x_hi)
138
+ fa = f_lo
139
+ # fb = f_hi
177
140
 
178
- return w
141
+ for _ in range(max_iter):
142
+ c = 0.5 * (a + b)
143
+ z_c = c + 1j * eta
144
+ fc = float(numpy.imag(stieltjes_poly.evaluate_scalar(z_c)) - im_thr)
145
+ if not numpy.isfinite(fc):
146
+ return c
147
+ if fc == 0.0 or (b - a) < 1e-14 * (1.0 + abs(c)):
148
+ return c
149
+ if fa * fc <= 0.0:
150
+ b = c
151
+ # fb = fc
152
+ else:
153
+ a = c
154
+ fa = fc
155
+
156
+ return 0.5 * (a + b)
179
157
 
180
158
 
181
159
  # ===============
182
160
  # compute support
183
161
  # ===============
184
162
 
185
- def compute_support(a_coeffs,
186
- x_min,
187
- x_max,
188
- n_scan=4000,
189
- y_eps=1e-3,
190
- im_sign=+1,
191
- root_tol=0.0,
192
- edge_rel_tol=1e-6,
193
- edge_x_cluster_tol=1e-3,
194
- newton_tol=1e-12):
195
- """
196
- Fast support from fitted polynomial using branch-point system P=0, Pm=0.
197
-
198
- Returns
199
- -------
200
- support : list of (a,b)
201
- info : dict (edges, rel_res_curve, etc.)
202
- """
203
-
204
- a_coeffs = numpy.asarray(a_coeffs)
205
- x_grid = numpy.linspace(float(x_min), float(x_max), int(n_scan))
206
-
207
- # For each x, find best real critical point m (Pm=0) minimizing rel
208
- # residual.
209
- rel = numpy.full(x_grid.size, numpy.inf, dtype=float)
210
- m_star = numpy.full(x_grid.size, numpy.nan, dtype=float)
211
-
212
- for i, x in enumerate(x_grid):
213
- z = x + 0.0j
214
- dcoef = _dPdm_coeffs_at_z(a_coeffs, z)
215
- mr = _roots_poly_in_m(dcoef, tol=root_tol)
216
-
217
- best = numpy.inf
218
- best_m = numpy.nan
219
-
220
- for w in mr:
221
- # accept nearly-real roots; numerical roots can have small imag
222
- # part
223
- if abs(w.imag) > 1e-6 * (1.0 + abs(w.real)):
224
- continue
225
- m = float(w.real)
226
- P, _, _, _, _, a = _P_and_partials(a_coeffs, z, m)
227
-
228
- denom = 1.0
229
- am = 1.0
230
- for j in range(a.size):
231
- denom += abs(a[j]) * abs(am)
232
- am *= m
233
-
234
- r = abs(numpy.real(P)) / denom
235
- if numpy.isfinite(r) and r < best:
236
- best = float(r)
237
- best_m = m
238
-
239
- rel[i] = best
240
- m_star[i] = best_m
241
-
242
- # Pick candidate edges as local minima of rel(x), below an automatic scale.
243
- rel_f = rel[numpy.isfinite(rel)]
244
- if rel_f.size == 0:
245
- return [], {"edges": numpy.array([], dtype=float), "n_edges": 0}
246
-
247
- med = float(numpy.median(rel_f))
248
- min_rel = float(numpy.min(rel_f))
249
-
250
- # accept local minima up to a factor above the best one, but never abov
251
- # background scale
252
- thr = min(0.1 * med, max(float(edge_rel_tol), 1e4 * min_rel))
253
-
254
- edges0 = []
255
- seeds = []
256
-
257
- for i in range(1, x_grid.size - 1):
258
- if not numpy.isfinite(rel[i]):
259
- continue
260
- if rel[i] <= rel[i - 1] and rel[i] <= rel[i + 1] and rel[i] < thr and \
261
- numpy.isfinite(m_star[i]):
262
- edges0.append(float(x_grid[i]))
263
- seeds.append((float(x_grid[i]), float(m_star[i])))
163
+ def compute_support(a_coeffs, x_min, x_max, n_scan=4000, **kwargs):
264
164
 
265
- # Refine each seed by 2D Newton (x,m)
266
- edges = []
267
- for x0, m0 in seeds:
268
- xe, me, ok = _newton_edge(a_coeffs, x0, m0, tol=newton_tol)
269
- if ok and numpy.isfinite(xe) and numpy.isfinite(me):
270
- edges.append(float(xe))
165
+ a_coeffs = numpy.asarray(a_coeffs, dtype=numpy.complex128)
271
166
 
272
- edges = _cluster_edges(edges, edge_x_cluster_tol)
273
- edges.sort()
167
+ x_min = float(x_min)
168
+ x_max = float(x_max)
169
+ n_scan = int(n_scan)
274
170
 
275
- # Build support by testing midpoints between consecutive real edges
276
- support = []
277
- m_im_tol = 1e-10
171
+ scale = max(1.0, abs(x_max - x_min), abs(x_min), abs(x_max))
172
+ eta = kwargs.get('eta', None)
173
+ if eta is None:
174
+ eta = 1e-6 * scale
175
+ eta = float(eta)
176
+
177
+ vopt = {
178
+ 'lam_space': 1.0,
179
+ 'lam_asym': 1.0,
180
+ 'lam_tiny_im': 200.0,
181
+ 'tiny_im': 0.5 * eta,
182
+ 'tol_im': 1e-14,
183
+ }
184
+ vopt.update(kwargs.get('viterbi_opt', {}) or {})
185
+ stieltjes = StieltjesPoly(a_coeffs, viterbi_opt=vopt)
186
+
187
+ x_grid = numpy.linspace(x_min, x_max, n_scan)
188
+ z_grid = x_grid + 1j * eta
189
+ m_grid = stieltjes(z_grid)
190
+ im_grid = numpy.imag(m_grid)
191
+
192
+ max_im = float(numpy.nanmax(im_grid)) \
193
+ if numpy.any(numpy.isfinite(im_grid)) else 0.0
278
194
 
279
- for i in range(edges.size - 1):
280
- a = float(edges[i])
281
- b = float(edges[i + 1])
282
- if b <= a:
195
+ thr_rel = float(kwargs.get('thr_rel', 1e-3))
196
+ thr_abs = kwargs.get('thr_abs', None)
197
+
198
+ im_thr = thr_rel * max_im
199
+ im_thr = max(im_thr, 10.0 * eta)
200
+ if thr_abs is not None:
201
+ im_thr = max(im_thr, float(thr_abs))
202
+
203
+ mask = numpy.isfinite(im_grid) & (im_grid > im_thr)
204
+
205
+ runs = []
206
+ i = 0
207
+ while i < mask.size:
208
+ if not mask[i]:
209
+ i += 1
210
+ continue
211
+ j = i
212
+ while j + 1 < mask.size and mask[j + 1]:
213
+ j += 1
214
+ runs.append((i, j))
215
+ i = j + 1
216
+
217
+ edges = []
218
+ for i0, i1 in runs:
219
+ if i0 == 0 or i1 == mask.size - 1:
283
220
  continue
284
221
 
285
- xmid = 0.5 * (a + b)
222
+ xL = _bisect_edge(stieltjes, x_grid[i0 - 1], x_grid[i0], eta, im_thr)
223
+ xR = _bisect_edge(stieltjes, x_grid[i1], x_grid[i1 + 1], eta, im_thr)
286
224
 
287
- # roots of P(xmid, m) with real coefficients
288
- a_m = _poly_coeffs_in_m_at_z(a_coeffs, xmid + 0.0j)
289
- r = _roots_poly_in_m(a_m, tol=root_tol)
225
+ edges.append(float(xL))
226
+ edges.append(float(xR))
290
227
 
291
- # interval is support iff there exists a non-real root (complex pair)
292
- if numpy.any(numpy.abs(numpy.imag(r)) > m_im_tol):
228
+ edge_x_cluster_tol = float(kwargs.get('edge_x_cluster_tol', 1e-8 * scale))
229
+ edges = _cluster_edges(edges, edge_x_cluster_tol)
230
+
231
+ refine = bool(kwargs.get('refine', True))
232
+ if refine and edges.size > 0:
233
+ newton_tol = float(kwargs.get('newton_tol', 1e-12))
234
+ edges_ref = []
235
+ for x0 in edges:
236
+ m0 = float(numpy.real(stieltjes.evaluate_scalar(x0 + 1j * eta)))
237
+ xe, _, ok = _newton_edge(a_coeffs, x0, m0, tol=newton_tol)
238
+ edges_ref.append(float(xe)
239
+ if ok and numpy.isfinite(xe) else float(x0))
240
+ edges = _cluster_edges(edges_ref, edge_x_cluster_tol)
241
+
242
+ edges.sort()
243
+ support = []
244
+ for k in range(0, edges.size - 1, 2):
245
+ a = float(edges[k])
246
+ b = float(edges[k + 1])
247
+ if b > a:
293
248
  support.append((a, b))
294
249
 
295
250
  info = {
296
- "edges": edges,
297
- "n_edges": int(edges.size),
298
- "support": support,
299
- "n_support": int(len(support)),
300
- "x_grid": x_grid,
301
- "rel": rel,
302
- "thr": float(thr),
303
- "x_min": float(x_min),
304
- "x_max": float(x_max),
305
- "n_scan": int(n_scan),
306
- "y_eps": float(y_eps),
251
+ 'x_grid': x_grid,
252
+ 'eta': eta,
253
+ 'm_grid': m_grid,
254
+ 'im_grid': im_grid,
255
+ 'im_thr': float(im_thr),
256
+ 'edges': edges,
257
+ 'support': support,
258
+ 'x_min': x_min,
259
+ 'x_max': x_max,
260
+ 'n_scan': n_scan,
261
+ 'scale': scale,
307
262
  }
308
263
 
309
264
  return support, info
@@ -323,7 +323,26 @@ class AlgebraicForm(object):
323
323
  status['res_99_9'] = float(res_99_9)
324
324
  status['fit_metrics'] = fit_metrics
325
325
  self.status = status
326
- self._stieltjes = StieltjesPoly(self.a_coeffs) # NOTE overwrite init
326
+
327
+ # -----------------
328
+
329
+ # Inflate a bit to make sure all points are searched
330
+ # x_min, x_max = self._inflate_broad_support(inflate=0.2)
331
+ # scale = float(max(1.0, abs(x_max - x_min), abs(x_min), abs(x_max)))
332
+ # eta = 1e-6 * scale
333
+ #
334
+ # vopt = {
335
+ # 'lam_space': 1.0,
336
+ # 'lam_asym': 1.0,
337
+ # 'lam_tiny_im': 200.0,
338
+ # 'tiny_im': 0.5 * eta,
339
+ # 'tol_im': 1e-14,
340
+ # }
341
+
342
+ # NOTE overwrite init
343
+ self._stieltjes = StieltjesPoly(self.a_coeffs)
344
+ # self._stieltjes = StieltjesPoly(self.a_coeffs, viterbi_opt=vopt)
345
+
327
346
  self._moments_base = AlgebraicStieltjesMoments(a_coeffs)
328
347
  self.moments = Moments(self._moments_base)
329
348
 
@@ -760,7 +779,7 @@ class AlgebraicForm(object):
760
779
 
761
780
  def mom(k):
762
781
  return self.moments(k, t_i)
763
-
782
+
764
783
  stieltjes_i = StieltjesPoly(coeffs_i, mom)
765
784
  rho[i, :] = stieltjes_i(x).imag
766
785