freealg 0.0.2__py3-none-any.whl → 0.1.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/__version__.py +1 -1
- freealg/_decompress.py +136 -0
- freealg/_jacobi.py +0 -2
- freealg/_pade.py +330 -6
- freealg/_plot_util.py +43 -53
- freealg/_sample.py +85 -0
- freealg/distributions/__init__.py +4 -4
- freealg/distributions/kesten_mckay.py +559 -0
- freealg/distributions/marchenko_pastur.py +4 -3
- freealg/distributions/wachter.py +568 -0
- freealg/distributions/wigner.py +552 -0
- freealg/freeform.py +252 -52
- {freealg-0.0.2.dist-info → freealg-0.1.0.dist-info}/METADATA +2 -1
- freealg-0.1.0.dist-info/RECORD +21 -0
- freealg-0.0.2.dist-info/RECORD +0 -16
- {freealg-0.0.2.dist-info → freealg-0.1.0.dist-info}/WHEEL +0 -0
- {freealg-0.0.2.dist-info → freealg-0.1.0.dist-info}/licenses/LICENSE.txt +0 -0
- {freealg-0.0.2.dist-info → freealg-0.1.0.dist-info}/top_level.txt +0 -0
freealg/__version__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.0
|
|
1
|
+
__version__ = "0.1.0"
|
freealg/_decompress.py
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# SPDX-License-Identifier: BSD-3-Clause
|
|
2
|
+
# SPDX-FileType: SOURCE
|
|
3
|
+
#
|
|
4
|
+
# This program is free software: you can redistribute it and/or modify it under
|
|
5
|
+
# the terms of the license found in the LICENSE.txt file in the root directory
|
|
6
|
+
# of this source tree.
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# =======
|
|
10
|
+
# Imports
|
|
11
|
+
# =======
|
|
12
|
+
|
|
13
|
+
import numpy
|
|
14
|
+
|
|
15
|
+
__all__ = ['decompress']
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# ==========
|
|
19
|
+
# decompress
|
|
20
|
+
# ==========
|
|
21
|
+
|
|
22
|
+
def decompress(matrix, size, x=None, delta=1e-4, iterations=500, step_size=0.1,
|
|
23
|
+
tolerance=1e-4):
|
|
24
|
+
"""
|
|
25
|
+
Free decompression of spectral density.
|
|
26
|
+
|
|
27
|
+
Parameters
|
|
28
|
+
----------
|
|
29
|
+
|
|
30
|
+
matrix : FreeForm
|
|
31
|
+
The initial matrix to be decompressed
|
|
32
|
+
|
|
33
|
+
size : int
|
|
34
|
+
Size of the decompressed matrix.
|
|
35
|
+
|
|
36
|
+
x : numpy.array, default=None
|
|
37
|
+
Positions where density to be evaluated at. If `None`, an interval
|
|
38
|
+
slightly larger than the support interval will be used.
|
|
39
|
+
|
|
40
|
+
delta: float, default=1e-4
|
|
41
|
+
Size of the perturbation into the upper half plane for Plemelj's
|
|
42
|
+
formula.
|
|
43
|
+
|
|
44
|
+
iterations: int, default=500
|
|
45
|
+
Maximum number of Newton iterations.
|
|
46
|
+
|
|
47
|
+
step_size: float, default=0.1
|
|
48
|
+
Step size for Newton iterations.
|
|
49
|
+
|
|
50
|
+
tolerance: float, default=1e-4
|
|
51
|
+
Tolerance for the solution obtained by the Newton solver. Also
|
|
52
|
+
used for the finite difference approximation to the derivative.
|
|
53
|
+
|
|
54
|
+
Returns
|
|
55
|
+
-------
|
|
56
|
+
|
|
57
|
+
rho : numpy.array
|
|
58
|
+
Spectral density
|
|
59
|
+
|
|
60
|
+
See Also
|
|
61
|
+
--------
|
|
62
|
+
|
|
63
|
+
density
|
|
64
|
+
stieltjes
|
|
65
|
+
|
|
66
|
+
Notes
|
|
67
|
+
-----
|
|
68
|
+
|
|
69
|
+
Work in progress.
|
|
70
|
+
|
|
71
|
+
References
|
|
72
|
+
----------
|
|
73
|
+
|
|
74
|
+
.. [1] tbd
|
|
75
|
+
|
|
76
|
+
Examples
|
|
77
|
+
--------
|
|
78
|
+
|
|
79
|
+
.. code-block:: python
|
|
80
|
+
|
|
81
|
+
>>> from freealg import FreeForm
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
alpha = size / matrix.n
|
|
85
|
+
m = matrix._eval_stieltjes
|
|
86
|
+
# Lower and upper bound on new support
|
|
87
|
+
hilb_lb = (1 / m(matrix.lam_m + delta * 1j)[1]).real
|
|
88
|
+
hilb_ub = (1 / m(matrix.lam_p + delta * 1j)[1]).real
|
|
89
|
+
lb = matrix.lam_m - (alpha - 1) * hilb_lb
|
|
90
|
+
ub = matrix.lam_p - (alpha - 1) * hilb_ub
|
|
91
|
+
|
|
92
|
+
# Create x if not given
|
|
93
|
+
if x is None:
|
|
94
|
+
radius = 0.5 * (ub - lb)
|
|
95
|
+
center = 0.5 * (ub + lb)
|
|
96
|
+
scale = 1.25
|
|
97
|
+
x_min = numpy.floor(center - radius * scale)
|
|
98
|
+
x_max = numpy.ceil(center + radius * scale)
|
|
99
|
+
x = numpy.linspace(x_min, x_max, 500)
|
|
100
|
+
|
|
101
|
+
def _char_z(z):
|
|
102
|
+
return z + (1 / m(z)[1]) * (1 - alpha)
|
|
103
|
+
|
|
104
|
+
# Ensure that input is an array
|
|
105
|
+
x = numpy.asarray(x)
|
|
106
|
+
|
|
107
|
+
target = x + delta * 1j
|
|
108
|
+
|
|
109
|
+
z = numpy.full(target.shape, numpy.mean(matrix.support) - .1j,
|
|
110
|
+
dtype=numpy.complex128)
|
|
111
|
+
|
|
112
|
+
# Broken Newton steps can produce a lot of warnings. Removing them
|
|
113
|
+
# for now.
|
|
114
|
+
with numpy.errstate(all='ignore'):
|
|
115
|
+
for _ in range(iterations):
|
|
116
|
+
objective = _char_z(z) - target
|
|
117
|
+
mask = numpy.abs(objective) >= tolerance
|
|
118
|
+
if not numpy.any(mask):
|
|
119
|
+
break
|
|
120
|
+
z_m = z[mask]
|
|
121
|
+
|
|
122
|
+
# Perform finite difference approximation
|
|
123
|
+
dfdz = _char_z(z_m+tolerance) - _char_z(z_m-tolerance)
|
|
124
|
+
dfdz /= 2*tolerance
|
|
125
|
+
dfdz[dfdz == 0] = 1.0
|
|
126
|
+
|
|
127
|
+
# Perform Newton step
|
|
128
|
+
z[mask] = z_m - step_size * objective[mask] / dfdz
|
|
129
|
+
|
|
130
|
+
# Plemelj's formula
|
|
131
|
+
char_s = m(z)[1] / alpha
|
|
132
|
+
rho = numpy.maximum(0, char_s.imag / numpy.pi)
|
|
133
|
+
rho[numpy.isnan(rho) | numpy.isinf(rho)] = 0
|
|
134
|
+
rho = rho.reshape(*x.shape)
|
|
135
|
+
|
|
136
|
+
return rho, x, (lb, ub)
|
freealg/_jacobi.py
CHANGED
|
@@ -174,9 +174,7 @@ def jacobi_stieltjes(z, psi, support, alpha=0.0, beta=0.0, n_base=40):
|
|
|
174
174
|
integrand = w_nodes * P_k_nodes # (n_quad,)
|
|
175
175
|
|
|
176
176
|
# Broadcast over z: shape (n_quad, ...) / ...
|
|
177
|
-
# diff = u_z[None, ...] - t_nodes[:, None] # (n_quad, ...)
|
|
178
177
|
diff = u_z[None, ...] - t_nodes[:, None, None] # (n_quad, Ny, Nx)
|
|
179
|
-
# m_k = (integrand[:, None] / diff).sum(axis=0) # shape like z
|
|
180
178
|
m_k = (integrand[:, None, None] / diff).sum(axis=0)
|
|
181
179
|
|
|
182
180
|
# Accumulate with factor 2/span
|
freealg/_pade.py
CHANGED
|
@@ -12,18 +12,342 @@
|
|
|
12
12
|
# =======
|
|
13
13
|
|
|
14
14
|
import numpy
|
|
15
|
+
from numpy.linalg import lstsq
|
|
15
16
|
from itertools import product
|
|
16
17
|
from scipy.optimize import least_squares, differential_evolution
|
|
17
18
|
|
|
18
19
|
__all__ = ['fit_pade', 'eval_pade']
|
|
19
20
|
|
|
20
21
|
|
|
22
|
+
# =============
|
|
23
|
+
# default poles
|
|
24
|
+
# =============
|
|
25
|
+
|
|
26
|
+
def _default_poles(q, lam_m, lam_p, safety=1.0, odd_side='left'):
|
|
27
|
+
"""
|
|
28
|
+
Generate q real poles outside [lam_m, lam_p].
|
|
29
|
+
|
|
30
|
+
• even q : q/2 on each side (Chebyshev-like layout)
|
|
31
|
+
• odd q : (q+1)/2 on the *left*, (q–1)/2 on the right
|
|
32
|
+
so q=1 => single pole on whichever side `odd_side` says.
|
|
33
|
+
|
|
34
|
+
safety >= 1: 1, then poles start half an interval away; >1 pushes them
|
|
35
|
+
farther.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
if q == 0:
|
|
39
|
+
return numpy.empty(0)
|
|
40
|
+
|
|
41
|
+
Delta = 0.5 * (lam_p - lam_m)
|
|
42
|
+
|
|
43
|
+
# Decide how many poles on each side. m_L and m_R determine how many poles
|
|
44
|
+
# to be on the left and right of the support interval.
|
|
45
|
+
if q % 2 == 0:
|
|
46
|
+
m_L = m_R = q // 2
|
|
47
|
+
else:
|
|
48
|
+
if odd_side == 'left':
|
|
49
|
+
m_L = (q + 1) // 2
|
|
50
|
+
m_R = q // 2
|
|
51
|
+
else:
|
|
52
|
+
m_L = q // 2
|
|
53
|
+
m_R = (q + 1) // 2
|
|
54
|
+
|
|
55
|
+
# Chebyshev-extrema offsets (all positive)
|
|
56
|
+
kL = numpy.arange(m_L)
|
|
57
|
+
tL = (2 * kL + 1) * numpy.pi / (2 * m_L)
|
|
58
|
+
offsL = safety * Delta * (1 + numpy.cos(tL))
|
|
59
|
+
|
|
60
|
+
kR = numpy.arange(m_R)
|
|
61
|
+
tR = (2 * kR + 1) * numpy.pi / (2 * m_R + (m_R == 0))
|
|
62
|
+
offsR = safety * Delta * (1 + numpy.cos(tR))
|
|
63
|
+
|
|
64
|
+
left = lam_m - offsL
|
|
65
|
+
right = lam_p + offsR
|
|
66
|
+
|
|
67
|
+
return numpy.sort(numpy.concatenate([left, right]))
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# ============
|
|
71
|
+
# encode poles
|
|
72
|
+
# ============
|
|
73
|
+
|
|
74
|
+
def _encode_poles(a, lam_m, lam_p):
|
|
75
|
+
"""
|
|
76
|
+
Map real pole a_j → unconstrained s_j,
|
|
77
|
+
so that the default left-of-interval pole stays left.
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
# half-width of the interval
|
|
81
|
+
d = 0.5 * (lam_p - lam_m)
|
|
82
|
+
# if a < lam_m, we want s ≥ 0; if a > lam_p, s < 0
|
|
83
|
+
return numpy.where(
|
|
84
|
+
a < lam_m,
|
|
85
|
+
numpy.log((lam_m - a) / d), # zero at a = lam_m - d
|
|
86
|
+
-numpy.log((a - lam_p) / d) # zero at a = lam_p + d
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# ============
|
|
91
|
+
# decode poles
|
|
92
|
+
# ============
|
|
93
|
+
|
|
94
|
+
def _decode_poles(s, lam_m, lam_p):
|
|
95
|
+
"""
|
|
96
|
+
Inverse map s_j → real pole a_j outside the interval.
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
d = 0.5 * (lam_p - lam_m)
|
|
100
|
+
return numpy.where(
|
|
101
|
+
s >= 0,
|
|
102
|
+
lam_m - d * numpy.exp(s), # maps s=0 to a=lam_m−d (left)
|
|
103
|
+
lam_p + d * numpy.exp(-s) # maps s=0 to a=lam_p+d (right)
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
# ========
|
|
108
|
+
# inner ls
|
|
109
|
+
# ========
|
|
110
|
+
|
|
111
|
+
# def _inner_ls(x, f, poles): # TEST
|
|
112
|
+
def _inner_ls(x, f, poles, p=1):
|
|
113
|
+
"""
|
|
114
|
+
This is the inner least square (blazing fast).
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
if poles.size == 0 and p == -1:
|
|
118
|
+
return 0.0, 0.0, numpy.empty(0)
|
|
119
|
+
|
|
120
|
+
if poles.size == 0: # q = 0
|
|
121
|
+
# A = numpy.column_stack((numpy.ones_like(x), x))
|
|
122
|
+
cols = [numpy.ones_like(x)] if p >= 0 else []
|
|
123
|
+
if p == 1:
|
|
124
|
+
cols.append(x)
|
|
125
|
+
A = numpy.column_stack(cols)
|
|
126
|
+
# ---
|
|
127
|
+
theta, *_ = lstsq(A, f, rcond=None)
|
|
128
|
+
# c, D = theta # TEST
|
|
129
|
+
if p == -1:
|
|
130
|
+
c = 0.0
|
|
131
|
+
D = 0.0
|
|
132
|
+
resid = numpy.empty(0)
|
|
133
|
+
elif p == 0:
|
|
134
|
+
c = theta[0]
|
|
135
|
+
D = 0.0
|
|
136
|
+
resid = numpy.empty(0)
|
|
137
|
+
else: # p == 1
|
|
138
|
+
c, D = theta
|
|
139
|
+
resid = numpy.empty(0)
|
|
140
|
+
else:
|
|
141
|
+
# phi = 1.0 / (x[:, None] - poles[None, :])
|
|
142
|
+
# # A = numpy.column_stack((numpy.ones_like(x), x, phi)) # TEST
|
|
143
|
+
# # theta, *_ = lstsq(A, f, rcond=None)
|
|
144
|
+
# # c, D, resid = theta[0], theta[1], theta[2:]
|
|
145
|
+
# phi = 1.0 / (x[:, None] - poles[None, :])
|
|
146
|
+
# cols = [numpy.ones_like(x)] if p >= 0 else []
|
|
147
|
+
# if p == 1:
|
|
148
|
+
# cols.append(x)
|
|
149
|
+
# cols.append(phi)
|
|
150
|
+
# A = numpy.column_stack(cols)
|
|
151
|
+
# theta, *_ = lstsq(A, f, rcond=None)
|
|
152
|
+
# if p == -1:
|
|
153
|
+
# c = 0.0
|
|
154
|
+
# D = 0.0
|
|
155
|
+
# resid = theta
|
|
156
|
+
# elif p == 0:
|
|
157
|
+
# c = theta[0]
|
|
158
|
+
# D = 0.0
|
|
159
|
+
# resid = theta[1:]
|
|
160
|
+
# else: # p == 1
|
|
161
|
+
# c = theta[0]
|
|
162
|
+
# D = theta[1]
|
|
163
|
+
# resid = theta[2:]
|
|
164
|
+
|
|
165
|
+
phi = 1.0 / (x[:, None] - poles[None, :])
|
|
166
|
+
cols = [numpy.ones_like(x)] if p >= 0 else []
|
|
167
|
+
if p == 1:
|
|
168
|
+
cols.append(x)
|
|
169
|
+
cols.append(phi)
|
|
170
|
+
|
|
171
|
+
A = numpy.column_stack(cols)
|
|
172
|
+
theta, *_ = lstsq(A, f, rcond=None)
|
|
173
|
+
|
|
174
|
+
if p == -1:
|
|
175
|
+
c, D, resid = 0.0, 0.0, theta
|
|
176
|
+
elif p == 0:
|
|
177
|
+
c, D, resid = theta[0], 0.0, theta[1:]
|
|
178
|
+
else: # p == 1
|
|
179
|
+
c, D, resid = theta[0], theta[1], theta[2:]
|
|
180
|
+
|
|
181
|
+
return c, D, resid
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
# =============
|
|
185
|
+
# eval rational
|
|
186
|
+
# =============
|
|
187
|
+
|
|
188
|
+
def _eval_rational(z, c, D, poles, resid):
|
|
189
|
+
"""
|
|
190
|
+
"""
|
|
191
|
+
|
|
192
|
+
# z = z[:, None]
|
|
193
|
+
# if poles.size == 0:
|
|
194
|
+
# term = 0.0
|
|
195
|
+
# else:
|
|
196
|
+
# term = numpy.sum(resid / (z - poles), axis=1)
|
|
197
|
+
#
|
|
198
|
+
# return c + D * z.ravel() + term
|
|
199
|
+
|
|
200
|
+
# ensure z is a 1-D array
|
|
201
|
+
z = numpy.asarray(z)
|
|
202
|
+
z_col = z[:, None]
|
|
203
|
+
|
|
204
|
+
if poles.size == 0:
|
|
205
|
+
term = 0.0
|
|
206
|
+
else:
|
|
207
|
+
term = numpy.sum(resid / (z_col - poles[None, :]), axis=1)
|
|
208
|
+
|
|
209
|
+
return c + D * z + term
|
|
210
|
+
|
|
211
|
+
|
|
21
212
|
# ========
|
|
22
213
|
# fit pade
|
|
23
214
|
# ========
|
|
24
215
|
|
|
25
|
-
def fit_pade(x, f, lam_m, lam_p, p, q
|
|
26
|
-
|
|
216
|
+
def fit_pade(x, f, lam_m, lam_p, p=1, q=2, odd_side='left', safety=1.0,
|
|
217
|
+
max_outer=40, xtol=1e-12, ftol=1e-12, optimizer='ls', verbose=0):
|
|
218
|
+
"""
|
|
219
|
+
This is the outer optimiser.
|
|
220
|
+
|
|
221
|
+
Fits G(x) = (p>=1 ? c : 0) + (p==1 ? D x : 0) + sum r_j/(x - a_j) # TEST
|
|
222
|
+
"""
|
|
223
|
+
|
|
224
|
+
# Checks
|
|
225
|
+
if not (odd_side in ['left', 'right']):
|
|
226
|
+
raise ValueError('"odd_side" can only be "left" or "right".')
|
|
227
|
+
|
|
228
|
+
if not (p in [-1, 0, 1]):
|
|
229
|
+
raise ValueError('"pade_p" can only be -1, 0, or 1.')
|
|
230
|
+
|
|
231
|
+
x = numpy.asarray(x, float)
|
|
232
|
+
f = numpy.asarray(f, float)
|
|
233
|
+
|
|
234
|
+
poles0 = _default_poles(q, lam_m, lam_p, safety=safety, odd_side=odd_side)
|
|
235
|
+
# if q == 0: # nothing to optimise
|
|
236
|
+
if q == 0 and p <= 0:
|
|
237
|
+
# c, D, resid = _inner_ls(x, f, poles0) # TEST
|
|
238
|
+
c, D, resid = _inner_ls(x, f, poles0, p)
|
|
239
|
+
pade_sol = {
|
|
240
|
+
'c': c, 'D': D, 'poles': poles0, 'resid': resid,
|
|
241
|
+
'outer_iters': 0
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return pade_sol
|
|
245
|
+
|
|
246
|
+
s0 = _encode_poles(poles0, lam_m, lam_p)
|
|
247
|
+
|
|
248
|
+
# --------
|
|
249
|
+
# residual
|
|
250
|
+
# --------
|
|
251
|
+
|
|
252
|
+
# def residual(s): # TEST
|
|
253
|
+
def residual(s, p=p):
|
|
254
|
+
poles = _decode_poles(s, lam_m, lam_p)
|
|
255
|
+
# c, D, resid = _inner_ls(x, f, poles) # TEST
|
|
256
|
+
c, D, resid = _inner_ls(x, f, poles, p)
|
|
257
|
+
return _eval_rational(x, c, D, poles, resid) - f
|
|
258
|
+
|
|
259
|
+
# ----------------
|
|
260
|
+
|
|
261
|
+
# Optimizer
|
|
262
|
+
if optimizer == 'ls':
|
|
263
|
+
# scale = numpy.maximum(1.0, numpy.abs(s0))
|
|
264
|
+
res = least_squares(residual, s0,
|
|
265
|
+
method='trf',
|
|
266
|
+
# method='lm',
|
|
267
|
+
# x_scale=scale,
|
|
268
|
+
max_nfev=max_outer, xtol=xtol, ftol=ftol,
|
|
269
|
+
verbose=verbose)
|
|
270
|
+
|
|
271
|
+
elif optimizer == 'de':
|
|
272
|
+
|
|
273
|
+
# Bounds
|
|
274
|
+
# span = lam_p - lam_m
|
|
275
|
+
# B = 3.0 # multiples of span
|
|
276
|
+
# L = numpy.log(B * span)
|
|
277
|
+
# bounds = [(-L, L)] * len(s0)
|
|
278
|
+
|
|
279
|
+
d = 0.5*(lam_p - lam_m)
|
|
280
|
+
# the minimum factor so that lam_m - d*exp(s)=0 is exp(s)=lam_m/d
|
|
281
|
+
min_factor = lam_m/d
|
|
282
|
+
B = max(10.0, min_factor*10.0)
|
|
283
|
+
L = numpy.log(B)
|
|
284
|
+
bounds = [(-L, L)] * len(s0)
|
|
285
|
+
|
|
286
|
+
# Global stage
|
|
287
|
+
glob = differential_evolution(lambda s: numpy.sum(residual(s)**2),
|
|
288
|
+
bounds, maxiter=50, popsize=10,
|
|
289
|
+
polish=False)
|
|
290
|
+
|
|
291
|
+
# local polish
|
|
292
|
+
res = least_squares(
|
|
293
|
+
residual, glob.x,
|
|
294
|
+
method='lm',
|
|
295
|
+
max_nfev=max_outer, xtol=xtol, ftol=ftol,
|
|
296
|
+
verbose=verbose)
|
|
297
|
+
|
|
298
|
+
else:
|
|
299
|
+
raise RuntimeError('"optimizer" is invalid.')
|
|
300
|
+
|
|
301
|
+
poles = _decode_poles(res.x, lam_m, lam_p)
|
|
302
|
+
# c, D, resid = _inner_ls(x, f, poles) # TEST
|
|
303
|
+
c, D, resid = _inner_ls(x, f, poles, p)
|
|
304
|
+
|
|
305
|
+
pade_sol = {
|
|
306
|
+
'c': c, 'D': D, 'poles': poles, 'resid': resid,
|
|
307
|
+
'outer_iters': res.nfev
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return pade_sol
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
# =========
|
|
314
|
+
# eval pade
|
|
315
|
+
# =========
|
|
316
|
+
|
|
317
|
+
def eval_pade(z, pade_sol):
|
|
318
|
+
"""
|
|
319
|
+
"""
|
|
320
|
+
|
|
321
|
+
# z_arr = numpy.asanyarray(z) # shape=(M,N)
|
|
322
|
+
# flat = z_arr.ravel() # shape=(M·N,)
|
|
323
|
+
# c, D = pade_sol['c'], pade_sol['D']
|
|
324
|
+
# poles = pade_sol['poles']
|
|
325
|
+
# resid = pade_sol['resid']
|
|
326
|
+
#
|
|
327
|
+
# # _eval_rational takes a 1-D array of z's and returns 1-D outputs
|
|
328
|
+
# flat_out = _eval_rational(flat, c, D, poles, resid)
|
|
329
|
+
#
|
|
330
|
+
# # restore the original shape
|
|
331
|
+
# out = flat_out.reshape(z_arr.shape) # shape=(M,N)
|
|
332
|
+
#
|
|
333
|
+
# return out
|
|
334
|
+
|
|
335
|
+
z = numpy.asanyarray(z) # complex or real, any shape
|
|
336
|
+
c, D = pade_sol['c'], pade_sol['D']
|
|
337
|
+
poles, resid = pade_sol['poles'], pade_sol['resid']
|
|
338
|
+
|
|
339
|
+
out = c + D*z
|
|
340
|
+
for bj, rj in zip(poles, resid):
|
|
341
|
+
out += rj/(z - bj) # each is an (N,) op, no N×q temp
|
|
342
|
+
return out
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
# ============
|
|
346
|
+
# fit pade old
|
|
347
|
+
# ============
|
|
348
|
+
|
|
349
|
+
def fit_pade_old(x, f, lam_m, lam_p, p, q, delta=1e-8, B=numpy.inf,
|
|
350
|
+
S=numpy.inf, B_default=10.0, S_factor=2.0, maxiter_de=200):
|
|
27
351
|
"""
|
|
28
352
|
Fit a [p/q] rational P/Q of the form:
|
|
29
353
|
P(x) = s * prod_{i=0..p-1}(x - a_i)
|
|
@@ -125,11 +449,11 @@ def fit_pade(x, f, lam_m, lam_p, p, q, delta=1e-8, B=numpy.inf, S=numpy.inf,
|
|
|
125
449
|
}
|
|
126
450
|
|
|
127
451
|
|
|
128
|
-
#
|
|
129
|
-
# eval pade
|
|
130
|
-
#
|
|
452
|
+
# =============
|
|
453
|
+
# eval pade old
|
|
454
|
+
# =============
|
|
131
455
|
|
|
132
|
-
def
|
|
456
|
+
def eval_pade_old(z, s, a, b):
|
|
133
457
|
"""
|
|
134
458
|
"""
|
|
135
459
|
|
freealg/_plot_util.py
CHANGED
|
@@ -19,28 +19,51 @@ import colorsys
|
|
|
19
19
|
import matplotlib.ticker as ticker
|
|
20
20
|
import matplotlib.gridspec as gridspec
|
|
21
21
|
|
|
22
|
-
__all__ = ['
|
|
23
|
-
'plot_stieltjes_on_disk'
|
|
22
|
+
__all__ = ['plot_fit', 'plot_density', 'plot_hilbert', 'plot_stieltjes',
|
|
23
|
+
'plot_stieltjes_on_disk']
|
|
24
24
|
|
|
25
25
|
|
|
26
|
-
#
|
|
27
|
-
# plot
|
|
28
|
-
#
|
|
26
|
+
# ========
|
|
27
|
+
# plot fit
|
|
28
|
+
# ========
|
|
29
29
|
|
|
30
|
-
def
|
|
30
|
+
def plot_fit(psi, x_supp, g_supp, g_supp_approx, support, latex=False,
|
|
31
|
+
save=False):
|
|
31
32
|
"""
|
|
32
33
|
"""
|
|
33
34
|
|
|
34
35
|
with texplot.theme(use_latex=latex):
|
|
35
36
|
|
|
36
|
-
fig, ax = plt.subplots(figsize=(
|
|
37
|
+
fig, ax = plt.subplots(figsize=(9, 3), ncols=2)
|
|
38
|
+
|
|
39
|
+
# Plot psi
|
|
37
40
|
n = numpy.arange(1, 1+psi.size)
|
|
38
|
-
ax.plot(n, psi**2, '-o', markersize=3, color='black')
|
|
39
|
-
ax.set_xlim([n[0]-1e-3, n[-1]+1e-3])
|
|
40
|
-
ax.set_xlabel(r'$k$')
|
|
41
|
-
ax.set_ylabel(r'$\vert \psi_k \vert^2$')
|
|
42
|
-
ax.set_title('Spectral Energy per Mode')
|
|
43
|
-
ax.set_yscale('log')
|
|
41
|
+
ax[0].plot(n, psi**2, '-o', markersize=3, color='black')
|
|
42
|
+
ax[0].set_xlim([n[0]-1e-3, n[-1]+1e-3])
|
|
43
|
+
ax[0].set_xlabel(r'$k$')
|
|
44
|
+
ax[0].set_ylabel(r'$\vert \psi_k \vert^2$')
|
|
45
|
+
ax[0].set_title('Spectral Energy per Mode')
|
|
46
|
+
ax[0].set_yscale('log')
|
|
47
|
+
|
|
48
|
+
# Plot pade
|
|
49
|
+
lam_m, lam_p = support
|
|
50
|
+
g_supp_min = numpy.min(g_supp)
|
|
51
|
+
g_supp_max = numpy.max(g_supp)
|
|
52
|
+
g_supp_dif = g_supp_max - g_supp_min
|
|
53
|
+
g_min = g_supp_min - g_supp_dif * 1.1
|
|
54
|
+
g_max = g_supp_max + g_supp_dif * 1.1
|
|
55
|
+
|
|
56
|
+
ax[1].plot(x_supp, g_supp, color='firebrick',
|
|
57
|
+
label=r'$2 \pi \times $ Hilbert Transform')
|
|
58
|
+
ax[1].plot(x_supp, g_supp_approx, color='black', label='Pade estimate')
|
|
59
|
+
ax[1].legend(fontsize='small')
|
|
60
|
+
ax[1].set_xlim([lam_m, lam_p])
|
|
61
|
+
ax[1].set_ylim([g_min, g_max])
|
|
62
|
+
ax[1].set_title('Approximation of Glue Function')
|
|
63
|
+
ax[1].set_xlabel(r'$x$')
|
|
64
|
+
ax[1].set_ylabel(r'$G(x)$')
|
|
65
|
+
|
|
66
|
+
plt.tight_layout()
|
|
44
67
|
|
|
45
68
|
# Save
|
|
46
69
|
if save is False:
|
|
@@ -51,7 +74,7 @@ def plot_coeff(psi, latex=False, save=False):
|
|
|
51
74
|
if isinstance(save, str):
|
|
52
75
|
save_filename = save
|
|
53
76
|
else:
|
|
54
|
-
save_filename = '
|
|
77
|
+
save_filename = 'fit.pdf'
|
|
55
78
|
|
|
56
79
|
texplot.show_or_save_plot(plt, default_filename=save_filename,
|
|
57
80
|
transparent_background=True, dpi=400,
|
|
@@ -349,6 +372,12 @@ def plot_stieltjes_on_disk(r, t, m1_D, m2_D, support, latex=False, save=False):
|
|
|
349
372
|
lam_m_z = (lam_m - 1j) / (lam_m + 1j)
|
|
350
373
|
theta_p = numpy.angle(lam_p_z)
|
|
351
374
|
theta_n = numpy.angle(lam_m_z)
|
|
375
|
+
|
|
376
|
+
if theta_n < 0:
|
|
377
|
+
theta_n += 2.0 * numpy.pi
|
|
378
|
+
if theta_p < 0:
|
|
379
|
+
theta_p += 2.0 * numpy.pi
|
|
380
|
+
|
|
352
381
|
theta_branch = numpy.linspace(theta_n, theta_p, 100)
|
|
353
382
|
theta_alt_branch = numpy.linspace(theta_p, theta_n + 2*numpy.pi, 100)
|
|
354
383
|
r_branch = numpy.ones_like(theta_branch)
|
|
@@ -462,45 +491,6 @@ def plot_stieltjes_on_disk(r, t, m1_D, m2_D, support, latex=False, save=False):
|
|
|
462
491
|
show_and_save=save_status, verbose=True)
|
|
463
492
|
|
|
464
493
|
|
|
465
|
-
# =============
|
|
466
|
-
# plot glue fit
|
|
467
|
-
# =============
|
|
468
|
-
|
|
469
|
-
def plot_glue_fit(x_supp, g_supp, g_supp_approx, support, latex=False,
|
|
470
|
-
save=False):
|
|
471
|
-
"""
|
|
472
|
-
"""
|
|
473
|
-
|
|
474
|
-
with texplot.theme(use_latex=latex):
|
|
475
|
-
|
|
476
|
-
fig, ax = plt.subplots(figsize=(6, 3))
|
|
477
|
-
|
|
478
|
-
lam_m, lam_p = support
|
|
479
|
-
ax.plot(x_supp, g_supp, color='black', label='Glue target')
|
|
480
|
-
ax.plot(x_supp, g_supp_approx, color='firebrick',
|
|
481
|
-
label='Glue estimate')
|
|
482
|
-
ax.legend(fontsize='small')
|
|
483
|
-
ax.set_xlim([lam_m, lam_p])
|
|
484
|
-
ax.set_title('Approximation of Glue function on real axis')
|
|
485
|
-
ax.set_xlabel(r'$x$')
|
|
486
|
-
ax.set_ylabel(r'$G(x)$')
|
|
487
|
-
|
|
488
|
-
# Save
|
|
489
|
-
if save is False:
|
|
490
|
-
save_status = False
|
|
491
|
-
save_filename = ''
|
|
492
|
-
else:
|
|
493
|
-
save_status = True
|
|
494
|
-
if isinstance(save, str):
|
|
495
|
-
save_filename = save
|
|
496
|
-
else:
|
|
497
|
-
save_filename = 'glue_fit.pdf'
|
|
498
|
-
|
|
499
|
-
texplot.show_or_save_plot(plt, default_filename=save_filename,
|
|
500
|
-
transparent_background=True, dpi=400,
|
|
501
|
-
show_and_save=save_status, verbose=True)
|
|
502
|
-
|
|
503
|
-
|
|
504
494
|
# ============
|
|
505
495
|
# plot samples
|
|
506
496
|
# ============
|