freealg 0.7.11__py3-none-any.whl → 0.7.14__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. freealg/__init__.py +2 -2
  2. freealg/__version__.py +1 -1
  3. freealg/_algebraic_form/__init__.py +2 -1
  4. freealg/_algebraic_form/_constraints.py +53 -12
  5. freealg/_algebraic_form/_cusp.py +357 -0
  6. freealg/_algebraic_form/_cusp_wrap.py +268 -0
  7. freealg/_algebraic_form/_decompress.py +330 -381
  8. freealg/_algebraic_form/_decompress2.py +120 -0
  9. freealg/_algebraic_form/_decompress4.py +739 -0
  10. freealg/_algebraic_form/_decompress5.py +738 -0
  11. freealg/_algebraic_form/_decompress6.py +492 -0
  12. freealg/_algebraic_form/_decompress7.py +355 -0
  13. freealg/_algebraic_form/_decompress8.py +369 -0
  14. freealg/_algebraic_form/_decompress9.py +363 -0
  15. freealg/_algebraic_form/_decompress_new.py +431 -0
  16. freealg/_algebraic_form/_decompress_new_2.py +1631 -0
  17. freealg/_algebraic_form/_decompress_util.py +172 -0
  18. freealg/_algebraic_form/_edge.py +46 -68
  19. freealg/_algebraic_form/_homotopy.py +62 -30
  20. freealg/_algebraic_form/_homotopy2.py +289 -0
  21. freealg/_algebraic_form/_homotopy3.py +215 -0
  22. freealg/_algebraic_form/_homotopy4.py +320 -0
  23. freealg/_algebraic_form/_homotopy5.py +185 -0
  24. freealg/_algebraic_form/_moments.py +43 -57
  25. freealg/_algebraic_form/_support.py +132 -177
  26. freealg/_algebraic_form/algebraic_form.py +163 -30
  27. freealg/distributions/__init__.py +3 -1
  28. freealg/distributions/_compound_poisson.py +464 -0
  29. freealg/distributions/_deformed_marchenko_pastur.py +51 -0
  30. freealg/distributions/_deformed_wigner.py +44 -0
  31. {freealg-0.7.11.dist-info → freealg-0.7.14.dist-info}/METADATA +2 -1
  32. {freealg-0.7.11.dist-info → freealg-0.7.14.dist-info}/RECORD +36 -20
  33. {freealg-0.7.11.dist-info → freealg-0.7.14.dist-info}/WHEEL +1 -1
  34. {freealg-0.7.11.dist-info → freealg-0.7.14.dist-info}/licenses/AUTHORS.txt +0 -0
  35. {freealg-0.7.11.dist-info → freealg-0.7.14.dist-info}/licenses/LICENSE.txt +0 -0
  36. {freealg-0.7.11.dist-info → freealg-0.7.14.dist-info}/top_level.txt +0 -0
@@ -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
@@ -18,12 +18,35 @@ from ._continuation_algebraic import sample_z_joukowski, \
18
18
  filter_z_away_from_cuts, fit_polynomial_relation, \
19
19
  sanity_check_stieltjes_branch, eval_P
20
20
  from ._edge import evolve_edges, merge_edges
21
- from ._decompress import build_time_grid, decompress_newton
22
- from ._decompress2 import decompress_coeffs
23
- from ._homotopy import StieltjesPoly
21
+ from ._cusp_wrap import cusp_wrap
22
+
23
+ # Decompress with Newton
24
+ # from ._decompress import build_time_grid, decompress_newton
25
+ from ._decompress_util import build_time_grid
26
+ # from ._decompress4 import decompress_newton # WORKS (mass issue)
27
+ # from ._decompress5 import build_time_grid, decompress_newton
28
+ # from ._decompress6 import build_time_grid, decompress_newton
29
+ # from ._decompress4_2 import build_time_grid, decompress_newton
30
+ # from ._decompress_new_2 import build_time_grid, decompress_newton
31
+ # from ._decompress_new import build_time_grid, decompress_newton
32
+ # from ._decompress6 import decompress_newton
33
+ # from ._decompress7 import decompress_newton
34
+ # from ._decompress8 import decompress_newton
35
+ from ._decompress9 import decompress_newton # With Predictor/Corrector
36
+
37
+ # Decompress with coefficients
38
+ from ._decompress2 import decompress_coeffs, plot_candidates
39
+
40
+ # Homotopy
41
+ # from ._homotopy import StieltjesPoly
42
+ # from ._homotopy2 import StieltjesPoly
43
+ # from ._homotopy3 import StieltjesPoly # Viterbi
44
+ # from ._homotopy4 import StieltjesPoly
45
+ from ._homotopy5 import StieltjesPoly
46
+
24
47
  from ._branch_points import compute_branch_points
