freealg 0.6.3__py3-none-any.whl → 0.7.1__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 (48) hide show
  1. freealg/__init__.py +8 -7
  2. freealg/__version__.py +1 -1
  3. freealg/_algebraic_form/__init__.py +11 -0
  4. freealg/_algebraic_form/_continuation_algebraic.py +503 -0
  5. freealg/_algebraic_form/_decompress.py +648 -0
  6. freealg/_algebraic_form/_edge.py +352 -0
  7. freealg/_algebraic_form/_sheets_util.py +145 -0
  8. freealg/_algebraic_form/algebraic_form.py +987 -0
  9. freealg/_freeform/__init__.py +16 -0
  10. freealg/_freeform/_density_util.py +243 -0
  11. freealg/{_linalg.py → _freeform/_linalg.py} +1 -1
  12. freealg/{freeform.py → _freeform/freeform.py} +2 -1
  13. freealg/_geometric_form/__init__.py +13 -0
  14. freealg/_geometric_form/_continuation_genus0.py +175 -0
  15. freealg/_geometric_form/_continuation_genus1.py +275 -0
  16. freealg/_geometric_form/_elliptic_functions.py +174 -0
  17. freealg/_geometric_form/_sphere_maps.py +63 -0
  18. freealg/_geometric_form/_torus_maps.py +118 -0
  19. freealg/_geometric_form/geometric_form.py +1094 -0
  20. freealg/_util.py +1 -228
  21. freealg/distributions/__init__.py +5 -1
  22. freealg/distributions/_chiral_block.py +440 -0
  23. freealg/distributions/_deformed_marchenko_pastur.py +617 -0
  24. freealg/distributions/_deformed_wigner.py +312 -0
  25. freealg/distributions/_kesten_mckay.py +2 -2
  26. freealg/distributions/_marchenko_pastur.py +199 -82
  27. freealg/distributions/_meixner.py +2 -2
  28. freealg/distributions/_wachter.py +2 -2
  29. freealg/distributions/_wigner.py +2 -2
  30. freealg/visualization/__init__.py +12 -0
  31. freealg/visualization/_glue_util.py +32 -0
  32. freealg/visualization/_rgb_hsv.py +125 -0
  33. {freealg-0.6.3.dist-info → freealg-0.7.1.dist-info}/METADATA +1 -1
  34. freealg-0.7.1.dist-info/RECORD +47 -0
  35. freealg-0.6.3.dist-info/RECORD +0 -26
  36. /freealg/{_chebyshev.py → _freeform/_chebyshev.py} +0 -0
  37. /freealg/{_damp.py → _freeform/_damp.py} +0 -0
  38. /freealg/{_decompress.py → _freeform/_decompress.py} +0 -0
  39. /freealg/{_jacobi.py → _freeform/_jacobi.py} +0 -0
  40. /freealg/{_pade.py → _freeform/_pade.py} +0 -0
  41. /freealg/{_plot_util.py → _freeform/_plot_util.py} +0 -0
  42. /freealg/{_sample.py → _freeform/_sample.py} +0 -0
  43. /freealg/{_series.py → _freeform/_series.py} +0 -0
  44. /freealg/{_support.py → _freeform/_support.py} +0 -0
  45. {freealg-0.6.3.dist-info → freealg-0.7.1.dist-info}/WHEEL +0 -0
  46. {freealg-0.6.3.dist-info → freealg-0.7.1.dist-info}/licenses/AUTHORS.txt +0 -0
  47. {freealg-0.6.3.dist-info → freealg-0.7.1.dist-info}/licenses/LICENSE.txt +0 -0
  48. {freealg-0.6.3.dist-info → freealg-0.7.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,987 @@
1
+ # SPDX-FileCopyrightText: Copyright 2026, Siavash Ameli <sameli@berkeley.edu>
2
+ # SPDX-License-Identifier: BSD-3-Clause
3
+ # SPDX-FileType: SOURCE
4
+ #
5
+ # This program is free software: you can redistribute it and/or modify it under
6
+ # the terms of the license found in the LICENSE.txt file in the root directory
7
+ # of this source tree.
8
+
9
+
10
+ # =======
11
+ # Imports
12
+ # =======
13
+
14
+ import numpy
15
+ from .._util import resolve_complex_dtype
16
+ # from .._util import compute_eig
17
+ from ._continuation_algebraic import sample_z_joukowski, \
18
+ filter_z_away_from_cuts, fit_polynomial_relation, eval_P
19
+ from ._edge import evolve_edges, merge_edges
20
+ from ._decompress import decompress_newton
21
+
22
+ # Fallback to previous numpy API
23
+ if not hasattr(numpy, 'trapezoid'):
24
+ numpy.trapezoid = numpy.trapz
25
+
26
+ __all__ = ['AlgebraicForm']
27
+
28
+
29
+ # ==============
30
+ # Algebraic Form
31
+ # ==============
32
+
33
+ class AlgebraicForm(object):
34
+ """
35
+ Algebraic surrogate for ensemble models.
36
+
37
+ Parameters
38
+ ----------
39
+
40
+ A : numpy.ndarray
41
+ The 2D symmetric :math:`\\mathbf{A}`. The eigenvalues of this will be
42
+ computed upon calling this class. If a 1D array provided, it is
43
+ assumed to be the eigenvalues of :math:`\\mathbf{A}`.
44
+
45
+ support : tuple, default=None
46
+ The support of the density of :math:`\\mathbf{A}`. If `None`, it is
47
+ estimated from the minimum and maximum of the eigenvalues.
48
+
49
+ delta: float, default=1e-6
50
+ Size of perturbations into the upper half plane for Plemelj's
51
+ formula.
52
+
53
+ dtype : {``'complex128'``, ``'complex256'``}, default = ``'complex128'``
54
+ Data type for inner computations of complex variables:
55
+
56
+ * ``'complex128'``: 128-bit complex numbers, equivalent of two double
57
+ precision floating point.
58
+ * ``'complex256'``: 256-bit complex numbers, equivalent of two long
59
+ double precision floating point. This optino is only available on
60
+ Linux machines.
61
+
62
+ When using series acceleration methods (such as setting
63
+ ``continuation`` in :func:`fit` function to ``wynn-eps``), setting a
64
+ higher precision floating point arithmetics might improve conference.
65
+
66
+ **kwargs : dict, optional
67
+ Parameters for the :func:`supp` function can also be prescribed
68
+ here when ``support=None``.
69
+
70
+ Attributes
71
+ ----------
72
+
73
+ eig : numpy.array
74
+ Eigenvalues of the matrix
75
+
76
+ support: tuple
77
+ The predicted (or given) support :math:`(\\lambda_{\\min},
78
+ \\lambda_{\\max})` of the eigenvalue density.
79
+
80
+ n : int
81
+ Initial array size (assuming a square matrix when :math:`\\mathbf{A}` is
82
+ 2D).
83
+
84
+ Methods
85
+ -------
86
+
87
+ fit
88
+ Fit the Jacobi polynomials to the empirical density.
89
+
90
+ density
91
+ Compute the spectral density of the matrix.
92
+
93
+ hilbert
94
+ Compute Hilbert transform of the spectral density
95
+
96
+ stieltjes
97
+ Compute Stieltjes transform of the spectral density
98
+
99
+ decompress
100
+ Free decompression of spectral density
101
+
102
+ eigvalsh
103
+ Estimate the eigenvalues
104
+
105
+ cond
106
+ Estimate the condition number
107
+
108
+ trace
109
+ Estimate the trace of a matrix power
110
+
111
+ slogdet
112
+ Estimate the sign and logarithm of the determinant
113
+
114
+ norm
115
+ Estimate the Schatten norm
116
+
117
+ Examples
118
+ --------
119
+
120
+ .. code-block:: python
121
+
122
+ >>> from freealg import FreeForm
123
+ """
124
+
125
+ # ====
126
+ # init
127
+ # ====
128
+
129
+ # def __init__(self, A, support=None, delta=1e-6, dtype='complex128',
130
+ # **kwargs):
131
+
132
+ def __init__(self, stieltjes, support=None, delta=1e-5, dtype='complex128',
133
+ **kwargs):
134
+ """
135
+ Initialization.
136
+ """
137
+
138
+ # self.A = None
139
+ # self.eig = None
140
+ self.stieltjes = stieltjes
141
+ self.support = support
142
+ self.delta = delta # Offset above real axis to apply Plemelj formula
143
+
144
+ # Data type for complex arrays
145
+ self.dtype = resolve_complex_dtype(dtype)
146
+
147
+ # # Eigenvalues
148
+ # if A.ndim == 1:
149
+ # # When A is a 1D array, it is assumed A is the eigenvalue array.
150
+ # self.eig = A
151
+ # self.n = len(A)
152
+ # elif A.ndim == 2:
153
+ # # When A is a 2D array, it is assumed A is the actual array,
154
+ # # and its eigenvalues will be computed.
155
+ # self.A = A
156
+ # self.n = A.shape[0]
157
+ # assert A.shape[0] == A.shape[1], \
158
+ # 'Only square matrices are permitted.'
159
+ # self.eig = compute_eig(A)
160
+
161
+ # Support
162
+ # if support is None:
163
+ # # Detect support
164
+ # self.lam_m, self.lam_p = supp(self.eig, **kwargs)
165
+ # else:
166
+ # self.lam_m = float(support[0])
167
+ # self.lam_p = float(support[1])
168
+ # self.support = (self.lam_m, self.lam_p)
169
+
170
+ # Initialize
171
+ # self.method = None # fitting rho: jacobi, chebyshev
172
+ self.a_coeffs = None # Polynomial coefficients
173
+ self.cache = {} # Cache inner-computations
174
+
175
+ # ===
176
+ # fit
177
+ # ===
178
+
179
+ def fit(self, deg_m, deg_z, reg=0.0,
180
+ r=[1.25, 6.0, 20.0],
181
+ n_r=[3, 2, 1],
182
+ n_samples=4096,
183
+ y_eps=2e-2,
184
+ x_pad=0.0,
185
+ triangular=None,
186
+ verbose=False):
187
+ """
188
+ Fits polynomial.
189
+ """
190
+
191
+ # Very important: reset cache whenever this function is called. This
192
+ # also empties all references holdign a cache copy.
193
+ # self.cache.clear()
194
+
195
+ # return self.a_coeffs
196
+
197
+ z_fits = []
198
+ for sup in self.support:
199
+ a, b = sup
200
+
201
+ for i in range(len(r)):
202
+ z_fits.append(sample_z_joukowski(a, b, n_samples=n_samples,
203
+ r=r[i], n_r=n_r[i]))
204
+
205
+ z_fit = numpy.concatenate(z_fits)
206
+
207
+ # Remove points too close to ANY cut
208
+ z_fit = filter_z_away_from_cuts(z_fit, self.support, y_eps=y_eps,
209
+ x_pad=x_pad)
210
+
211
+ # ---------
212
+
213
+ m1_fit = self.stieltjes(z_fit)
214
+ a_coeffs = fit_polynomial_relation(z_fit, m1_fit, s=deg_m, deg_z=deg_z,
215
+ ridge_lambda=reg,
216
+ triangular=triangular)
217
+
218
+ self.a_coeffs = a_coeffs
219
+
220
+ if verbose:
221
+ P_res = numpy.abs(eval_P(z_fit, m1_fit, a_coeffs))
222
+ print("fit residual max:", numpy.max(P_res[numpy.isfinite(P_res)]))
223
+ print("fit residual 99.9%:",
224
+ numpy.quantile(P_res[numpy.isfinite(P_res)], 0.999))
225
+
226
+ print('\nCoefficinets')
227
+ with numpy.printoptions(precision=4, suppress=True):
228
+ for i in range(a_coeffs.shape[0]):
229
+ for j in range(a_coeffs.shape[1]):
230
+ v = a_coeffs[i, j]
231
+ print(f"{v.real:>+0.4f}{v.imag:>+0.4f}j", end=" ")
232
+ print('')
233
+
234
+ print('\nCoefficients mangitudes')
235
+ with numpy.printoptions(precision=6, suppress=True):
236
+ print(numpy.abs(a_coeffs))
237
+
238
+ return a_coeffs
239
+
240
+ # =============
241
+ # generate grid
242
+ # =============
243
+
244
+ # def _generate_grid(self, scale, extend=1.0, N=500):
245
+ # """
246
+ # Generate a grid of points to evaluate density / Hilbert / Stieltjes
247
+ # transforms.
248
+ # """
249
+ #
250
+ # radius = 0.5 * (self.lam_p - self.lam_m)
251
+ # center = 0.5 * (self.lam_p + self.lam_m)
252
+ #
253
+ # x_min = numpy.floor(extend * (center - extend * radius * scale))
254
+ # x_max = numpy.ceil(extend * (center + extend * radius * scale))
255
+ #
256
+ # x_min /= extend
257
+ # x_max /= extend
258
+ #
259
+ # return numpy.linspace(x_min, x_max, N)
260
+
261
+ # =======
262
+ # density
263
+ # =======
264
+
265
+ def density(self, x=None, plot=False, latex=False, save=False):
266
+ """
267
+ Evaluate spectral density.
268
+
269
+ Parameters
270
+ ----------
271
+
272
+ x : numpy.array, default=None
273
+ Positions where density to be evaluated at. If `None`, an interval
274
+ slightly larger than the support interval will be used.
275
+
276
+ plot : bool, default=False
277
+ If `True`, density is plotted.
278
+
279
+ latex : bool, default=False
280
+ If `True`, the plot is rendered using LaTeX. This option is
281
+ relevant only if ``plot=True``.
282
+
283
+ save : bool, default=False
284
+ If not `False`, the plot is saved. If a string is given, it is
285
+ assumed to the save filename (with the file extension). This option
286
+ is relevant only if ``plot=True``.
287
+
288
+ Returns
289
+ -------
290
+
291
+ rho : numpy.array
292
+ Density at locations x.
293
+
294
+ See Also
295
+ --------
296
+ hilbert
297
+ stieltjes
298
+
299
+ Examples
300
+ --------
301
+
302
+ .. code-block:: python
303
+
304
+ >>> from freealg import FreeForm
305
+ """
306
+
307
+ pass
308
+
309
+ if self.a_coeffs is None:
310
+ raise RuntimeError('The model needs to be fit using the .fit() ' +
311
+ 'function.')
312
+
313
+ # # Create x if not given
314
+ # if x is None:
315
+ # x = self._generate_grid(1.25)
316
+ #
317
+ # # Preallocate density to zero
318
+ # rho = numpy.zeros_like(x)
319
+ #
320
+ # # Compute density only inside support
321
+ # mask = numpy.logical_and(x >= self.lam_m, x <= self.lam_p)
322
+ #
323
+ # if self.method == 'jacobi':
324
+ # rho[mask] = jacobi_density(x[mask], self.psi, self.support,
325
+ # self.alpha, self.beta)
326
+ # elif self.method == 'chebyshev':
327
+ # rho[mask] = chebyshev_density(x[mask], self.psi, self.support)
328
+ # else:
329
+ # raise RuntimeError('"method" is invalid.')
330
+ #
331
+ # # Check density is unit mass
332
+ # mass = numpy.trapezoid(rho, x)
333
+ # if not numpy.isclose(mass, 1.0, atol=1e-2):
334
+ # print(f'"rho" is not unit mass. mass: {mass:>0.3f}. Set ' +
335
+ # r'"force=True".')
336
+ #
337
+ # # Check density is positive
338
+ # min_rho = numpy.min(rho)
339
+ # if min_rho < 0.0 - 1e-3:
340
+ # print(f'"rho" is not positive. min_rho: {min_rho:>0.3f}. Set ' +
341
+ # r'"force=True".')
342
+ #
343
+ # if plot:
344
+ # plot_density(x, rho, eig=self.eig, support=self.support,
345
+ # label='Estimate', latex=latex, save=save)
346
+ #
347
+ # return rho
348
+
349
+ # =======
350
+ # hilbert
351
+ # =======
352
+
353
+ def hilbert(self, x=None, rho=None, plot=False, latex=False, save=False):
354
+ """
355
+ Compute Hilbert transform of the spectral density.
356
+
357
+ Parameters
358
+ ----------
359
+
360
+ x : numpy.array, default=None
361
+ The locations where Hilbert transform is evaluated at. If `None`,
362
+ an interval slightly larger than the support interval of the
363
+ spectral density is used.
364
+
365
+ rho : numpy.array, default=None
366
+ Density. If `None`, it will be computed.
367
+
368
+ plot : bool, default=False
369
+ If `True`, density is plotted.
370
+
371
+ latex : bool, default=False
372
+ If `True`, the plot is rendered using LaTeX. This option is
373
+ relevant only if ``plot=True``.
374
+
375
+ save : bool, default=False
376
+ If not `False`, the plot is saved. If a string is given, it is
377
+ assumed to the save filename (with the file extension). This option
378
+ is relevant only if ``plot=True``.
379
+
380
+ Returns
381
+ -------
382
+
383
+ hilb : numpy.array
384
+ The Hilbert transform on the locations `x`.
385
+
386
+ See Also
387
+ --------
388
+ density
389
+ stieltjes
390
+
391
+ Examples
392
+ --------
393
+
394
+ .. code-block:: python
395
+
396
+ >>> from freealg import FreeForm
397
+ """
398
+
399
+ pass
400
+
401
+ if self.a_coeffs is None:
402
+ raise RuntimeError('The model needs to be fit using the .fit() ' +
403
+ 'function.')
404
+
405
+ # # Create x if not given
406
+ # if x is None:
407
+ # x = self._generate_grid(1.25)
408
+ #
409
+ # # if (numpy.min(x) > self.lam_m) or (numpy.max(x) < self.lam_p):
410
+ # # raise ValueError('"x" does not encompass support interval.')
411
+ #
412
+ # # Preallocate density to zero
413
+ # if rho is None:
414
+ # rho = self.density(x)
415
+ #
416
+ # # mask of support [lam_m, lam_p]
417
+ # mask = numpy.logical_and(x >= self.lam_m, x <= self.lam_p)
418
+ # x_s = x[mask]
419
+ # rho_s = rho[mask]
420
+ #
421
+ # # Form the matrix of integrands: rho_s / (t - x_i)
422
+ # # Here, we have diff[i,j] = x[i] - x_s[j]
423
+ # diff = x[:, None] - x_s[None, :]
424
+ # D = rho_s[None, :] / diff
425
+ #
426
+ # # Principal-value: wherever t == x_i, then diff == 0, zero that entry
427
+ # # (numpy.isclose handles floating-point exactly)
428
+ # D[numpy.isclose(diff, 0.0)] = 0.0
429
+ #
430
+ # # Integrate each row over t using trapezoid rule on x_s
431
+ # # Namely, hilb[i] = int rho_s(t)/(t - x[i]) dt
432
+ # hilb = numpy.trapezoid(D, x_s, axis=1) / numpy.pi
433
+ #
434
+ # # We use negative sign convention
435
+ # hilb = -hilb
436
+ #
437
+ # if plot:
438
+ # plot_hilbert(x, hilb, support=self.support, latex=latex,
439
+ # save=save)
440
+ #
441
+ # return hilb
442
+
443
+ # =========
444
+ # stieltjes
445
+ # =========
446
+
447
+ def stieltjes(self, x=None, y=None, plot=False, latex=False, save=False):
448
+ """
449
+ Compute Stieltjes transform of the spectral density on a grid.
450
+
451
+ This function evaluates Stieltjes transform on an array of points, or
452
+ over a 2D Cartesian grid on the complex plane.
453
+
454
+ Parameters
455
+ ----------
456
+
457
+ x : numpy.array, default=None
458
+ The x axis of the grid where the Stieltjes transform is evaluated.
459
+ If `None`, an interval slightly larger than the support interval of
460
+ the spectral density is used.
461
+
462
+ y : numpy.array, default=None
463
+ The y axis of the grid where the Stieltjes transform is evaluated.
464
+ If `None`, a grid on the interval ``[-1, 1]`` is used.
465
+
466
+ plot : bool, default=False
467
+ If `True`, density is plotted.
468
+
469
+ latex : bool, default=False
470
+ If `True`, the plot is rendered using LaTeX. This option is
471
+ relevant only if ``plot=True``.
472
+
473
+ save : bool, default=False
474
+ If not `False`, the plot is saved. If a string is given, it is
475
+ assumed to the save filename (with the file extension). This option
476
+ is relevant only if ``plot=True``.
477
+
478
+ Returns
479
+ -------
480
+
481
+ m_p : numpy.ndarray
482
+ The Stieltjes transform on the principal branch.
483
+
484
+ m_m : numpy.ndarray
485
+ The Stieltjes transform continued to the secondary branch.
486
+
487
+ See Also
488
+ --------
489
+
490
+ density
491
+ hilbert
492
+
493
+ Examples
494
+ --------
495
+
496
+ .. code-block:: python
497
+
498
+ >>> from freealg import FreeForm
499
+ """
500
+
501
+ pass
502
+
503
+ if self.a_coeffs is None:
504
+ raise RuntimeError('The model needs to be fit using the .fit() ' +
505
+ 'function.')
506
+
507
+ # # Create x if not given
508
+ # if x is None:
509
+ # x = self._generate_grid(2.0, extend=2.0)
510
+ #
511
+ # # Create y if not given
512
+ # if (plot is False) and (y is None):
513
+ # # Do not use a Cartesian grid. Create a 1D array z slightly above
514
+ # # the real line.
515
+ # y = self.delta * 1j
516
+ # z = x.astype(complex) + y # shape (Nx,)
517
+ # else:
518
+ # # Use a Cartesian grid
519
+ # if y is None:
520
+ # y = numpy.linspace(-1, 1, 400)
521
+ # x_grid, y_grid = numpy.meshgrid(x.real, y.real)
522
+ # z = x_grid + 1j * y_grid # shape (Ny, Nx)
523
+ #
524
+ # m1, m2 = self._eval_stieltjes(z, branches=True)
525
+ #
526
+ # if plot:
527
+ # plot_stieltjes(x, y, m1, m2, self.support, latex=latex,
528
+ # save=save)
529
+ #
530
+ # return m1, m2
531
+
532
+ # ==============
533
+ # eval stieltjes
534
+ # ==============
535
+
536
+ def _eval_stieltjes(self, z, branches=False):
537
+ """
538
+ Compute Stieltjes transform of the spectral density.
539
+
540
+ Parameters
541
+ ----------
542
+
543
+ z : numpy.array
544
+ The z values in the complex plan where the Stieltjes transform is
545
+ evaluated.
546
+
547
+ branches : bool, default = False
548
+ Return both the principal and secondary branches of the Stieltjes
549
+ transform. The default ``branches=False`` will return only
550
+ the secondary branch.
551
+
552
+ Returns
553
+ -------
554
+
555
+ m_p : numpy.ndarray
556
+ The Stieltjes transform on the principal branch if
557
+ ``branches=True``.
558
+
559
+ m_m : numpy.ndarray
560
+ The Stieltjes transform continued to the secondary branch.
561
+ """
562
+
563
+ pass
564
+
565
+ # ==========
566
+ # decompress
567
+ # ==========
568
+
569
+ def decompress(self, x, t,
570
+ max_iter=50,
571
+ tol=1e-12,
572
+ armijo=1e-4,
573
+ min_lam=1e-6,
574
+ w_min=1e-14,
575
+ sweep=True,
576
+ verbose=False):
577
+ """
578
+ Free decompression of spectral density.
579
+ """
580
+
581
+ # Check size argument
582
+ # if numpy.isscalar(size):
583
+ # size = int(size)
584
+ # else:
585
+ # # Check monotonic increment (either all increasing or decreasing)
586
+ # diff = numpy.diff(size)
587
+ # if not (numpy.all(diff >= 0) or numpy.all(diff <= 0)):
588
+ # raise ValueError('"size" increment should be monotonic.')
589
+
590
+ # Decompression ratio equal to e^{t}.
591
+ # alpha = numpy.atleast_1d(size) / self.n
592
+
593
+ # # If the input size was only a scalar, return a 1D rho, otherwise 2D.
594
+ # if numpy.isscalar(size):
595
+ # rho = numpy.squeeze(rho)
596
+ #
597
+ # # Plot only the last size
598
+ # if plot:
599
+ # if numpy.isscalar(size):
600
+ # rho_last = rho
601
+ # else:
602
+ # rho_last = rho[-1, :]
603
+ # plot_density(x, rho_last, support=(lb, ub),
604
+ # label='Decompression', latex=latex, save=save)
605
+ #
606
+ # return rho, x
607
+
608
+ # Query grid on the real axis + a small imaginary buffer
609
+ z_query = x + 1j * self.delta
610
+
611
+ # Initial condition at t=0 (physical branch)
612
+ w0_list = self.stieltjes(z_query)
613
+
614
+ # Evolve
615
+ W, ok = decompress_newton(
616
+ z_query, t, self.a_coeffs,
617
+ w0_list=w0_list,
618
+ max_iter=max_iter,
619
+ tol=tol,
620
+ armijo=armijo,
621
+ min_lam=min_lam,
622
+ w_min=w_min,
623
+ sweep=sweep)
624
+
625
+ rho = W.imag / numpy.pi
626
+
627
+ if verbose:
628
+ print("success rate per t:", ok.mean(axis=1))
629
+
630
+ return rho
631
+
632
+ # ====
633
+ # edge
634
+ # ====
635
+
636
+ def edge(self, t, eta=1e-3, dt_max=0.1, max_iter=30, tol=1e-12,
637
+ verbose=False):
638
+ """
639
+ Evolves spectral edges.
640
+ """
641
+
642
+ edges, ok_edges = evolve_edges(t, self.a_coeffs, support=self.support,
643
+ eta=eta, dt_max=dt_max,
644
+ max_iter=max_iter, tol=tol)
645
+
646
+ edges2, active_k = merge_edges(edges, tol=1e-4)
647
+
648
+ if verbose:
649
+ print("edge success rate:", ok_edges.mean())
650
+
651
+ return edges2, active_k
652
+
653
+ # ========
654
+ # eigvalsh
655
+ # ========
656
+
657
+ def eigvalsh(self, size=None, seed=None, **kwargs):
658
+ """
659
+ Estimate the eigenvalues.
660
+
661
+ This function estimates the eigenvalues of the freeform matrix
662
+ or a larger matrix containing it using free decompression.
663
+
664
+ Parameters
665
+ ----------
666
+
667
+ size : int, default=None
668
+ The size of the matrix containing :math:`\\mathbf{A}` to estimate
669
+ eigenvalues of. If None, returns estimates of the eigenvalues of
670
+ :math:`\\mathbf{A}` itself.
671
+
672
+ seed : int, default=None
673
+ The seed for the Quasi-Monte Carlo sampler.
674
+
675
+ **kwargs : dict, optional
676
+ Pass additional options to the underlying
677
+ :func:`FreeForm.decompress` function.
678
+
679
+ Returns
680
+ -------
681
+
682
+ eigs : numpy.array
683
+ Eigenvalues of decompressed matrix
684
+
685
+ See Also
686
+ --------
687
+
688
+ FreeForm.decompress
689
+ FreeForm.cond
690
+
691
+ Notes
692
+ -----
693
+
694
+ All arguments to the `.decompress()` procedure can be provided.
695
+
696
+ Examples
697
+ --------
698
+
699
+ .. code-block:: python
700
+ :emphasize-lines: 1
701
+
702
+ >>> from freealg import FreeForm
703
+ """
704
+
705
+ # if size is None:
706
+ # size = self.n
707
+ #
708
+ # rho, x = self.decompress(size, **kwargs)
709
+ # eigs = numpy.sort(sample(x, rho, size, method='qmc', seed=seed))
710
+ #
711
+ # return eigs
712
+ pass
713
+
714
+ # ====
715
+ # cond
716
+ # ====
717
+
718
+ def cond(self, size=None, seed=None, **kwargs):
719
+ """
720
+ Estimate the condition number.
721
+
722
+ This function estimates the condition number of the matrix
723
+ :math:`\\mathbf{A}` or a larger matrix containing :math:`\\mathbf{A}`
724
+ using free decompression.
725
+
726
+ Parameters
727
+ ----------
728
+
729
+ size : int, default=None
730
+ The size of the matrix containing :math:`\\mathbf{A}` to estimate
731
+ eigenvalues of. If None, returns estimates of the eigenvalues of
732
+ :math:`\\mathbf{A}` itself.
733
+
734
+ **kwargs : dict, optional
735
+ Pass additional options to the underlying
736
+ :func:`FreeForm.decompress` function.
737
+
738
+ Returns
739
+ -------
740
+
741
+ c : float
742
+ Condition number
743
+
744
+ See Also
745
+ --------
746
+
747
+ FreeForm.eigvalsh
748
+ FreeForm.norm
749
+ FreeForm.slogdet
750
+ FreeForm.trace
751
+
752
+ Examples
753
+ --------
754
+
755
+ .. code-block:: python
756
+ :emphasize-lines: 1
757
+
758
+ >>> from freealg import FreeForm
759
+ """
760
+
761
+ eigs = self.eigvalsh(size=size, **kwargs)
762
+ return eigs.max() / eigs.min()
763
+
764
+ # =====
765
+ # trace
766
+ # =====
767
+
768
+ def trace(self, size=None, p=1.0, seed=None, **kwargs):
769
+ """
770
+ Estimate the trace of a power.
771
+
772
+ This function estimates the trace of the matrix power
773
+ :math:`\\mathbf{A}^p` of the freeform or that of a larger matrix
774
+ containing it.
775
+
776
+ Parameters
777
+ ----------
778
+
779
+ size : int, default=None
780
+ The size of the matrix containing :math:`\\mathbf{A}` to estimate
781
+ eigenvalues of. If None, returns estimates of the eigenvalues of
782
+ :math:`\\mathbf{A}` itself.
783
+
784
+ p : float, default=1.0
785
+ The exponent :math:`p` in :math:`\\mathbf{A}^p`.
786
+
787
+ seed : int, default=None
788
+ The seed for the Quasi-Monte Carlo sampler.
789
+
790
+ **kwargs : dict, optional
791
+ Pass additional options to the underlying
792
+ :func:`FreeForm.decompress` function.
793
+
794
+ Returns
795
+ -------
796
+
797
+ trace : float
798
+ matrix trace
799
+
800
+ See Also
801
+ --------
802
+
803
+ FreeForm.eigvalsh
804
+ FreeForm.cond
805
+ FreeForm.slogdet
806
+ FreeForm.norm
807
+
808
+ Notes
809
+ -----
810
+
811
+ The trace is highly amenable to subsampling: under free decompression
812
+ the average eigenvalue is assumed constant, so the trace increases
813
+ linearly. Traces of powers fall back to :func:`eigvalsh`.
814
+ All arguments to the `.decompress()` procedure can be provided.
815
+
816
+ Examples
817
+ --------
818
+
819
+ .. code-block:: python
820
+ :emphasize-lines: 1
821
+
822
+ >>> from freealg import FreeForm
823
+ """
824
+
825
+ if numpy.isclose(p, 1.0):
826
+ return numpy.mean(self.eig) * (size / self.n)
827
+
828
+ eig = self.eigvalsh(size=size, seed=seed, **kwargs)
829
+ return numpy.sum(eig ** p)
830
+
831
+ # =======
832
+ # slogdet
833
+ # =======
834
+
835
+ def slogdet(self, size=None, seed=None, **kwargs):
836
+ """
837
+ Estimate the sign and logarithm of the determinant.
838
+
839
+ This function estimates the *slogdet* of the freeform or that of
840
+ a larger matrix containing it using free decompression.
841
+
842
+ Parameters
843
+ ----------
844
+
845
+ size : int, default=None
846
+ The size of the matrix containing :math:`\\mathbf{A}` to estimate
847
+ eigenvalues of. If None, returns estimates of the eigenvalues of
848
+ :math:`\\mathbf{A}` itself.
849
+
850
+ seed : int, default=None
851
+ The seed for the Quasi-Monte Carlo sampler.
852
+
853
+ Returns
854
+ -------
855
+
856
+ sign : float
857
+ Sign of determinant
858
+
859
+ ld : float
860
+ natural logarithm of the absolute value of the determinant
861
+
862
+ See Also
863
+ --------
864
+
865
+ FreeForm.eigvalsh
866
+ FreeForm.cond
867
+ FreeForm.trace
868
+ FreeForm.norm
869
+
870
+ Notes
871
+ -----
872
+
873
+ All arguments to the `.decompress()` procedure can be provided.
874
+
875
+ Examples
876
+ --------
877
+
878
+ .. code-block:: python
879
+ :emphasize-lines: 1
880
+
881
+ >>> from freealg import FreeForm
882
+ """
883
+
884
+ eigs = self.eigvalsh(size=size, seed=seed, **kwargs)
885
+ sign = numpy.prod(numpy.sign(eigs))
886
+ ld = numpy.sum(numpy.log(numpy.abs(eigs)))
887
+ return sign, ld
888
+
889
+ # ====
890
+ # norm
891
+ # ====
892
+
893
+ def norm(self, size=None, order=2, seed=None, **kwargs):
894
+ """
895
+ Estimate the Schatten norm.
896
+
897
+ This function estimates the norm of the freeform or a larger
898
+ matrix containing it using free decompression.
899
+
900
+ Parameters
901
+ ----------
902
+
903
+ size : int, default=None
904
+ The size of the matrix containing :math:`\\mathbf{A}` to estimate
905
+ eigenvalues of. If None, returns estimates of the eigenvalues of
906
+ :math:`\\mathbf{A}` itself.
907
+
908
+ order : {float, ``''inf``, ``'-inf'``, ``'fro'``, ``'nuc'``}, default=2
909
+ Order of the norm.
910
+
911
+ * float :math:`p`: Schatten p-norm.
912
+ * ``'inf'``: Largest absolute eigenvalue
913
+ :math:`\\max \\vert \\lambda_i \\vert)`
914
+ * ``'-inf'``: Smallest absolute eigenvalue
915
+ :math:`\\min \\vert \\lambda_i \\vert)`
916
+ * ``'fro'``: Frobenius norm corresponding to :math:`p=2`
917
+ * ``'nuc'``: Nuclear (or trace) norm corresponding to :math:`p=1`
918
+
919
+ seed : int, default=None
920
+ The seed for the Quasi-Monte Carlo sampler.
921
+
922
+ **kwargs : dict, optional
923
+ Pass additional options to the underlying
924
+ :func:`FreeForm.decompress` function.
925
+
926
+ Returns
927
+ -------
928
+
929
+ norm : float
930
+ matrix norm
931
+
932
+ See Also
933
+ --------
934
+
935
+ FreeForm.eigvalsh
936
+ FreeForm.cond
937
+ FreeForm.slogdet
938
+ FreeForm.trace
939
+
940
+ Notes
941
+ -----
942
+
943
+ Thes Schatten :math:`p`-norm is defined by
944
+
945
+ .. math::
946
+
947
+ \\Vert \\mathbf{A} \\Vert_p = \\left(
948
+ \\sum_{i=1}^N \\vert \\lambda_i \\vert^p \\right)^{1/p}.
949
+
950
+ Examples
951
+ --------
952
+
953
+ .. code-block:: python
954
+ :emphasize-lines: 1
955
+
956
+ >>> from freealg import FreeForm
957
+ """
958
+
959
+ eigs = self.eigvalsh(size, seed=seed, **kwargs)
960
+
961
+ # Check order type and convert to float
962
+ if order == 'nuc':
963
+ order = 1
964
+ elif order == 'fro':
965
+ order = 2
966
+ elif order == 'inf':
967
+ order = float('inf')
968
+ elif order == '-inf':
969
+ order = -float('inf')
970
+ elif not isinstance(order,
971
+ (int, float, numpy.integer, numpy.floating)) \
972
+ and not isinstance(order, (bool, numpy.bool_)):
973
+ raise ValueError('"order" is invalid.')
974
+
975
+ # Compute norm
976
+ if numpy.isinf(order) and not numpy.isneginf(order):
977
+ norm_ = max(numpy.abs(eigs))
978
+
979
+ elif numpy.isneginf(order):
980
+ norm_ = min(numpy.abs(eigs))
981
+
982
+ elif isinstance(order, (int, float, numpy.integer, numpy.floating)) \
983
+ and not isinstance(order, (bool, numpy.bool_)):
984
+ norm_q = numpy.sum(numpy.abs(eigs)**order)
985
+ norm_ = norm_q**(1.0 / order)
986
+
987
+ return norm_