sparse-ir 1.1.6__py3-none-any.whl → 2.0.0a2__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.
- sparse_ir/__init__.py +33 -15
- sparse_ir/abstract.py +70 -82
- sparse_ir/augment.py +27 -17
- sparse_ir/basis.py +130 -239
- sparse_ir/basis_set.py +99 -57
- sparse_ir/dlr.py +131 -88
- sparse_ir/kernel.py +54 -477
- sparse_ir/poly.py +221 -476
- sparse_ir/sampling.py +260 -371
- sparse_ir/sve.py +56 -358
- sparse_ir-2.0.0a2.dist-info/METADATA +23 -0
- sparse_ir-2.0.0a2.dist-info/RECORD +16 -0
- {sparse_ir-1.1.6.dist-info → sparse_ir-2.0.0a2.dist-info}/WHEEL +1 -1
- sparse_ir/_gauss.py +0 -260
- sparse_ir/_roots.py +0 -140
- sparse_ir/adapter.py +0 -267
- sparse_ir/svd.py +0 -102
- sparse_ir-1.1.6.dist-info/METADATA +0 -155
- sparse_ir-1.1.6.dist-info/RECORD +0 -20
- {sparse_ir-1.1.6.dist-info → sparse_ir-2.0.0a2.dist-info/licenses}/LICENSE.txt +0 -0
- {sparse_ir-1.1.6.dist-info → sparse_ir-2.0.0a2.dist-info}/top_level.txt +0 -0
sparse_ir/kernel.py
CHANGED
@@ -1,139 +1,33 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
import numpy as np
|
4
|
-
from typing import Callable
|
5
|
-
|
6
|
-
|
7
|
-
class AbstractKernel:
|
8
|
-
r"""Integral kernel ``K(x, y)``.
|
9
|
-
|
10
|
-
Abstract base class for an integral kernel, i.e., a real binary function
|
11
|
-
``K(x, y)`` used in a Fredhold integral equation of the first kind:
|
12
|
-
|
13
|
-
.. math:: u(x) = \int K(x, y) v(y) dy
|
14
|
-
|
15
|
-
where ``x ∈ [xmin, xmax]`` and ``y ∈ [ymin, ymax]``. For its SVE to exist,
|
16
|
-
the kernel must be square-integrable, for its singular values to decay
|
17
|
-
exponentially, it must be smooth.
|
18
|
-
|
19
|
-
In general, the kernel is applied to a scaled spectral function ρ'(y) as:
|
20
|
-
|
21
|
-
.. math:: \int K(x, y) \rho'(y) dy,
|
22
|
-
|
23
|
-
where ρ'(y) = w(y) ρ(y).
|
24
|
-
"""
|
25
|
-
def __call__(self, x, y, x_plus=None, x_minus=None):
|
26
|
-
"""Evaluate kernel at point (x, y)
|
27
|
-
|
28
|
-
For given ``x, y``, return the value of ``K(x, y)``. The arguments may
|
29
|
-
be numpy arrays, in which case the function shall be evaluated over
|
30
|
-
the broadcast arrays.
|
31
|
-
|
32
|
-
The parameters ``x_plus`` and ``x_minus``, if given, shall contain the
|
33
|
-
values of ``x - xmin`` and ``xmax - x``, respectively. This is useful
|
34
|
-
if either difference is to be formed and cancellation expected.
|
35
|
-
"""
|
36
|
-
raise NotImplementedError()
|
37
|
-
|
38
|
-
def sve_hints(self, eps):
|
39
|
-
"""Provide discretisation hints for the SVE routines.
|
40
|
-
|
41
|
-
Advises the SVE routines of discretisation parameters suitable in
|
42
|
-
tranforming the (infinite) SVE into an (finite) SVD problem.
|
43
|
-
|
44
|
-
See: :class:``AbstractSVEHints``.
|
45
|
-
"""
|
46
|
-
raise NotImplementedError()
|
47
|
-
|
48
|
-
@property
|
49
|
-
def xrange(self):
|
50
|
-
"""Tuple ``(xmin, xmax)`` delimiting the range of allowed x values"""
|
51
|
-
return -1, 1
|
1
|
+
"""
|
2
|
+
Kernel classes for SparseIR.
|
52
3
|
|
53
|
-
|
54
|
-
|
55
|
-
"""Tuple ``(ymin, ymax)`` delimiting the range of allowed y values"""
|
56
|
-
return -1, 1
|
57
|
-
|
58
|
-
@property
|
59
|
-
def is_centrosymmetric(self):
|
60
|
-
"""Kernel is centrosymmetric.
|
61
|
-
|
62
|
-
Returns true if and only if ``K(x, y) == K(-x, -y)`` for all values of
|
63
|
-
``x`` and ``y``. This allows the kernel to be block-diagonalized,
|
64
|
-
speeding up the singular value expansion by a factor of 4. Defaults
|
65
|
-
to false.
|
66
|
-
"""
|
67
|
-
return False
|
68
|
-
|
69
|
-
def get_symmetrized(self, sign):
|
70
|
-
"""Return symmetrized kernel ``K(x, y) + sign * K(x, -y)``.
|
4
|
+
This module provides Python wrappers for kernel objects from the C library.
|
5
|
+
"""
|
71
6
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
"""
|
76
|
-
return ReducedKernel(self, sign)
|
77
|
-
|
78
|
-
@property
|
79
|
-
def ypower(self):
|
80
|
-
"""Power with which the y coordinate scales."""
|
81
|
-
return 0
|
82
|
-
|
83
|
-
@property
|
84
|
-
def conv_radius(self):
|
85
|
-
"""Convergence radius of the Matsubara basis asymptotic model.
|
86
|
-
|
87
|
-
For improved relative numerical accuracy, the IR basis functions on the
|
88
|
-
Matsubara axis ``basis.uhat(n)`` can be evaluated from an asymptotic
|
89
|
-
expression for ``abs(n) > conv_radius``. If ``conv_radius`` is
|
90
|
-
``None``, then the asymptotics are unused (the default).
|
91
|
-
"""
|
92
|
-
return None
|
93
|
-
|
94
|
-
def weight_func(self, statistics: str) -> Callable[[np.ndarray], np.ndarray]:
|
95
|
-
"""Return the weight function for given statistics"""
|
96
|
-
if statistics not in 'FB':
|
97
|
-
raise ValueError("statistics must be 'F' for fermions or 'B' for bosons")
|
98
|
-
return lambda x: np.ones_like(x)
|
99
|
-
|
100
|
-
|
101
|
-
class AbstractSVEHints:
|
102
|
-
"""Discretization hints for singular value expansion of a given kernel."""
|
103
|
-
@property
|
104
|
-
def segments_x(self):
|
105
|
-
"""Segments for piecewise polynomials on the ``x`` axis.
|
106
|
-
|
107
|
-
List of segments on the ``x`` axis for the associated piecewise
|
108
|
-
polynomial. Should reflect the approximate position of roots of a
|
109
|
-
high-order singular function in ``x``.
|
110
|
-
"""
|
111
|
-
raise NotImplementedError()
|
112
|
-
|
113
|
-
@property
|
114
|
-
def segments_y(self):
|
115
|
-
"""Segments for piecewise polynomials on the ``y`` axis.
|
7
|
+
import ctypes
|
8
|
+
from ctypes import c_int, c_double, byref
|
9
|
+
import numpy as np
|
116
10
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
raise NotImplementedError()
|
11
|
+
from pylibsparseir.core import _lib
|
12
|
+
from pylibsparseir.core import logistic_kernel_new, reg_bose_kernel_new
|
13
|
+
from pylibsparseir.constants import COMPUTATION_SUCCESS
|
14
|
+
from .abstract import AbstractKernel
|
122
15
|
|
123
|
-
@property
|
124
|
-
def ngauss(self):
|
125
|
-
"""Gauss-Legendre order to use to guarantee accuracy"""
|
126
|
-
raise NotImplementedError()
|
127
16
|
|
128
|
-
|
129
|
-
|
130
|
-
|
17
|
+
def kernel_domain(kernel: AbstractKernel):
|
18
|
+
"""Get the domain boundaries of a kernel."""
|
19
|
+
xmin = c_double()
|
20
|
+
xmax = c_double()
|
21
|
+
ymin = c_double()
|
22
|
+
ymax = c_double()
|
131
23
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
24
|
+
status = _lib.spir_kernel_domain(
|
25
|
+
kernel._ptr, byref(xmin), byref(xmax), byref(ymin), byref(ymax)
|
26
|
+
)
|
27
|
+
if status != COMPUTATION_SUCCESS:
|
28
|
+
raise RuntimeError(f"Failed to get kernel domain: {status}")
|
136
29
|
|
30
|
+
return xmin.value, xmax.value, ymin.value, ymax.value
|
137
31
|
|
138
32
|
class LogisticKernel(AbstractKernel):
|
139
33
|
r"""Fermionic/bosonic analytical continuation kernel.
|
@@ -155,97 +49,27 @@ class LogisticKernel(AbstractKernel):
|
|
155
49
|
i.e., a rescaling of the spectral function with the weight function:
|
156
50
|
|
157
51
|
.. math:: w(y) = \frac1{\tanh(\Lambda y/2)}.
|
158
|
-
"""
|
159
|
-
def __init__(self, lambda_):
|
160
|
-
self.lambda_ = lambda_
|
161
|
-
|
162
|
-
def __call__(self, x, y, x_plus=None, x_minus=None):
|
163
|
-
x, y = _check_domain(self, x, y)
|
164
|
-
u_plus, u_minus, v = _compute_uv(self.lambda_, x, y, x_plus, x_minus)
|
165
|
-
return self._compute(u_plus, u_minus, v)
|
166
|
-
|
167
|
-
def sve_hints(self, eps):
|
168
|
-
return _SVEHintsLogistic(self, eps)
|
169
|
-
|
170
|
-
def _compute(self, u_plus, u_minus, v):
|
171
|
-
# By introducing u_\pm = (1 \pm x)/2 and v = lambda * y, we can write
|
172
|
-
# the kernel in the following two ways:
|
173
|
-
#
|
174
|
-
# k = exp(-u_+ * v) / (exp(-v) + 1)
|
175
|
-
# = exp(-u_- * -v) / (exp(v) + 1)
|
176
|
-
#
|
177
|
-
# We need to use the upper equation for v >= 0 and the lower one for
|
178
|
-
# v < 0 to avoid overflowing both numerator and denominator
|
179
|
-
abs_v = np.abs(v)
|
180
|
-
enum = np.exp(-abs_v * np.where(v > 0, u_plus, u_minus))
|
181
|
-
denom = 1 + np.exp(-abs_v)
|
182
|
-
return enum / denom
|
183
|
-
|
184
|
-
@property
|
185
|
-
def is_centrosymmetric(self):
|
186
|
-
return True
|
187
|
-
|
188
|
-
def get_symmetrized(self, sign):
|
189
|
-
if sign == -1:
|
190
|
-
return _LogisticKernelOdd(self, sign)
|
191
|
-
return super().get_symmetrized(sign)
|
192
|
-
|
193
|
-
@property
|
194
|
-
def conv_radius(self): return 40 * self.lambda_
|
195
|
-
|
196
|
-
def weight_func(self, statistics: str) -> Callable[[np.ndarray], np.ndarray]:
|
197
|
-
"""
|
198
|
-
Return the weight function for given statistics.
|
199
|
-
|
200
|
-
- Fermion: `w(x) == 1`
|
201
|
-
- Boson: `w(y) == 1/tanh(Λ*y/2)`
|
202
|
-
"""
|
203
|
-
if statistics not in "FB":
|
204
|
-
raise ValueError("invalid value of statistics argument")
|
205
|
-
if statistics == "F":
|
206
|
-
return lambda y: np.ones_like(y)
|
207
|
-
else:
|
208
|
-
return lambda y: 1/np.tanh(0.5*self.lambda_*y)
|
209
|
-
|
210
|
-
|
211
|
-
class _SVEHintsLogistic(AbstractSVEHints):
|
212
|
-
def __init__(self, kernel, eps):
|
213
|
-
self.kernel = kernel
|
214
|
-
self.eps = eps
|
215
52
|
|
216
|
-
|
217
|
-
|
53
|
+
Parameters
|
54
|
+
----------
|
55
|
+
lambda_ : float
|
56
|
+
Kernel cutoff Λ = β * ωmax
|
57
|
+
"""
|
218
58
|
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
zeros_pos = diffs.cumsum()
|
224
|
-
zeros_pos /= zeros_pos[-1]
|
225
|
-
return np.concatenate((-zeros_pos[::-1], [0], zeros_pos))
|
59
|
+
def __init__(self, lambda_):
|
60
|
+
"""Initialize logistic kernel with cutoff lambda."""
|
61
|
+
self._lambda = float(lambda_)
|
62
|
+
self._ptr = logistic_kernel_new(self._lambda)
|
226
63
|
|
227
64
|
@property
|
228
|
-
def
|
229
|
-
|
230
|
-
|
231
|
-
0.01523, 0.03314, 0.04848, 0.05987, 0.06703, 0.07028, 0.07030,
|
232
|
-
0.06791, 0.06391, 0.05896, 0.05358, 0.04814, 0.04288, 0.03795,
|
233
|
-
0.03342, 0.02932, 0.02565, 0.02239, 0.01951, 0.01699])
|
65
|
+
def lambda_(self):
|
66
|
+
"""Kernel cutoff."""
|
67
|
+
return self._lambda
|
234
68
|
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
diffs[:leading_diffs.size] = leading_diffs
|
240
|
-
zeros = diffs.cumsum()
|
241
|
-
zeros = zeros[:-1] / zeros[-1]
|
242
|
-
zeros -= 1
|
243
|
-
return np.concatenate(([-1], zeros, [0], -zeros[::-1], [1]))
|
244
|
-
|
245
|
-
@property
|
246
|
-
def nsvals(self):
|
247
|
-
log10_lambda = max(1, np.log10(self.kernel.lambda_))
|
248
|
-
return int(np.round((25 + log10_lambda) * log10_lambda))
|
69
|
+
def __del__(self):
|
70
|
+
"""Clean up kernel resources."""
|
71
|
+
if hasattr(self, '_ptr') and self._ptr:
|
72
|
+
_lib.spir_kernel_release(self._ptr)
|
249
73
|
|
250
74
|
|
251
75
|
class RegularizedBoseKernel(AbstractKernel):
|
@@ -259,271 +83,24 @@ class RegularizedBoseKernel(AbstractKernel):
|
|
259
83
|
K(x, y) = \frac{y \exp(-\Lambda y(x + 1)/2)}{\exp(-\Lambda y) - 1}
|
260
84
|
|
261
85
|
Care has to be taken in evaluating this expression around ``y == 0``.
|
262
|
-
"""
|
263
|
-
def __init__(self, lambda_):
|
264
|
-
self.lambda_ = lambda_
|
265
86
|
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
def _compute(self, u_plus, u_minus, v):
|
272
|
-
# With "reduced variables" u, v we have:
|
273
|
-
#
|
274
|
-
# K = -1/lambda * exp(-u_+ * v) * v / (exp(-v) - 1)
|
275
|
-
# = -1/lambda * exp(-u_- * -v) * (-v) / (exp(v) - 1)
|
276
|
-
#
|
277
|
-
# where we again need to use the upper equation for v >= 0 and the
|
278
|
-
# lower one for v < 0 to avoid overflow.
|
279
|
-
abs_v = np.abs(v)
|
280
|
-
enum = np.exp(-abs_v * np.where(v >= 0, u_plus, u_minus))
|
281
|
-
dtype = v.dtype
|
282
|
-
|
283
|
-
# The expression ``v / (exp(v) - 1)`` is tricky to evaluate: firstly,
|
284
|
-
# it has a singularity at v=0, which can be cured by treating that case
|
285
|
-
# separately. Secondly, the denominator loses precision around 0 since
|
286
|
-
# exp(v) = 1 + v + ..., which can be avoided using expm1(...)
|
287
|
-
not_tiny = abs_v >= 1e-200
|
288
|
-
denom = -np.ones_like(abs_v)
|
289
|
-
np.divide(abs_v, np.expm1(-abs_v, where=not_tiny),
|
290
|
-
out=denom, where=not_tiny)
|
291
|
-
return -1/dtype.type(self.lambda_) * enum * denom
|
292
|
-
|
293
|
-
def sve_hints(self, eps):
|
294
|
-
return _SVEHintsRegularizedBose(self, eps)
|
295
|
-
|
296
|
-
@property
|
297
|
-
def is_centrosymmetric(self):
|
298
|
-
return True
|
299
|
-
|
300
|
-
def get_symmetrized(self, sign):
|
301
|
-
if sign == -1:
|
302
|
-
return _RegularizedBoseKernelOdd(self, sign)
|
303
|
-
return super().get_symmetrized(sign)
|
304
|
-
|
305
|
-
@property
|
306
|
-
def ypower(self): return 1
|
307
|
-
|
308
|
-
@property
|
309
|
-
def conv_radius(self): return 40 * self.lambda_
|
310
|
-
|
311
|
-
def weight_func(self, statistics: str) -> Callable[[np.ndarray], np.ndarray]:
|
312
|
-
""" Return the weight function for given statistics """
|
313
|
-
if statistics != "B":
|
314
|
-
raise ValueError("Kernel is designed for bosonic functions")
|
315
|
-
return lambda y: 1/y
|
316
|
-
|
317
|
-
|
318
|
-
class _SVEHintsRegularizedBose(AbstractSVEHints):
|
319
|
-
def __init__(self, kernel, eps):
|
320
|
-
self.kernel = kernel
|
321
|
-
self.eps = eps
|
322
|
-
|
323
|
-
@property
|
324
|
-
def ngauss(self): return 10 if self.eps >= 1e-8 else 16
|
325
|
-
|
326
|
-
@property
|
327
|
-
def segments_x(self):
|
328
|
-
# Somewhat less accurate ...
|
329
|
-
nzeros = max(int(np.round(15 * np.log10(self.kernel.lambda_))), 15)
|
330
|
-
diffs = 1./np.cosh(.18 * np.arange(nzeros))
|
331
|
-
zeros_pos = diffs.cumsum()
|
332
|
-
zeros_pos /= zeros_pos[-1]
|
333
|
-
return np.concatenate((-zeros_pos[::-1], [0], zeros_pos))
|
334
|
-
|
335
|
-
@property
|
336
|
-
def segments_y(self):
|
337
|
-
# Zeros around -1 and 1 are distributed asymptotically identical
|
338
|
-
leading_diffs = np.array([
|
339
|
-
0.01363, 0.02984, 0.04408, 0.05514, 0.06268, 0.06679, 0.06793,
|
340
|
-
0.06669, 0.06373, 0.05963, 0.05488, 0.04987, 0.04487, 0.04005,
|
341
|
-
0.03553, 0.03137, 0.02758, 0.02418, 0.02115, 0.01846])
|
342
|
-
|
343
|
-
nzeros = max(int(np.round(20 * np.log10(self.kernel.lambda_))), 20)
|
344
|
-
i = np.arange(nzeros)
|
345
|
-
diffs = .12/np.exp(.0337 * i * np.log(i+1))
|
346
|
-
#diffs[:leading_diffs.size] = leading_diffs
|
347
|
-
zeros = diffs.cumsum()
|
348
|
-
zeros = zeros[:-1] / zeros[-1]
|
349
|
-
zeros -= 1
|
350
|
-
return np.concatenate(([-1], zeros, [0], -zeros[::-1], [1]))
|
351
|
-
|
352
|
-
@property
|
353
|
-
def nsvals(self):
|
354
|
-
log10_lambda = max(1, np.log10(self.kernel.lambda_))
|
355
|
-
return int(28 * log10_lambda)
|
356
|
-
|
357
|
-
|
358
|
-
class ReducedKernel(AbstractKernel):
|
359
|
-
"""Restriction of centrosymmetric kernel to positive interval.
|
360
|
-
|
361
|
-
For a kernel ``K`` on ``[-1, 1] x [-1, 1]`` that is centrosymmetric, i.e.
|
362
|
-
``K(x, y) == K(-x, -y)``, it is straight-forward to show that the left/right
|
363
|
-
singular vectors can be chosen as either odd or even functions.
|
364
|
-
|
365
|
-
Consequentially, they are singular functions of a reduced kernel ``K_red``
|
366
|
-
on ``[0, 1] x [0, 1]`` that is given as either::
|
367
|
-
|
368
|
-
K_red(x, y) == K(x, y) + sign * K(x, -y)
|
369
|
-
|
370
|
-
This kernel is what this class represents. The full singular functions can
|
371
|
-
be reconstructed by (anti-)symmetrically continuing them to the negative
|
372
|
-
axis.
|
87
|
+
Parameters
|
88
|
+
----------
|
89
|
+
lambda_ : float
|
90
|
+
Kernel cutoff Λ = β * ωmax
|
373
91
|
"""
|
374
|
-
def __init__(self, inner, sign=1):
|
375
|
-
if not inner.is_centrosymmetric:
|
376
|
-
raise ValueError("inner kernel must be centrosymmetric")
|
377
|
-
if np.abs(sign) != 1:
|
378
|
-
raise ValueError("sign must square to one")
|
379
|
-
|
380
|
-
self.inner = inner
|
381
|
-
self.sign = sign
|
382
|
-
|
383
|
-
def __call__(self, x, y, x_plus=None, x_minus=None):
|
384
|
-
x, y = _check_domain(self, x, y)
|
385
|
-
|
386
|
-
# The reduced kernel is defined only over the interval [0, 1], which
|
387
|
-
# means we must add one to get the x_plus for the inner kernels. We
|
388
|
-
# can compute this as 1 + x, since we are away from -1.
|
389
|
-
x_plus = 1 + x_plus
|
390
|
-
|
391
|
-
K_plus = self.inner(x, y, x_plus, x_minus)
|
392
|
-
K_minus = self.inner(x, -y, x_plus, x_minus)
|
393
|
-
return K_plus + K_minus if self.sign == 1 else K_plus - K_minus
|
394
|
-
|
395
|
-
@property
|
396
|
-
def xrange(self):
|
397
|
-
_, xmax = self.inner.xrange
|
398
|
-
return 0, xmax
|
399
|
-
|
400
|
-
@property
|
401
|
-
def yrange(self):
|
402
|
-
_, ymax = self.inner.yrange
|
403
|
-
return 0, ymax
|
404
|
-
|
405
|
-
def sve_hints(self, eps):
|
406
|
-
return _SVEHintsReduced(self.inner.sve_hints(eps))
|
407
|
-
|
408
|
-
@property
|
409
|
-
def is_centrosymmetric(self):
|
410
|
-
"""True iff K(x,y) = K(-x, -y)"""
|
411
|
-
return False
|
412
|
-
|
413
|
-
def get_symmetrized(self, sign):
|
414
|
-
raise RuntimeError("cannot symmetrize twice")
|
415
|
-
|
416
|
-
@property
|
417
|
-
def ypower(self): return self.inner.ypower
|
418
|
-
|
419
|
-
@property
|
420
|
-
def conv_radius(self): return self.inner.conv_radius
|
421
|
-
|
422
|
-
|
423
|
-
class _SVEHintsReduced(AbstractSVEHints):
|
424
|
-
def __init__(self, inner_hints):
|
425
|
-
self.inner_hints = inner_hints
|
426
|
-
|
427
|
-
@property
|
428
|
-
def ngauss(self): return self.inner_hints.ngauss
|
429
|
-
|
430
|
-
@property
|
431
|
-
def segments_x(self): return _symm_segments(self.inner_hints.segments_x)
|
432
92
|
|
433
|
-
|
434
|
-
|
93
|
+
def __init__(self, lambda_):
|
94
|
+
"""Initialize regularized bosonic kernel with cutoff lambda."""
|
95
|
+
self._lambda = float(lambda_)
|
96
|
+
self._ptr = reg_bose_kernel_new(self._lambda)
|
435
97
|
|
436
98
|
@property
|
437
|
-
def
|
438
|
-
|
439
|
-
|
440
|
-
class _LogisticKernelOdd(ReducedKernel):
|
441
|
-
"""Fermionic analytical continuation kernel, odd.
|
442
|
-
|
443
|
-
In dimensionless variables ``x = 2*τ/β - 1``, ``y = β*ω/Λ``, the fermionic
|
444
|
-
integral kernel is a function on ``[-1, 1] x [-1, 1]``::
|
445
|
-
|
446
|
-
K(x, y) == -sinh(Λ/2 * x * y) / cosh(Λ/2 * y)
|
447
|
-
"""
|
448
|
-
def __call__(self, x, y, x_plus=None, x_minus=None):
|
449
|
-
result = super().__call__(x, y, x_plus, x_minus)
|
450
|
-
|
451
|
-
# For x * y around 0, antisymmetrization introduces cancellation, which
|
452
|
-
# reduces the relative precision. To combat this, we replace the
|
453
|
-
# values with the explicit form
|
454
|
-
v_half = self.inner.lambda_/2 * y
|
455
|
-
xy_small = x * v_half < 1
|
456
|
-
cosh_finite = v_half < 85
|
457
|
-
np.divide(-np.sinh(v_half * x, where=xy_small),
|
458
|
-
np.cosh(v_half, where=cosh_finite),
|
459
|
-
out=result, where=np.logical_and(xy_small, cosh_finite))
|
460
|
-
return result
|
461
|
-
|
462
|
-
|
463
|
-
class _RegularizedBoseKernelOdd(ReducedKernel):
|
464
|
-
"""Bosonic analytical continuation kernel, odd.
|
465
|
-
|
466
|
-
In dimensionless variables ``x = 2*τ/β - 1``, ``y = β*ω/Λ``, the fermionic
|
467
|
-
integral kernel is a function on ``[-1, 1] x [-1, 1]``::
|
468
|
-
|
469
|
-
K(x, y) = -y * sinh(Λ/2 * x * y) / sinh(Λ/2 * y)
|
470
|
-
"""
|
471
|
-
def __call__(self, x, y, x_plus=None, x_minus=None):
|
472
|
-
result = super().__call__(x, y, x_plus, x_minus)
|
473
|
-
|
474
|
-
# For x * y around 0, antisymmetrization introduces cancellation, which
|
475
|
-
# reduces the relative precision. To combat this, we replace the
|
476
|
-
# values with the explicit form
|
477
|
-
v_half = self.inner.lambda_/2 * y
|
478
|
-
xv_half = x * v_half
|
479
|
-
xy_small = xv_half < 1
|
480
|
-
sinh_range = np.logical_and(v_half > 1e-200, v_half < 85)
|
481
|
-
np.divide(
|
482
|
-
np.multiply(-y, np.sinh(xv_half, where=xy_small), where=xy_small),
|
483
|
-
np.sinh(v_half, where=sinh_range),
|
484
|
-
out=result, where=np.logical_and(xy_small, sinh_range))
|
485
|
-
return result
|
486
|
-
|
487
|
-
|
488
|
-
def matrix_from_gauss(kernel, gauss_x, gauss_y):
|
489
|
-
"""Compute matrix for kernel from Gauss rule"""
|
490
|
-
# (1 +- x) is problematic around x = -1 and x = 1, where the quadrature
|
491
|
-
# nodes are clustered most tightly. Thus we have the need for the
|
492
|
-
# matrix method.
|
493
|
-
return kernel(gauss_x.x[:,None], gauss_y.x[None,:],
|
494
|
-
gauss_x.x_forward[:,None], gauss_x.x_backward[:,None])
|
495
|
-
|
496
|
-
|
497
|
-
def _check_domain(kernel, x, y):
|
498
|
-
"""Check that arguments lie within the correct domain"""
|
499
|
-
x = np.asarray(x)
|
500
|
-
xmin, xmax = kernel.xrange
|
501
|
-
if not (x >= xmin).all() or not (x <= xmax).all():
|
502
|
-
raise ValueError("x values not in range [{:g},{:g}]".format(xmin, xmax))
|
503
|
-
|
504
|
-
y = np.asarray(y)
|
505
|
-
ymin, ymax = kernel.yrange
|
506
|
-
if not (y >= ymin).all() or not (y <= ymax).all():
|
507
|
-
raise ValueError("y values not in range [{:g},{:g}]".format(ymin, ymax))
|
508
|
-
return x, y
|
509
|
-
|
510
|
-
|
511
|
-
def _symm_segments(x):
|
512
|
-
x = np.asarray(x)
|
513
|
-
if not np.allclose(x, -x[::-1]):
|
514
|
-
raise ValueError("segments must be symmetric")
|
515
|
-
xpos = x[x.size // 2:]
|
516
|
-
if xpos[0] != 0:
|
517
|
-
xpos = np.hstack([0, xpos])
|
518
|
-
return xpos
|
519
|
-
|
99
|
+
def lambda_(self):
|
100
|
+
"""Kernel cutoff."""
|
101
|
+
return self._lambda
|
520
102
|
|
521
|
-
def
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
x_minus = 1 - x
|
526
|
-
u_plus = .5 * x_plus
|
527
|
-
u_minus = .5 * x_minus
|
528
|
-
v = lambda_ * y
|
529
|
-
return u_plus, u_minus, v
|
103
|
+
def __del__(self):
|
104
|
+
"""Clean up kernel resources."""
|
105
|
+
if hasattr(self, '_ptr') and self._ptr:
|
106
|
+
_lib.spir_kernel_release(self._ptr)
|