25
48
  from ._support import compute_support
26
- from ._moments import MomentsESD
49
+ from ._moments import Moments, AlgebraicStieltjesMoments
27
50
  from .._free_form._support import supp
28
51
  from .._free_form._plot_util import plot_density, plot_hilbert, plot_stieltjes
29
52
 
@@ -180,7 +203,7 @@ class AlgebraicForm(object):
180
203
  # Use empirical Stieltjes function
181
204
  self._stieltjes = lambda z: \
182
205
  numpy.mean(1.0/(self.eig-z[:, numpy.newaxis]), axis=-1)
183
- self._moments = MomentsESD(self.eig) # NOTE (never used)
206
+ self._moments = Moments(self.eig) # NOTE (never used)
184
207
 
185
208
  # broad support
186
209
  if self.support is None:
@@ -189,10 +212,10 @@ class AlgebraicForm(object):
189
212
 
190
213
  # Detect support
191
214
  self.lam_m, self.lam_p = supp(self.eig, **kwargs)
192
- self.broad_support = (self.lam_m, self.lam_p)
215
+ self.broad_support = (float(self.lam_m), float(self.lam_p))
193
216
  else:
194
- self.lam_m = min([s[0] for s in self.support])
195
- self.lam_p = max([s[1] for s in self.support])
217
+ self.lam_m = float(min([s[0] for s in self.support]))
218
+ self.lam_p = float(max([s[1] for s in self.support]))
196
219
  self.broad_support = (self.lam_m, self.lam_p)
197
220
 
198
221
  # Initialize
@@ -257,7 +280,7 @@ class AlgebraicForm(object):
257
280
  if self.support is not None:
258
281
  possible_support = self.support
259
282
  else:
260
- possible_support = self.broad_support
283
+ possible_support = [self.broad_support]
261
284
 
262
285
  for sup in possible_support:
263
286
  a, b = sup
@@ -300,7 +323,28 @@ class AlgebraicForm(object):
300
323
  status['res_99_9'] = float(res_99_9)
301
324
  status['fit_metrics'] = fit_metrics
302
325
  self.status = status
303
- 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
+
346
+ self._moments_base = AlgebraicStieltjesMoments(a_coeffs)
347
+ self.moments = Moments(self._moments_base)
304
348
 
305
349
  if verbose:
306
350
  print(f'fit residual max : {res_max:>0.4e}')
@@ -349,7 +393,7 @@ class AlgebraicForm(object):
349
393
  # estimate support
350
394
  # ================
351
395
 
352
- def estimate_support(self, a_coeffs=None, n_scan=4000):
396
+ def estimate_support(self, a_coeffs=None, scan_range=None, n_scan=4000):
353
397
  """
354
398
  """
355
399
 
@@ -360,7 +404,10 @@ class AlgebraicForm(object):
360
404
  a_coeffs = self.a_coeffs
361
405
 
362
406
  # Inflate a bit to make sure all points are searched
363
- x_min, x_max = self._inflate_broad_support(inflate=0.2)
407
+ if scan_range is not None:
408
+ x_min, x_max = scan_range
409
+ else:
410
+ x_min, x_max = self._inflate_broad_support(inflate=0.2)
364
411
 
365
412
  est_support, info = compute_support(a_coeffs, x_min=x_min, x_max=x_max,
366
413
  n_scan=n_scan)
@@ -371,7 +418,7 @@ class AlgebraicForm(object):
371
418
  # estimate branch points
372
419
  # ======================
373
420
 
