freealg 0.6.2__py3-none-any.whl → 0.7.0__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/__init__.py +8 -7
- freealg/__version__.py +1 -1
- freealg/_algebraic_form/__init__.py +11 -0
- freealg/_algebraic_form/_continuation_algebraic.py +503 -0
- freealg/_algebraic_form/_decompress.py +648 -0
- freealg/_algebraic_form/_edge.py +352 -0
- freealg/_algebraic_form/_sheets_util.py +145 -0
- freealg/_algebraic_form/algebraic_form.py +987 -0
- freealg/_freeform/__init__.py +16 -0
- freealg/{_decompress.py → _freeform/_decompress.py} +0 -10
- freealg/_freeform/_density_util.py +243 -0
- freealg/{_linalg.py → _freeform/_linalg.py} +1 -1
- freealg/{_pade.py → _freeform/_pade.py} +0 -1
- freealg/{freeform.py → _freeform/freeform.py} +2 -31
- freealg/_geometric_form/__init__.py +13 -0
- freealg/_geometric_form/_continuation_genus0.py +175 -0
- freealg/_geometric_form/_continuation_genus1.py +275 -0
- freealg/_geometric_form/_elliptic_functions.py +174 -0
- freealg/_geometric_form/_sphere_maps.py +63 -0
- freealg/_geometric_form/_torus_maps.py +118 -0
- freealg/_geometric_form/geometric_form.py +1094 -0
- freealg/_util.py +1 -217
- freealg/distributions/__init__.py +5 -1
- freealg/distributions/_chiral_block.py +440 -0
- freealg/distributions/_deformed_marchenko_pastur.py +617 -0
- freealg/distributions/_deformed_wigner.py +312 -0
- freealg/distributions/_marchenko_pastur.py +197 -80
- freealg/visualization/__init__.py +12 -0
- freealg/visualization/_glue_util.py +32 -0
- freealg/visualization/_rgb_hsv.py +125 -0
- {freealg-0.6.2.dist-info → freealg-0.7.0.dist-info}/METADATA +9 -11
- freealg-0.7.0.dist-info/RECORD +47 -0
- freealg-0.6.2.dist-info/RECORD +0 -26
- /freealg/{_chebyshev.py → _freeform/_chebyshev.py} +0 -0
- /freealg/{_damp.py → _freeform/_damp.py} +0 -0
- /freealg/{_jacobi.py → _freeform/_jacobi.py} +0 -0
- /freealg/{_plot_util.py → _freeform/_plot_util.py} +0 -0
- /freealg/{_sample.py → _freeform/_sample.py} +0 -0
- /freealg/{_series.py → _freeform/_series.py} +0 -0
- /freealg/{_support.py → _freeform/_support.py} +0 -0
- {freealg-0.6.2.dist-info → freealg-0.7.0.dist-info}/WHEEL +0 -0
- {freealg-0.6.2.dist-info → freealg-0.7.0.dist-info}/licenses/AUTHORS.txt +0 -0
- {freealg-0.6.2.dist-info → freealg-0.7.0.dist-info}/licenses/LICENSE.txt +0 -0
- {freealg-0.6.2.dist-info → freealg-0.7.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright 2025, 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
|
|
6
|
+
# under the terms of the license found in the LICENSE.txt file in the root
|
|
7
|
+
# directory of this source tree.
|
|
8
|
+
|
|
9
|
+
from .freeform import FreeForm
|
|
10
|
+
from ._linalg import eigvalsh, cond, norm, trace, slogdet
|
|
11
|
+
from ._support import supp
|
|
12
|
+
from ._sample import sample
|
|
13
|
+
from ._density_util import kde
|
|
14
|
+
|
|
15
|
+
__all__ = ['FreeForm', 'eigvalsh', 'cond', 'norm', 'trace', 'slogdet', 'supp',
|
|
16
|
+
'sample', 'kde']
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright 2025, 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 scipy.stats import gaussian_kde
|
|
16
|
+
from scipy.stats import beta
|
|
17
|
+
# from statsmodels.nonparametric.kde import KDEUnivariate
|
|
18
|
+
from scipy.optimize import minimize
|
|
19
|
+
import matplotlib.pyplot as plt
|
|
20
|
+
import texplot
|
|
21
|
+
from ._plot_util import _auto_bins
|
|
22
|
+
|
|
23
|
+
# Fallback to previous API
|
|
24
|
+
if not hasattr(numpy, 'trapezoid'):
|
|
25
|
+
numpy.trapezoid = numpy.trapz
|
|
26
|
+
|
|
27
|
+
__all__ = ['kde', 'force_density']
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# ===
|
|
31
|
+
# kde
|
|
32
|
+
# ===
|
|
33
|
+
|
|
34
|
+
def kde(eig, xs, lam_m, lam_p, h, kernel='beta', plot=False):
|
|
35
|
+
"""
|
|
36
|
+
Kernel density estimation of eigenvalues.
|
|
37
|
+
|
|
38
|
+
Parameters
|
|
39
|
+
----------
|
|
40
|
+
|
|
41
|
+
eig : numpy.array
|
|
42
|
+
1D array of samples of size `n`.
|
|
43
|
+
|
|
44
|
+
xs : numpy.array
|
|
45
|
+
1D array of evaluation grid (must lie within ``[lam_m, lam_p]``)
|
|
46
|
+
|
|
47
|
+
lam_m : float
|
|
48
|
+
Lower end of the support endpoints with ``lam_m < lam_p``.
|
|
49
|
+
|
|
50
|
+
lam_p : float
|
|
51
|
+
Upper end of the support endpoints with ``lam_m < lam_p``.
|
|
52
|
+
|
|
53
|
+
h : float
|
|
54
|
+
Kernel bandwidth in rescaled units where ``0 < h < 1``.
|
|
55
|
+
|
|
56
|
+
kernel : {``'gaussian'``, ``'beta'``}, default= ``'beta'``
|
|
57
|
+
Kernel function using either Gaussian or Beta distribution.
|
|
58
|
+
|
|
59
|
+
plot : bool, default=False
|
|
60
|
+
If `True`, the KDE is plotted.
|
|
61
|
+
|
|
62
|
+
Returns
|
|
63
|
+
-------
|
|
64
|
+
|
|
65
|
+
pdf : numpy.ndarray
|
|
66
|
+
Probability distribution function with the same length as ``xs``.
|
|
67
|
+
|
|
68
|
+
See Also
|
|
69
|
+
--------
|
|
70
|
+
|
|
71
|
+
freealg.supp
|
|
72
|
+
freealg.sample
|
|
73
|
+
|
|
74
|
+
References
|
|
75
|
+
----------
|
|
76
|
+
|
|
77
|
+
.. [1] `R-package documentation for Beta kernel
|
|
78
|
+
<https://search.r-project.org/CRAN/refmans/DELTD/html/Beta.html>`__
|
|
79
|
+
|
|
80
|
+
.. [2] Chen, S. X. (1999). Beta Kernel estimators for density functions.
|
|
81
|
+
*Computational Statistics and Data Analysis* 31 p. 131--145.
|
|
82
|
+
|
|
83
|
+
Notes
|
|
84
|
+
-----
|
|
85
|
+
|
|
86
|
+
In Beta kernel density estimation, the shape parameters :math:`a` and
|
|
87
|
+
:math:`b` of the :math:`\\mathrm{Beta}(a, b)` distribution are computed
|
|
88
|
+
for each data point :math:`u` as:
|
|
89
|
+
|
|
90
|
+
.. math::
|
|
91
|
+
|
|
92
|
+
a = (u / h) + 1.0
|
|
93
|
+
b = ((1.0 - u) / h) + 1.0
|
|
94
|
+
|
|
95
|
+
This is a standard way of using Beta kernel (see R-package documentation
|
|
96
|
+
[1]_). These equations are derived from *moment matching* method, where
|
|
97
|
+
|
|
98
|
+
.. math::
|
|
99
|
+
|
|
100
|
+
\\mathrm{Mean}(\\mathrm{Beta}(a,b)) = u
|
|
101
|
+
\\mathrm{Var}(\\mathrm{Beta}(a,b)) = (1-u) u h
|
|
102
|
+
|
|
103
|
+
Solving these two equations for :math:`a` and :math:`b` yields the
|
|
104
|
+
relations above. See [2]_ (page 134).
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
if kernel == 'gaussian':
|
|
108
|
+
pdf = gaussian_kde(eig, bw_method=h)(xs)
|
|
109
|
+
|
|
110
|
+
# Adaptive KDE
|
|
111
|
+
# k = KDEUnivariate(eig)
|
|
112
|
+
# k.fit(kernel='gau', bw='silverman', fft=False, weights=None,
|
|
113
|
+
# gridsize=1024, adaptive=True)
|
|
114
|
+
# pdf = k.evaluate(xs)
|
|
115
|
+
|
|
116
|
+
elif kernel == 'beta':
|
|
117
|
+
|
|
118
|
+
span = lam_p - lam_m
|
|
119
|
+
if span <= 0:
|
|
120
|
+
raise ValueError('"lam_p" must be larger than "lam_m".')
|
|
121
|
+
|
|
122
|
+
# map samples and grid to [0, 1]
|
|
123
|
+
u = (eig - lam_m) / span
|
|
124
|
+
t = (xs - lam_m) / span
|
|
125
|
+
|
|
126
|
+
# keep only samples strictly inside (0,1)
|
|
127
|
+
if (u.min() < 0) or (u.max() > 1):
|
|
128
|
+
u = u[(u > 0) & (u < 1)]
|
|
129
|
+
|
|
130
|
+
n = u.size
|
|
131
|
+
if n == 0:
|
|
132
|
+
return numpy.zeros_like(xs, dtype=float)
|
|
133
|
+
|
|
134
|
+
# Shape parameters "a" and "b" or the kernel Beta(a, b), which is
|
|
135
|
+
# computed for each data point "u" (see notes above). These are
|
|
136
|
+
# vectorized.
|
|
137
|
+
a = (u / h) + 1.0
|
|
138
|
+
b = ((1.0 - u) / h) + 1.0
|
|
139
|
+
|
|
140
|
+
# # tiny positive number to keep shape parameters > 0
|
|
141
|
+
eps = 1e-6
|
|
142
|
+
a = numpy.clip(a, eps, None)
|
|
143
|
+
b = numpy.clip(b, eps, None)
|
|
144
|
+
|
|
145
|
+
# Beta kernel
|
|
146
|
+
pdf_matrix = beta.pdf(t[None, :], a[:, None], b[:, None])
|
|
147
|
+
|
|
148
|
+
# Average and re-normalize back to x variable
|
|
149
|
+
pdf = pdf_matrix.sum(axis=0) / (n * span)
|
|
150
|
+
|
|
151
|
+
# Exact zeros outside [lam_m, lam_p]
|
|
152
|
+
pdf[(t < 0) | (t > 1)] = 0.0
|
|
153
|
+
|
|
154
|
+
else:
|
|
155
|
+
raise NotImplementedError('"kernel" is invalid.')
|
|
156
|
+
|
|
157
|
+
if plot:
|
|
158
|
+
with texplot.theme(use_latex=False):
|
|
159
|
+
fig, ax = plt.subplots(figsize=(6, 4))
|
|
160
|
+
|
|
161
|
+
x_min = numpy.min(xs)
|
|
162
|
+
x_max = numpy.max(xs)
|
|
163
|
+
bins = numpy.linspace(x_min, x_max, _auto_bins(eig))
|
|
164
|
+
_ = ax.hist(eig, bins, density=True, color='silver',
|
|
165
|
+
edgecolor='none', label='Samples histogram')
|
|
166
|
+
ax.plot(xs, pdf, color='black', label='KDE')
|
|
167
|
+
ax.set_xlabel(r'$x$')
|
|
168
|
+
ax.set_ylabel(r'$\\rho(x)$')
|
|
169
|
+
ax.set_xlim([xs[0], xs[-1]])
|
|
170
|
+
ax.set_ylim(bottom=0)
|
|
171
|
+
ax.set_title('Kernel Density Estimation')
|
|
172
|
+
ax.legend(fontsize='x-small')
|
|
173
|
+
plt.show()
|
|
174
|
+
|
|
175
|
+
return pdf
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
# =============
|
|
179
|
+
# force density
|
|
180
|
+
# =============
|
|
181
|
+
|
|
182
|
+
def force_density(psi0, support, density, grid, alpha=0.0, beta=0.0):
|
|
183
|
+
"""
|
|
184
|
+
Starting from psi0 (raw projection), solve
|
|
185
|
+
min 0.5 ||psi - psi0||^2
|
|
186
|
+
s.t. F_pos psi >= 0 (positivity on grid)
|
|
187
|
+
psi[0] = psi0[0] (mass)
|
|
188
|
+
f(lam_m) psi = 0 (zero at left edge)
|
|
189
|
+
f(lam_p) psi = 0 (zero at right edge)
|
|
190
|
+
"""
|
|
191
|
+
|
|
192
|
+
lam_m, lam_p = support
|
|
193
|
+
|
|
194
|
+
# Objective and gradient
|
|
195
|
+
def fun(psi):
|
|
196
|
+
return 0.5 * numpy.dot(psi-psi0, psi-psi0)
|
|
197
|
+
|
|
198
|
+
def grad(psi):
|
|
199
|
+
return psi - psi0
|
|
200
|
+
|
|
201
|
+
# Constraints:
|
|
202
|
+
constraints = []
|
|
203
|
+
|
|
204
|
+
# Enforce positivity
|
|
205
|
+
constraints.append({'type': 'ineq',
|
|
206
|
+
'fun': lambda psi: density(grid, psi)})
|
|
207
|
+
|
|
208
|
+
# Enforce unit mass
|
|
209
|
+
constraints.append({
|
|
210
|
+
'type': 'eq',
|
|
211
|
+
'fun': lambda psi: numpy.trapz(density(grid, psi), grid) - 1.0
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
# Enforce zero at left edge
|
|
215
|
+
if beta <= 0.0 and beta > -0.5:
|
|
216
|
+
constraints.append({
|
|
217
|
+
'type': 'eq',
|
|
218
|
+
'fun': lambda psi: density(numpy.array([lam_m]), psi)[0]
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
# Enforce zero at right edge
|
|
222
|
+
if alpha <= 0.0 and alpha > -0.5:
|
|
223
|
+
constraints.append({
|
|
224
|
+
'type': 'eq',
|
|
225
|
+
'fun': lambda psi: density(numpy.array([lam_p]), psi)[0]
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
# Solve a small quadratic programming
|
|
229
|
+
res = minimize(fun, psi0, jac=grad,
|
|
230
|
+
constraints=constraints,
|
|
231
|
+
# method='trust-constr',
|
|
232
|
+
method='SLSQP',
|
|
233
|
+
options={'maxiter': 1000, 'ftol': 1e-9, 'eps': 1e-8})
|
|
234
|
+
|
|
235
|
+
psi = res.x
|
|
236
|
+
|
|
237
|
+
# Normalize first mode to unit mass
|
|
238
|
+
x = numpy.linspace(lam_m, lam_p, 1000)
|
|
239
|
+
rho = density(x, psi)
|
|
240
|
+
mass = numpy.trapezoid(rho, x)
|
|
241
|
+
psi[0] = psi[0] / mass
|
|
242
|
+
|
|
243
|
+
return psi
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
|
|
14
14
|
import numpy
|
|
15
15
|
from functools import partial
|
|
16
|
-
from
|
|
16
|
+
from .._util import resolve_complex_dtype, compute_eig
|
|
17
|
+
from ._density_util import kde, force_density
|
|
17
18
|
from ._jacobi import jacobi_sample_proj, jacobi_kernel_proj, jacobi_density, \
|
|
18
19
|
jacobi_stieltjes
|
|
19
20
|
from ._chebyshev import chebyshev_sample_proj, chebyshev_kernel_proj, \
|
|
@@ -74,16 +75,6 @@ class FreeForm(object):
|
|
|
74
75
|
Parameters for the :func:`supp` function can also be prescribed
|
|
75
76
|
here when ``support=None``.
|
|
76
77
|
|
|
77
|
-
Notes
|
|
78
|
-
-----
|
|
79
|
-
|
|
80
|
-
TBD
|
|
81
|
-
|
|
82
|
-
References
|
|
83
|
-
----------
|
|
84
|
-
|
|
85
|
-
.. [1] Reference.
|
|
86
|
-
|
|
87
78
|
Attributes
|
|
88
79
|
----------
|
|
89
80
|
|
|
@@ -749,16 +740,6 @@ class FreeForm(object):
|
|
|
749
740
|
density
|
|
750
741
|
hilbert
|
|
751
742
|
|
|
752
|
-
Notes
|
|
753
|
-
-----
|
|
754
|
-
|
|
755
|
-
Notes.
|
|
756
|
-
|
|
757
|
-
References
|
|
758
|
-
----------
|
|
759
|
-
|
|
760
|
-
.. [1] tbd
|
|
761
|
-
|
|
762
743
|
Examples
|
|
763
744
|
--------
|
|
764
745
|
|
|
@@ -973,16 +954,6 @@ class FreeForm(object):
|
|
|
973
954
|
density
|
|
974
955
|
stieltjes
|
|
975
956
|
|
|
976
|
-
Notes
|
|
977
|
-
-----
|
|
978
|
-
|
|
979
|
-
Work in progress.
|
|
980
|
-
|
|
981
|
-
References
|
|
982
|
-
----------
|
|
983
|
-
|
|
984
|
-
.. [1] tbd
|
|
985
|
-
|
|
986
957
|
Examples
|
|
987
958
|
--------
|
|
988
959
|
|
|
@@ -0,0 +1,13 @@
|
|
|
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
|
|
6
|
+
# under the terms of the license found in the LICENSE.txt file in the root
|
|
7
|
+
# directory of this source tree.
|
|
8
|
+
|
|
9
|
+
from .geometric_form import GeometricForm
|
|
10
|
+
# from ._elliptic_functions import ellipj
|
|
11
|
+
# from ._continuation_genus1 import mobius_z
|
|
12
|
+
|
|
13
|
+
__all__ = ['GeometricForm']
|
|
@@ -0,0 +1,175 @@
|
|
|
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
|
|
6
|
+
# under the terms of the license found in the LICENSE.txt file in the root
|
|
7
|
+
# directory of this source tree.
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# =======
|
|
11
|
+
# Imports
|
|
12
|
+
# =======
|
|
13
|
+
|
|
14
|
+
import numpy
|
|
15
|
+
|
|
16
|
+
__all__ = ['joukowski_z', 'joukowski_w', 'fit_pade', 'eval_pade',
|
|
17
|
+
'generate_pade']
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# ===========
|
|
21
|
+
# joukowski z
|
|
22
|
+
# ===========
|
|
23
|
+
|
|
24
|
+
def joukowski_z(w, a, b):
|
|
25
|
+
|
|
26
|
+
c = 0.5 * (a + b)
|
|
27
|
+
d = 0.5 * (b - a)
|
|
28
|
+
z = c + 0.5 * d * (w + 1.0 / w)
|
|
29
|
+
|
|
30
|
+
return z
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# ===========
|
|
34
|
+
# joukowski w
|
|
35
|
+
# ===========
|
|
36
|
+
|
|
37
|
+
def joukowski_w(z, a, b):
|
|
38
|
+
|
|
39
|
+
c = 0.5 * (a + b)
|
|
40
|
+
d = 0.5 * (b - a)
|
|
41
|
+
xi = (z - c) / d
|
|
42
|
+
s = numpy.sqrt(xi * xi - 1.0)
|
|
43
|
+
|
|
44
|
+
# Stabilize sqrt branch: make s have same sign as xi (helps continuity)
|
|
45
|
+
s = numpy.where(numpy.real(xi) < 0.0, -s, s)
|
|
46
|
+
|
|
47
|
+
w1 = xi + s
|
|
48
|
+
w2 = xi - s
|
|
49
|
+
w = numpy.where(numpy.abs(w1) >= 1.0, w1, w2)
|
|
50
|
+
|
|
51
|
+
return w
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# ========
|
|
55
|
+
# fit pade
|
|
56
|
+
# ========
|
|
57
|
+
|
|
58
|
+
def fit_pade(w, m1, deg_p=12, deg_q=12, ridge_lambda=0.0):
|
|
59
|
+
"""
|
|
60
|
+
Fit m1 on w-plane using Pade rational approximation.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
n_samples = m1.size
|
|
64
|
+
|
|
65
|
+
wp = numpy.ones((n_samples, deg_p + 1), dtype=complex)
|
|
66
|
+
for k in range(1, deg_p + 1):
|
|
67
|
+
wp[:, k] = wp[:, k - 1] * w
|
|
68
|
+
|
|
69
|
+
wq = numpy.ones((n_samples, deg_q + 1), dtype=complex)
|
|
70
|
+
for k in range(1, deg_q + 1):
|
|
71
|
+
wq[:, k] = wq[:, k - 1] * w
|
|
72
|
+
|
|
73
|
+
A = numpy.hstack([wp, -m1[:, None] * wq[:, 1:]])
|
|
74
|
+
bvec = m1
|
|
75
|
+
|
|
76
|
+
# Scale columns for better conditioning on LS
|
|
77
|
+
s = numpy.linalg.norm(A, axis=0)
|
|
78
|
+
s[s == 0] = 1.0
|
|
79
|
+
As = A / s[None, :]
|
|
80
|
+
|
|
81
|
+
if ridge_lambda is None:
|
|
82
|
+
ridge_lambda = 0.0
|
|
83
|
+
|
|
84
|
+
if ridge_lambda > 0.0:
|
|
85
|
+
# Scale ridge by average diagonal magnitude of AhA
|
|
86
|
+
# Since columns of As have unit norm, this is typically ~1.
|
|
87
|
+
alpha = ridge_lambda
|
|
88
|
+
|
|
89
|
+
# Solving augmented least square
|
|
90
|
+
n_coef = As.shape[1]
|
|
91
|
+
A_aug = numpy.vstack([As, numpy.sqrt(alpha) * numpy.eye(
|
|
92
|
+
n_coef, dtype=complex)])
|
|
93
|
+
b_aug = numpy.concatenate([bvec, numpy.zeros(n_coef, dtype=complex)])
|
|
94
|
+
|
|
95
|
+
coef, _, _, _ = numpy.linalg.lstsq(A_aug, b_aug, rcond=None)
|
|
96
|
+
else:
|
|
97
|
+
coef, _, _, _ = numpy.linalg.lstsq(As, bvec, rcond=None)
|
|
98
|
+
|
|
99
|
+
coef = coef / s
|
|
100
|
+
|
|
101
|
+
p = coef[:deg_p + 1]
|
|
102
|
+
q = numpy.zeros(deg_q + 1, dtype=complex)
|
|
103
|
+
q[0] = 1.0
|
|
104
|
+
q[1:] = coef[deg_p + 1:]
|
|
105
|
+
|
|
106
|
+
return p, q
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
# =========
|
|
110
|
+
# eval pade
|
|
111
|
+
# =========
|
|
112
|
+
|
|
113
|
+
def eval_pade(w, p, q):
|
|
114
|
+
|
|
115
|
+
num = numpy.zeros_like(w, dtype=complex)
|
|
116
|
+
den = numpy.zeros_like(w, dtype=complex)
|
|
117
|
+
|
|
118
|
+
for k in range(len(p) - 1, -1, -1):
|
|
119
|
+
num = num * w + p[k]
|
|
120
|
+
|
|
121
|
+
for k in range(len(q) - 1, -1, -1):
|
|
122
|
+
den = den * w + q[k]
|
|
123
|
+
|
|
124
|
+
return num / den
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
# =============
|
|
128
|
+
# generate pade
|
|
129
|
+
# =============
|
|
130
|
+
|
|
131
|
+
def generate_pade(m1_fn, a, b, deg_p=12, deg_q=12, n_samples=4096, r=1.2,
|
|
132
|
+
n_r=1, r_min=None, ridge_lambda=0.0):
|
|
133
|
+
|
|
134
|
+
if r_min is None:
|
|
135
|
+
r_min = 1.0 + 0.05 * (r - 1.0) if r > 1.0 else 1.0
|
|
136
|
+
|
|
137
|
+
if n_r is None or n_r < 1:
|
|
138
|
+
n_r = 1
|
|
139
|
+
|
|
140
|
+
if n_samples % 2 != 0:
|
|
141
|
+
raise ValueError('n_samples should be even.')
|
|
142
|
+
|
|
143
|
+
if n_r == 1:
|
|
144
|
+
rs = numpy.array([r], dtype=float)
|
|
145
|
+
else:
|
|
146
|
+
rs = numpy.linspace(r_min, r, n_r)
|
|
147
|
+
|
|
148
|
+
W_list = []
|
|
149
|
+
M_list = []
|
|
150
|
+
|
|
151
|
+
n_half = n_samples // 2
|
|
152
|
+
|
|
153
|
+
for r_i in rs:
|
|
154
|
+
|
|
155
|
+
# Generate sample points along theta
|
|
156
|
+
theta = numpy.pi * (numpy.arange(n_half) + 0.5) / n_half
|
|
157
|
+
w = r_i * numpy.exp(1j * theta)
|
|
158
|
+
z = joukowski_z(w, a, b)
|
|
159
|
+
m1 = m1_fn(z)
|
|
160
|
+
|
|
161
|
+
W_list.append(w)
|
|
162
|
+
M_list.append(m1)
|
|
163
|
+
|
|
164
|
+
# Add conjugate points which enforces Schwarz reflection
|
|
165
|
+
W_list.append(numpy.conjugate(w))
|
|
166
|
+
M_list.append(numpy.conjugate(m1))
|
|
167
|
+
|
|
168
|
+
w_all = numpy.concatenate(W_list)
|
|
169
|
+
m1_all = numpy.concatenate(M_list)
|
|
170
|
+
|
|
171
|
+
# Fit on the sample data from m1
|
|
172
|
+
p, q = fit_pade(w_all, m1_all, deg_p=deg_p, deg_q=deg_q,
|
|
173
|
+
ridge_lambda=ridge_lambda)
|
|
174
|
+
|
|
175
|
+
return p, q
|