374
- def estimate_branch_points(self):
421
+ def estimate_branch_points(self, tol=1e-15, real_tol=None):
375
422
  """
376
423
  Compute global branch points and zeros of leading a_j
377
424
  """
@@ -380,7 +427,7 @@ class AlgebraicForm(object):
380
427
  raise RuntimeError('Call "fit" first.')
381
428
 
382
429
  bp, leading_zeros, info = compute_branch_points(
383
- self.a_coeffs, tol=1e-12, real_tol=None)
430
+ self.a_coeffs, tol=tol, real_tol=real_tol)
384
431
 
385
432
  return bp, leading_zeros, info
386
433
 
@@ -460,7 +507,8 @@ class AlgebraicForm(object):
460
507
  x = self._generate_grid(1.25)
461
508
 
462
509
  # Preallocate density to zero
463
- rho = self._stieltjes(x).imag / numpy.pi
510
+ z = x.astype(complex) + 1j * self.delta
511
+ rho = self._stieltjes(z).imag / numpy.pi
464
512
 
465
513
  if plot:
466
514
  plot_density(x, rho, eig=self.eig, support=self.broad_support,
@@ -654,10 +702,10 @@ class AlgebraicForm(object):
654
702
  # ==========
655
703
 
656
704
  def decompress(self, size, x=None, method='one', plot=False, latex=False,
657
- save=False, verbose=False, newton_opt={
658
- 'min_n_times': 10, 'max_iter': 50, 'tol': 1e-12,
659
- 'armijo': 1e-4, 'min_lam': 1e-6, 'w_min': 1e-14,
660
- 'sweep': True}):
705
+ save=False, verbose=False, min_n_times=10,
706
+ newton_opt={'max_iter': 50, 'tol': 1e-12, 'armijo': 1e-4,
707
+ 'min_lam': 1e-6, 'w_min': 1e-14,
708
+ 'sweep': True}):
661
709
  """
662
710
  Free decompression of spectral density.
663
711
  """
@@ -704,7 +752,7 @@ class AlgebraicForm(object):
704
752
  # Ensure there are at least min_n_times time t, including requested
705
753
  # times, and especially time t = 0
706
754
  t_all, idx_req = build_time_grid(
707
- size, self.n, min_n_time=newton_opt.get("min_n_time", 0))
755
+ size, self.n, min_n_times=min_n_times)
708
756
 
709
757
  # Evolve
710
758
  W, ok = decompress_newton(
@@ -726,9 +774,13 @@ class AlgebraicForm(object):
726
774
 
727
775
  # Decompress to each alpha
728
776
  for i in range(alpha.size):
729
- coeffs_i = decompress_coeffs(self.a_coeffs,
730
- numpy.log(alpha[i]))
731
- stieltjes_i = StieltjesPoly(coeffs_i)
777
+ t_i = numpy.log(alpha[i])
778
+ coeffs_i = decompress_coeffs(self.a_coeffs, t_i)
779
+
780
+ def mom(k):
781
+ return self.moments(k, t_i)
782
+
783
+ stieltjes_i = StieltjesPoly(coeffs_i, mom)
732
784
  rho[i, :] = stieltjes_i(x).imag
733
785
 
734
786
  rho = rho / numpy.pi
@@ -751,6 +803,49 @@ class AlgebraicForm(object):
751
803
 
752
804
  return rho, x
753
805
 
806
+ # ==========
807
+ # candidates
808
+ # ==========
809
+
810
+ def candidates(self, size, x=None, verbose=False):
811
+
812
+ # Check size argument
813
+ if numpy.isscalar(size):
814
+ size = int(size)
815
+ else:
816
+ # Check monotonic increment (either all increasing or decreasing)
817
+ diff = numpy.diff(size)
818
+ if not (numpy.all(diff >= 0) or numpy.all(diff <= 0)):
819
+ raise ValueError('"size" increment should be monotonic.')
820
+
821
+ # Decompression ratio equal to e^{t}.
822
+ alpha = numpy.atleast_1d(size) / self.n
823
+
824
+ # Lower and upper bound on new support
825
+ hilb_lb = \
826
+ (1.0 / self._stieltjes(self.lam_m + self.delta * 1j).item()).real
827
+ hilb_ub = \
828
+ (1.0 / self._stieltjes(self.lam_p + self.delta * 1j).item()).real
829
+ lb = self.lam_m - (numpy.max(alpha) - 1) * hilb_lb
830
+ ub = self.lam_p - (numpy.max(alpha) - 1) * hilb_ub
831
+
832
+ # Create x if not given
833
+ if x is None:
834
+ radius = 0.5 * (ub - lb)
835
+ center = 0.5 * (ub + lb)
836
+ scale = 1.25
837
+ x_min = numpy.floor(center - radius * scale)
838
+ x_max = numpy.ceil(center + radius * scale)
839
+ x = numpy.linspace(x_min, x_max, 2000)
840
+ else:
841
+ x = numpy.asarray(x)
842
+
843
+ for i in range(alpha.size):
844
+ t_i = numpy.log(alpha[i])
845
+ coeffs_i = decompress_coeffs(self.a_coeffs, t_i)
846
+ plot_candidates(coeffs_i, x, size=int(alpha[i]*self.n),
847
+ verbose=verbose)
848
+
754
849
  # ====
755
850
  # edge
756
851
  # ====
@@ -759,6 +854,9 @@ class AlgebraicForm(object):
759
854
  verbose=False):
760
855
  """
761
856
  Evolves spectral edges.
857
+
858
+ Fix: if t is a scalar or length-1 array, we prepend t=0 internally so
859
+ evolve_edges actually advances from the initialization at t=0.
762
860
  """
763
861
 
764
862
  if self.support is not None:
@@ -768,18 +866,53 @@ class AlgebraicForm(object):
768
866
  else:
769
867
  raise RuntimeError('Call "fit" first.')
770
868
 
771
- edges, ok_edges = evolve_edges(t, self.a_coeffs,
772
- support=known_support, eta=eta,
773
- dt_max=dt_max, max_iter=max_iter,
774
- tol=tol)
869
+ t = numpy.asarray(t, dtype=float).ravel()
870
+
871
+ if t.size == 1:
872
+ t1 = float(t[0])
873
+ if t1 == 0.0:
874
+ t_grid = numpy.array([0.0], dtype=float)
875
+ complex_edges, ok_edges = evolve_edges(
876
+ t_grid, self.a_coeffs, support=known_support, eta=eta,
877
+ dt_max=dt_max, max_iter=max_iter, tol=tol
878
+ )
879
+ else:
880
+ # prepend 0 and drop it after evolution
881
+ t_grid = numpy.array([0.0, t1], dtype=float)
882
+ complex_edges2, ok_edges2 = evolve_edges(
883
+ t_grid, self.a_coeffs, support=known_support, eta=eta,
884
+ dt_max=dt_max, max_iter=max_iter, tol=tol
885
+ )
886
+ complex_edges = complex_edges2[-1:, :]
887
+ ok_edges = ok_edges2[-1:, :]
888
+ else:
889
+ # For vector t, require it starts at 0 for correct initialization
890
+ # (you can relax this if you want by prepending 0 similarly).
891
+ complex_edges, ok_edges = evolve_edges(
892
+ t, self.a_coeffs, support=known_support, eta=eta,
893
+ dt_max=dt_max, max_iter=max_iter, tol=tol
894
+ )
895
+
896
+ real_edges = complex_edges.real
775
897
 
776
- # Remove spurious edges, where two edge cross and are no longer valid.
777
- edges2, active_k = merge_edges(edges, tol=1e-4)
898
+ # Remove spurious edges / merges for plotting
899
+ real_merged_edges, active_k = merge_edges(real_edges, tol=1e-4)
778
900
 
779
901
  if verbose:
780
902
  print("edge success rate:", ok_edges.mean())
781
903
 
782
- return edges2, active_k
904
+ return complex_edges, real_merged_edges, active_k
905
+
906
+ # ====
907
+ # cusp
908
+ # ====
909
+
910
+ def cusp(self, t_grid):
911
+ """
912
+ """
913
+
914
+ return cusp_wrap(self, t_grid, edge_kwargs=None, max_iter=50,
915
+ tol=1.0e-12)
783
916
 
784
917
  # ========
785
918
  # eigvalsh