sparse-ir 2.1.2__tar.gz → 2.1.4__tar.gz
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-2.1.2/src/sparse_ir.egg-info → sparse_ir-2.1.4}/PKG-INFO +1 -1
- {sparse_ir-2.1.2 → sparse_ir-2.1.4}/pyproject.toml +1 -1
- {sparse_ir-2.1.2 → sparse_ir-2.1.4}/src/sparse_ir/__init__.py +2 -2
- sparse_ir-2.1.4/src/sparse_ir/_util.py +298 -0
- {sparse_ir-2.1.2 → sparse_ir-2.1.4}/src/sparse_ir/augment.py +41 -6
- {sparse_ir-2.1.2 → sparse_ir-2.1.4}/src/sparse_ir/basis.py +9 -9
- {sparse_ir-2.1.2 → sparse_ir-2.1.4}/src/sparse_ir/dlr.py +103 -30
- {sparse_ir-2.1.2 → sparse_ir-2.1.4}/src/sparse_ir/poly.py +85 -116
- {sparse_ir-2.1.2 → sparse_ir-2.1.4}/src/sparse_ir/sampling.py +154 -69
- {sparse_ir-2.1.2 → sparse_ir-2.1.4/src/sparse_ir.egg-info}/PKG-INFO +1 -1
- {sparse_ir-2.1.2 → sparse_ir-2.1.4}/src/sparse_ir.egg-info/SOURCES.txt +1 -0
- sparse_ir-2.1.4/tests/test_advanced_features.py +223 -0
- {sparse_ir-2.1.2 → sparse_ir-2.1.4}/tests/test_augment.py +27 -30
- {sparse_ir-2.1.2 → sparse_ir-2.1.4}/tests/test_dlr.py +34 -0
- sparse_ir-2.1.4/tests/test_ffi_boundary.py +564 -0
- {sparse_ir-2.1.2 → sparse_ir-2.1.4}/tests/test_poly.py +1 -117
- {sparse_ir-2.1.2 → sparse_ir-2.1.4}/tests/test_sampling.py +50 -0
- {sparse_ir-2.1.2 → sparse_ir-2.1.4}/tests/test_sampling_advanced.py +6 -14
- {sparse_ir-2.1.2 → sparse_ir-2.1.4}/tests/test_sve_advanced.py +38 -44
- sparse_ir-2.1.2/src/sparse_ir/_util.py +0 -154
- sparse_ir-2.1.2/tests/test_advanced_features.py +0 -250
- {sparse_ir-2.1.2 → sparse_ir-2.1.4}/LICENSE.txt +0 -0
- {sparse_ir-2.1.2 → sparse_ir-2.1.4}/MANIFEST.in +0 -0
- {sparse_ir-2.1.2 → sparse_ir-2.1.4}/README.rst +0 -0
- {sparse_ir-2.1.2 → sparse_ir-2.1.4}/setup.cfg +0 -0
- {sparse_ir-2.1.2 → sparse_ir-2.1.4}/src/sparse_ir/_gauss.py +0 -0
- {sparse_ir-2.1.2 → sparse_ir-2.1.4}/src/sparse_ir/abstract.py +0 -0
- {sparse_ir-2.1.2 → sparse_ir-2.1.4}/src/sparse_ir/basis_set.py +0 -0
- {sparse_ir-2.1.2 → sparse_ir-2.1.4}/src/sparse_ir/kernel.py +0 -0
- {sparse_ir-2.1.2 → sparse_ir-2.1.4}/src/sparse_ir/sve.py +0 -0
- {sparse_ir-2.1.2 → sparse_ir-2.1.4}/src/sparse_ir.egg-info/dependency_links.txt +0 -0
- {sparse_ir-2.1.2 → sparse_ir-2.1.4}/src/sparse_ir.egg-info/requires.txt +0 -0
- {sparse_ir-2.1.2 → sparse_ir-2.1.4}/src/sparse_ir.egg-info/top_level.txt +0 -0
- {sparse_ir-2.1.2 → sparse_ir-2.1.4}/tests/test_basis.py +0 -0
- {sparse_ir-2.1.2 → sparse_ir-2.1.4}/tests/test_basis_set.py +0 -0
- {sparse_ir-2.1.2 → sparse_ir-2.1.4}/tests/test_core.py +0 -0
- {sparse_ir-2.1.2 → sparse_ir-2.1.4}/tests/test_kernel.py +0 -0
- {sparse_ir-2.1.2 → sparse_ir-2.1.4}/tests/test_sve.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "sparse-ir"
|
|
3
|
-
version = "2.1.
|
|
3
|
+
version = "2.1.4"
|
|
4
4
|
description = "Python bindings for the libsparseir library, providing efficient sparse intermediate representation for many-body physics calculations"
|
|
5
5
|
readme = "README.rst"
|
|
6
6
|
requires-python = ">=3.10"
|
|
@@ -30,12 +30,12 @@ __all__ = [
|
|
|
30
30
|
'AbstractBasis', 'FiniteTempBasis', 'finite_temp_bases',
|
|
31
31
|
'TauSampling', 'MatsubaraSampling', 'FiniteTempBasisSet',
|
|
32
32
|
'LogisticKernel', 'RegularizedBoseKernel',
|
|
33
|
-
'SVEResult', 'compute',
|
|
33
|
+
'SVEResult', 'compute', 'compute_sve',
|
|
34
34
|
|
|
35
35
|
# Augmented functionality
|
|
36
36
|
'AugmentedBasis', 'AugmentedTauFunction', 'AugmentedMatsubaraFunction',
|
|
37
37
|
'AbstractAugmentation', 'TauConst', 'TauLinear', 'MatsubaraConst',
|
|
38
38
|
|
|
39
39
|
# DLR functionality
|
|
40
|
-
'DiscreteLehmannRepresentation',
|
|
40
|
+
'DiscreteLehmannRepresentation',
|
|
41
41
|
]
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
# Copyright (C) 2020-2025 Satoshi Terasaki, Markus Wallerberger, Hiroshi Shinaoka, and others
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
import functools
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def ravel_argument(last_dim=False):
|
|
8
|
+
"""Wrap function operating on 1-D numpy array to allow arbitrary shapes.
|
|
9
|
+
|
|
10
|
+
This decorator allows to write functions which only need to operate over
|
|
11
|
+
one-dimensional (ravelled) arrays. This often simplifies the "shape logic"
|
|
12
|
+
of the computation.
|
|
13
|
+
"""
|
|
14
|
+
return lambda fn: RavelArgumentDecorator(fn, last_dim)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class RavelArgumentDecorator(object):
|
|
18
|
+
def __init__(self, inner, last_dim=False):
|
|
19
|
+
self.instance = None
|
|
20
|
+
self.inner = inner
|
|
21
|
+
self.last_dim = last_dim
|
|
22
|
+
functools.update_wrapper(self, inner)
|
|
23
|
+
|
|
24
|
+
def __get__(self, instance, _owner=None):
|
|
25
|
+
self.instance = instance
|
|
26
|
+
return self
|
|
27
|
+
|
|
28
|
+
def __call__(self, x):
|
|
29
|
+
x = np.asarray(x)
|
|
30
|
+
if self.instance is None:
|
|
31
|
+
res = self.inner(x.ravel())
|
|
32
|
+
else:
|
|
33
|
+
res = self.inner(self.instance, x.ravel())
|
|
34
|
+
if self.last_dim:
|
|
35
|
+
return res.reshape(res.shape[:-1] + x.shape)
|
|
36
|
+
else:
|
|
37
|
+
return res.reshape(x.shape + res.shape[1:])
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# Element-type kinds that can be widened to float64 without losing
|
|
41
|
+
# information about what the caller meant: bool, signed/unsigned integer,
|
|
42
|
+
# and floating point. Complex is deliberately excluded.
|
|
43
|
+
_REAL_KINDS = "biuf"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def check_reduced_matsubara(n, zeta=None):
|
|
47
|
+
"""Checks that ``n`` is a reduced Matsubara frequency.
|
|
48
|
+
|
|
49
|
+
Check that the argument is a reduced Matsubara frequency, which is an
|
|
50
|
+
integer obtained by scaling the freqency `w[n]` as follows::
|
|
51
|
+
|
|
52
|
+
beta / np.pi * w[n] == 2 * n + zeta
|
|
53
|
+
|
|
54
|
+
Note that this means that instead of a fermionic frequency (``zeta == 1``),
|
|
55
|
+
we expect an odd integer, while for a bosonic frequency (``zeta == 0``),
|
|
56
|
+
we expect an even one. If ``zeta`` is omitted, any one is fine.
|
|
57
|
+
|
|
58
|
+
Raises:
|
|
59
|
+
TypeError: if ``n`` is complex.
|
|
60
|
+
ValueError: if ``n`` is not integral (naming the offending value) or
|
|
61
|
+
has the wrong parity.
|
|
62
|
+
"""
|
|
63
|
+
n = np.asarray(n)
|
|
64
|
+
if n.dtype.kind == 'c':
|
|
65
|
+
raise TypeError(
|
|
66
|
+
f"reduced Matsubara frequency must be real, got dtype {n.dtype}")
|
|
67
|
+
if not np.issubdtype(n.dtype, np.integer):
|
|
68
|
+
if n.dtype.kind not in _REAL_KINDS:
|
|
69
|
+
raise TypeError(
|
|
70
|
+
f"reduced Matsubara frequency must be numeric, "
|
|
71
|
+
f"got dtype {n.dtype}")
|
|
72
|
+
nfloat = np.asarray(n, dtype=np.float64)
|
|
73
|
+
if not np.all(np.isfinite(nfloat)):
|
|
74
|
+
raise ValueError(
|
|
75
|
+
"reduced Matsubara frequency must be finite, got "
|
|
76
|
+
f"{nfloat[~np.isfinite(nfloat)][0]!r}")
|
|
77
|
+
n = np.rint(nfloat).astype(np.int64)
|
|
78
|
+
bad = n != nfloat
|
|
79
|
+
if bad.any():
|
|
80
|
+
offending = np.atleast_1d(nfloat)[np.atleast_1d(bad)][0]
|
|
81
|
+
raise ValueError(
|
|
82
|
+
"reduced Matsubara frequency must be an integer, got "
|
|
83
|
+
f"{offending!r} (no truncation is performed)")
|
|
84
|
+
if zeta is not None:
|
|
85
|
+
parity = np.asarray(n) & 1
|
|
86
|
+
if not (parity == zeta).all():
|
|
87
|
+
expected = "odd" if zeta else "even"
|
|
88
|
+
offending = np.atleast_1d(n)[np.atleast_1d(parity != zeta)][0]
|
|
89
|
+
raise ValueError(
|
|
90
|
+
f"reduced Matsubara frequency must be {expected} for "
|
|
91
|
+
f"zeta={zeta}, got {offending!r}")
|
|
92
|
+
return n
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _check_finite(arr, name):
|
|
96
|
+
if arr.size and not np.all(np.isfinite(arr)):
|
|
97
|
+
pos = tuple(int(i) for i in np.argwhere(~np.isfinite(arr))[0])
|
|
98
|
+
raise ValueError(
|
|
99
|
+
f"{name} must be finite, but contains {arr[pos]!r} at index "
|
|
100
|
+
f"{pos[0] if arr.ndim == 1 else pos}")
|
|
101
|
+
return arr
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def as_boundary_real(a, name="array", check_finite=True):
|
|
105
|
+
"""Normalize ``a`` into a C-contiguous ``float64`` array for the C boundary.
|
|
106
|
+
|
|
107
|
+
The returned object is the one whose pointer must be handed to C: a
|
|
108
|
+
pointer taken from the *original* array would be a defect if a copy was
|
|
109
|
+
made here (see ``rules/ffi-boundary.md``, Pointer Provenance).
|
|
110
|
+
|
|
111
|
+
Raises:
|
|
112
|
+
TypeError: if ``a`` is complex or of a non-numeric element type.
|
|
113
|
+
ValueError: if ``a`` contains a non-finite value.
|
|
114
|
+
"""
|
|
115
|
+
arr = np.asarray(a)
|
|
116
|
+
if arr.dtype.kind == 'c':
|
|
117
|
+
raise TypeError(
|
|
118
|
+
f"{name} must be real-valued, got dtype {arr.dtype}; "
|
|
119
|
+
"the C entry point takes a double pointer")
|
|
120
|
+
if arr.dtype.kind not in _REAL_KINDS:
|
|
121
|
+
raise TypeError(f"{name} has unsupported dtype {arr.dtype}")
|
|
122
|
+
out = np.ascontiguousarray(arr, dtype=np.float64)
|
|
123
|
+
if check_finite:
|
|
124
|
+
_check_finite(out, name)
|
|
125
|
+
return out
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def as_boundary_complex(a, name="array", check_finite=True):
|
|
129
|
+
"""Normalize ``a`` into a C-contiguous ``complex128`` array.
|
|
130
|
+
|
|
131
|
+
``complex64`` is *not* ``complex128``: passing its buffer through a
|
|
132
|
+
``c_double_complex`` pointer would read twice as many bytes per element
|
|
133
|
+
as were allocated, so the conversion here is explicit and the pointer
|
|
134
|
+
must be taken from the returned object.
|
|
135
|
+
"""
|
|
136
|
+
arr = np.asarray(a)
|
|
137
|
+
if arr.dtype.kind not in _REAL_KINDS + "c":
|
|
138
|
+
raise TypeError(f"{name} has unsupported dtype {arr.dtype}")
|
|
139
|
+
out = np.ascontiguousarray(arr, dtype=np.complex128)
|
|
140
|
+
if check_finite:
|
|
141
|
+
_check_finite(out, name)
|
|
142
|
+
return out
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def as_boundary_matsubara(n, name="Matsubara indices", zeta=None):
|
|
146
|
+
"""Normalize reduced Matsubara indices into a C-contiguous ``int64`` array.
|
|
147
|
+
|
|
148
|
+
Validates integrality (and, if ``zeta`` is given, parity) *before* the
|
|
149
|
+
conversion, so a non-integral index raises instead of being truncated.
|
|
150
|
+
"""
|
|
151
|
+
checked = check_reduced_matsubara(n, zeta=zeta)
|
|
152
|
+
return np.ascontiguousarray(checked, dtype=np.int64)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def normalize_axis(axis, ndim):
|
|
156
|
+
"""Resolve a possibly negative ``axis`` against ``ndim`` and range-check it.
|
|
157
|
+
|
|
158
|
+
The C API takes a non-negative target dimension; a negative Python axis
|
|
159
|
+
must be resolved here rather than handed through.
|
|
160
|
+
"""
|
|
161
|
+
axis = int(axis)
|
|
162
|
+
resolved = axis + ndim if axis < 0 else axis
|
|
163
|
+
if not 0 <= resolved < ndim:
|
|
164
|
+
raise IndexError(
|
|
165
|
+
f"axis {axis} is out of bounds for an array of dimension {ndim} "
|
|
166
|
+
f"(valid: {-ndim} .. {ndim - 1})")
|
|
167
|
+
return resolved
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def resolve_function_indices(index, size):
|
|
171
|
+
"""Resolve a basis-function index, list of indices, or slice.
|
|
172
|
+
|
|
173
|
+
Negative indices are resolved explicitly (Python semantics); an index
|
|
174
|
+
outside ``[-size, size)`` raises :class:`IndexError` naming the requested
|
|
175
|
+
index and the valid range. No modulo wrap-around is performed.
|
|
176
|
+
"""
|
|
177
|
+
if isinstance(index, slice):
|
|
178
|
+
return list(range(*index.indices(size)))
|
|
179
|
+
|
|
180
|
+
idx = np.asarray(index)
|
|
181
|
+
if idx.dtype.kind == 'c':
|
|
182
|
+
raise TypeError(
|
|
183
|
+
f"basis-function index must be an integer, got dtype {idx.dtype}")
|
|
184
|
+
if idx.dtype.kind not in _REAL_KINDS:
|
|
185
|
+
raise TypeError(
|
|
186
|
+
f"basis-function index must be an integer, got dtype {idx.dtype}")
|
|
187
|
+
if idx.dtype.kind == 'f':
|
|
188
|
+
rounded = np.rint(idx)
|
|
189
|
+
if not np.array_equal(rounded, idx):
|
|
190
|
+
offending = np.atleast_1d(idx)[np.atleast_1d(rounded != idx)][0]
|
|
191
|
+
raise ValueError(
|
|
192
|
+
f"basis-function index must be an integer, got {offending!r} "
|
|
193
|
+
"(no truncation is performed)")
|
|
194
|
+
idx = rounded.astype(np.int64)
|
|
195
|
+
|
|
196
|
+
flat = np.atleast_1d(idx).ravel()
|
|
197
|
+
resolved = []
|
|
198
|
+
for i in flat.tolist():
|
|
199
|
+
j = i + size if i < 0 else i
|
|
200
|
+
if not 0 <= j < size:
|
|
201
|
+
raise IndexError(
|
|
202
|
+
f"basis-function index {i} is out of range for a function set "
|
|
203
|
+
f"of size {size} (valid: {-size} .. {size - 1})")
|
|
204
|
+
resolved.append(int(j))
|
|
205
|
+
return resolved
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def check_range(x, xmin, xmax):
|
|
209
|
+
"""Checks each element is in range [xmin, xmax]"""
|
|
210
|
+
x = np.asarray(x)
|
|
211
|
+
if not (x >= xmin).all():
|
|
212
|
+
raise ValueError(f"Some x violate lower bound {xmin}")
|
|
213
|
+
if not (x <= xmax).all():
|
|
214
|
+
raise ValueError(f"Some x violate upper bound {xmax}")
|
|
215
|
+
return x
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def normalize_tau(statistics, tau, beta):
|
|
219
|
+
"""Normalize τ to [0, β] with statistics-dependent periodicity.
|
|
220
|
+
|
|
221
|
+
Handles boundary conditions based on statistics:
|
|
222
|
+
- Fermions ('F'): Anti-periodic G(τ + β) = -G(τ)
|
|
223
|
+
- Bosons ('B'): Periodic G(τ + β) = G(τ)
|
|
224
|
+
|
|
225
|
+
This function maps τ values from the range [-β, β] to [0, β] with
|
|
226
|
+
appropriate sign factors, following the periodicity rules.
|
|
227
|
+
|
|
228
|
+
Arguments:
|
|
229
|
+
statistics (str):
|
|
230
|
+
'F' for Fermionic or 'B' for Bosonic statistics.
|
|
231
|
+
tau (array_like):
|
|
232
|
+
Imaginary time value(s) in range [-β, β].
|
|
233
|
+
beta (float):
|
|
234
|
+
Inverse temperature.
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
tuple[ndarray, ndarray]:
|
|
238
|
+
(tau_normalized, sign) where:
|
|
239
|
+
- tau_normalized: τ values mapped to [0, β]
|
|
240
|
+
- sign: Sign factor (±1) for periodicity
|
|
241
|
+
|
|
242
|
+
Raises:
|
|
243
|
+
ValueError: If tau is outside [-β, β] or statistics is invalid.
|
|
244
|
+
|
|
245
|
+
Special cases:
|
|
246
|
+
- Negative zero (τ = -0.0) is treated as τ = β with appropriate sign
|
|
247
|
+
- For τ in [0, β]: returns (τ, +1)
|
|
248
|
+
- For τ in [-β, 0): returns (τ + β, sign) where sign depends on statistics
|
|
249
|
+
|
|
250
|
+
.. versionadded:: 1.2
|
|
251
|
+
"""
|
|
252
|
+
tau = np.asarray(tau, dtype=np.float64)
|
|
253
|
+
beta = float(beta)
|
|
254
|
+
|
|
255
|
+
if statistics not in ('F', 'B'):
|
|
256
|
+
raise ValueError("statistics must be 'F' (Fermionic) or 'B' (Bosonic)")
|
|
257
|
+
|
|
258
|
+
if np.any(tau < -beta) or np.any(tau > beta):
|
|
259
|
+
raise ValueError(f"τ must be in [-β, β] = [{-beta}, {beta}]")
|
|
260
|
+
|
|
261
|
+
# Handle negative zero: τ = -0.0 → τ = β
|
|
262
|
+
is_neg_zero = (tau == 0.0) & np.signbit(tau)
|
|
263
|
+
|
|
264
|
+
tau_normalized = np.where(is_neg_zero, beta, tau)
|
|
265
|
+
sign = np.ones_like(tau, dtype=np.float64)
|
|
266
|
+
|
|
267
|
+
if statistics == 'F':
|
|
268
|
+
# Fermionic: anti-periodic
|
|
269
|
+
sign = np.where(is_neg_zero, -1.0, sign)
|
|
270
|
+
else: # statistics == 'B'
|
|
271
|
+
# Bosonic: periodic
|
|
272
|
+
sign = np.where(is_neg_zero, 1.0, sign)
|
|
273
|
+
|
|
274
|
+
# Normalize negative tau to [0, β]
|
|
275
|
+
mask_neg = tau_normalized < 0
|
|
276
|
+
tau_normalized = np.where(mask_neg, tau_normalized + beta, tau_normalized)
|
|
277
|
+
|
|
278
|
+
if statistics == 'F':
|
|
279
|
+
sign = np.where(mask_neg, -sign, sign)
|
|
280
|
+
|
|
281
|
+
return tau_normalized, sign
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def check_svd_result(svd_result, matrix_shape=None):
|
|
285
|
+
"""Checks that argument is a valid SVD triple (u, s, vH)"""
|
|
286
|
+
u, s, vH = map(np.asarray, svd_result)
|
|
287
|
+
m_u, k_u = u.shape
|
|
288
|
+
k_s, = s.shape
|
|
289
|
+
k_v, n_v = vH.shape
|
|
290
|
+
if k_u != k_s or k_s != k_v:
|
|
291
|
+
raise ValueError("shape mismatch between SVD elements:"
|
|
292
|
+
f"({m_u}, {k_u}) x ({k_s}) x ({k_v}, {n_v})")
|
|
293
|
+
if matrix_shape is not None:
|
|
294
|
+
m, n = matrix_shape
|
|
295
|
+
if m_u != m or n_v != n:
|
|
296
|
+
raise ValueError(f"shape mismatch between SVD ({m_u}, {n_v}) "
|
|
297
|
+
f"and matrix ({m}, {n})")
|
|
298
|
+
return u, s, vH
|
|
@@ -77,7 +77,7 @@ class AugmentedBasis(abstract.AbstractBasis):
|
|
|
77
77
|
return self._basis.statistics
|
|
78
78
|
|
|
79
79
|
def __getitem__(self, index):
|
|
80
|
-
stop =
|
|
80
|
+
stop = _slice_to_size(index, self.size)
|
|
81
81
|
if stop <= self._naug:
|
|
82
82
|
raise ValueError("Cannot truncate to only augmentation")
|
|
83
83
|
return AugmentedBasis(self._basis[:stop - self._naug],
|
|
@@ -202,7 +202,7 @@ class _AugmentedFunction:
|
|
|
202
202
|
def __getitem__(self, l):
|
|
203
203
|
# TODO make this more general
|
|
204
204
|
if isinstance(l, slice):
|
|
205
|
-
stop =
|
|
205
|
+
stop = _slice_to_size(l, self.size)
|
|
206
206
|
if stop <= self._naug:
|
|
207
207
|
raise NotImplementedError("Don't truncate to only augmentation")
|
|
208
208
|
return _AugmentedFunction(self._fbasis[:stop-self._naug], self._faug)
|
|
@@ -221,13 +221,13 @@ class AugmentedTauFunction(_AugmentedFunction):
|
|
|
221
221
|
|
|
222
222
|
@property
|
|
223
223
|
def xmax(self):
|
|
224
|
-
return self._fbasis.
|
|
224
|
+
return self._fbasis.xmax
|
|
225
225
|
|
|
226
226
|
def deriv(self, n=1):
|
|
227
227
|
"""Get polynomial for the n'th derivative"""
|
|
228
228
|
dbasis = self._fbasis.deriv(n)
|
|
229
229
|
daug = [faug_l.deriv(n) for faug_l in self._faug]
|
|
230
|
-
return AugmentedTauFunction(dbasis,
|
|
230
|
+
return AugmentedTauFunction(dbasis, daug)
|
|
231
231
|
|
|
232
232
|
|
|
233
233
|
class AugmentedMatsubaraFunction(_AugmentedFunction):
|
|
@@ -291,6 +291,12 @@ class TauConst(AbstractAugmentation):
|
|
|
291
291
|
raise ValueError("temperature must be positive")
|
|
292
292
|
if statistics not in ('F', 'B'):
|
|
293
293
|
raise ValueError("statistics must be 'F' or 'B'")
|
|
294
|
+
# A fermionic TauConst is not merely ill-conditioned, it is useless:
|
|
295
|
+
# its Fourier transform is sqrt(beta) * (n == 0), and fermionic reduced
|
|
296
|
+
# frequencies are odd, so the augmentation column vanishes identically
|
|
297
|
+
# and the augmented basis is rank-deficient. Refuse instead of
|
|
298
|
+
# silently returning a singular fit.
|
|
299
|
+
_check_bosonic_statistics(statistics)
|
|
294
300
|
self._beta = beta
|
|
295
301
|
self._statistics = statistics
|
|
296
302
|
|
|
@@ -406,6 +412,31 @@ class MatsubaraConst(AbstractAugmentation):
|
|
|
406
412
|
return np.broadcast_to(1.0, n.shape)
|
|
407
413
|
|
|
408
414
|
|
|
415
|
+
def _slice_to_size(index, size):
|
|
416
|
+
"""Return the number of basis functions selected by ``index``.
|
|
417
|
+
|
|
418
|
+
Only ``basis[:stop]``-style truncation is supported, mirroring
|
|
419
|
+
:py:meth:`FiniteTempBasis.__getitem__`.
|
|
420
|
+
"""
|
|
421
|
+
if not isinstance(index, slice):
|
|
422
|
+
raise TypeError(
|
|
423
|
+
f"only slice truncation is supported, got {index!r}")
|
|
424
|
+
if index.start not in (None, 0):
|
|
425
|
+
raise ValueError(
|
|
426
|
+
f"basis truncation must start at 0, got {index.start!r}")
|
|
427
|
+
if index.step not in (None, 1):
|
|
428
|
+
raise ValueError(
|
|
429
|
+
f"basis truncation must have unit step, got {index.step!r}")
|
|
430
|
+
if index.stop is None:
|
|
431
|
+
return size
|
|
432
|
+
stop = int(index.stop)
|
|
433
|
+
if not 0 < stop <= size:
|
|
434
|
+
raise IndexError(
|
|
435
|
+
f"truncation to {stop} functions is out of range for a basis of "
|
|
436
|
+
f"size {size}")
|
|
437
|
+
return stop
|
|
438
|
+
|
|
439
|
+
|
|
409
440
|
def _augmentation_factory(basis, *augs):
|
|
410
441
|
for aug in augs:
|
|
411
442
|
if isinstance(aug, AbstractAugmentation):
|
|
@@ -418,6 +449,10 @@ def _check_bosonic_statistics(statistics):
|
|
|
418
449
|
if statistics == 'B':
|
|
419
450
|
return
|
|
420
451
|
elif statistics == 'F':
|
|
421
|
-
raise ValueError(
|
|
452
|
+
raise ValueError(
|
|
453
|
+
"TauConst augmentation is only allowed for a bosonic basis: for "
|
|
454
|
+
"fermionic statistics its Fourier transform vanishes at every "
|
|
455
|
+
"(odd) reduced Matsubara frequency, which makes the augmented "
|
|
456
|
+
"basis rank-deficient")
|
|
422
457
|
else:
|
|
423
|
-
raise ValueError("invalid statistics")
|
|
458
|
+
raise ValueError(f"invalid statistics {statistics!r}, expected 'F' or 'B'")
|
|
@@ -303,16 +303,16 @@ class FiniteTempBasis(AbstractBasis):
|
|
|
303
303
|
temperature. Note that this implies a different UV cutoff ``wmax``,
|
|
304
304
|
since ``lambda_ == beta * wmax`` stays constant.
|
|
305
305
|
"""
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
new_beta = new_lambda / new_wmax
|
|
311
|
-
|
|
312
|
-
# Get epsilon from the current basis accuracy
|
|
313
|
-
eps = self.accuracy
|
|
306
|
+
new_beta = float(new_beta)
|
|
307
|
+
if not new_beta > 0:
|
|
308
|
+
raise ValueError(
|
|
309
|
+
f"inverse temperature must be positive, got {new_beta!r}")
|
|
314
310
|
|
|
315
|
-
|
|
311
|
+
# lambda_ == beta * wmax is held fixed, so the SVE (which depends only
|
|
312
|
+
# on lambda_ and eps) can be reused as is.
|
|
313
|
+
new_wmax = self._lambda / new_beta
|
|
314
|
+
return FiniteTempBasis(self.statistics, new_beta, new_wmax, self._eps,
|
|
315
|
+
kernel=self._kernel, sve_result=self._sve)
|
|
316
316
|
|
|
317
317
|
|
|
318
318
|
def finite_temp_bases(beta, wmax, eps=None, sve_result=None):
|
|
@@ -11,8 +11,22 @@ import ctypes
|
|
|
11
11
|
import numpy as np
|
|
12
12
|
from .abstract import AbstractBasis
|
|
13
13
|
from pylibsparseir.core import basis_get_default_omega_sampling_points
|
|
14
|
-
from pylibsparseir.core import
|
|
14
|
+
from pylibsparseir.core import (
|
|
15
|
+
_lib,
|
|
16
|
+
COMPUTATION_SUCCESS,
|
|
17
|
+
get_default_blas_backend,
|
|
18
|
+
c_double_complex,
|
|
19
|
+
basis_get_u,
|
|
20
|
+
basis_get_uhat,
|
|
21
|
+
)
|
|
15
22
|
from pylibsparseir.constants import SPIR_ORDER_ROW_MAJOR
|
|
23
|
+
from . import _util
|
|
24
|
+
from .poly import (
|
|
25
|
+
FunctionSet,
|
|
26
|
+
FunctionSetFT,
|
|
27
|
+
PiecewiseLegendrePolyVector,
|
|
28
|
+
PiecewiseLegendrePolyFTVector,
|
|
29
|
+
)
|
|
16
30
|
|
|
17
31
|
class DiscreteLehmannRepresentation(AbstractBasis):
|
|
18
32
|
"""Discrete Lehmann representation (DLR), with poles being extrema of IR.
|
|
@@ -41,18 +55,65 @@ class DiscreteLehmannRepresentation(AbstractBasis):
|
|
|
41
55
|
status = ctypes.c_int()
|
|
42
56
|
if poles is None:
|
|
43
57
|
poles = basis_get_default_omega_sampling_points(basis._ptr)
|
|
58
|
+
# Normalize first, then take the pointer from the *normalized* object.
|
|
59
|
+
# Taking it from the caller's array instead silently hands C the
|
|
60
|
+
# buffer of a non-contiguous or non-float64 array.
|
|
61
|
+
poles = _util.as_boundary_real(poles, "poles")
|
|
62
|
+
if poles.ndim != 1:
|
|
63
|
+
raise ValueError(
|
|
64
|
+
f"poles must be one-dimensional, got shape {poles.shape}")
|
|
65
|
+
if poles.size == 0:
|
|
66
|
+
raise ValueError("poles must not be empty")
|
|
44
67
|
self._basis = basis
|
|
45
|
-
self._poles =
|
|
68
|
+
self._poles = poles
|
|
69
|
+
self._u = None
|
|
70
|
+
self._uhat = None
|
|
46
71
|
self._backend = get_default_blas_backend()
|
|
47
|
-
self._ptr = _lib.spir_dlr_new_with_poles(
|
|
72
|
+
self._ptr = _lib.spir_dlr_new_with_poles(
|
|
73
|
+
basis._ptr,
|
|
74
|
+
poles.size,
|
|
75
|
+
poles.ctypes.data_as(ctypes.POINTER(ctypes.c_double)),
|
|
76
|
+
status,
|
|
77
|
+
)
|
|
48
78
|
if status.value != COMPUTATION_SUCCESS:
|
|
49
79
|
raise RuntimeError(f"Failed to create DLR basis: {status.value}")
|
|
80
|
+
if not self._ptr:
|
|
81
|
+
raise RuntimeError("Failed to create DLR basis: null handle")
|
|
50
82
|
|
|
51
83
|
@property
|
|
52
|
-
def u(self):
|
|
84
|
+
def u(self):
|
|
85
|
+
r"""DLR basis functions on the imaginary-time axis.
|
|
86
|
+
|
|
87
|
+
These are the *DLR* basis functions, i.e. ``u[i](tau)`` is the
|
|
88
|
+
imaginary-time kernel evaluated at the ``i``-th pole, so that::
|
|
89
|
+
|
|
90
|
+
gtau == g_dlr @ dlr.u(tau)
|
|
91
|
+
|
|
92
|
+
holds for DLR coefficients ``g_dlr``. They are **not** the basis
|
|
93
|
+
functions of the underlying IR basis.
|
|
94
|
+
"""
|
|
95
|
+
if self._u is None:
|
|
96
|
+
beta = self._basis.beta
|
|
97
|
+
self._u = PiecewiseLegendrePolyVector(
|
|
98
|
+
FunctionSet(basis_get_u(self._ptr)),
|
|
99
|
+
-beta, beta, beta, default_overlap_range=(0, beta))
|
|
100
|
+
return self._u
|
|
53
101
|
|
|
54
102
|
@property
|
|
55
|
-
def uhat(self):
|
|
103
|
+
def uhat(self):
|
|
104
|
+
r"""DLR basis functions on the reduced Matsubara frequency axis.
|
|
105
|
+
|
|
106
|
+
``uhat[i](n)`` is the Fourier transform of :py:attr:`u`, so that::
|
|
107
|
+
|
|
108
|
+
giv == g_dlr @ dlr.uhat(n)
|
|
109
|
+
|
|
110
|
+
holds for DLR coefficients ``g_dlr``. They are **not** the Matsubara
|
|
111
|
+
basis functions of the underlying IR basis.
|
|
112
|
+
"""
|
|
113
|
+
if self._uhat is None:
|
|
114
|
+
self._uhat = PiecewiseLegendrePolyFTVector(
|
|
115
|
+
FunctionSetFT(basis_get_uhat(self._ptr)))
|
|
116
|
+
return self._uhat
|
|
56
117
|
|
|
57
118
|
@property
|
|
58
119
|
def statistics(self):
|
|
@@ -110,20 +171,26 @@ class DiscreteLehmannRepresentation(AbstractBasis):
|
|
|
110
171
|
array_like
|
|
111
172
|
Expansion coefficients in DLR
|
|
112
173
|
"""
|
|
113
|
-
gl = np.
|
|
174
|
+
gl = np.asarray(gl)
|
|
175
|
+
if gl.ndim == 0:
|
|
176
|
+
raise ValueError("IR coefficients must be at least one-dimensional")
|
|
177
|
+
axis = _util.normalize_axis(axis, gl.ndim)
|
|
114
178
|
if gl.shape[axis] != self.basis.size:
|
|
115
|
-
raise ValueError(
|
|
179
|
+
raise ValueError(
|
|
180
|
+
f"IR coefficients have length {gl.shape[axis]} along axis "
|
|
181
|
+
f"{axis}, expected {self.basis.size}")
|
|
116
182
|
|
|
117
183
|
output_dims = list(gl.shape)
|
|
118
184
|
output_dims[axis] = self.size
|
|
119
|
-
output = np.zeros(output_dims, dtype=gl.dtype)
|
|
120
185
|
|
|
121
|
-
ndim =
|
|
122
|
-
input_dims = np.
|
|
186
|
+
ndim = gl.ndim
|
|
187
|
+
input_dims = np.ascontiguousarray(gl.shape, dtype=np.int32)
|
|
123
188
|
target_dim = axis
|
|
124
189
|
order = SPIR_ORDER_ROW_MAJOR
|
|
125
190
|
|
|
126
|
-
if gl.dtype.kind
|
|
191
|
+
if gl.dtype.kind != 'c':
|
|
192
|
+
gl = _util.as_boundary_real(gl, "IR coefficients")
|
|
193
|
+
output = np.zeros(output_dims, dtype=np.float64)
|
|
127
194
|
ret = _lib.spir_ir2dlr_dd(
|
|
128
195
|
self._ptr,
|
|
129
196
|
self._backend,
|
|
@@ -134,7 +201,9 @@ class DiscreteLehmannRepresentation(AbstractBasis):
|
|
|
134
201
|
gl.ctypes.data_as(ctypes.POINTER(ctypes.c_double)),
|
|
135
202
|
output.ctypes.data_as(ctypes.POINTER(ctypes.c_double)),
|
|
136
203
|
)
|
|
137
|
-
|
|
204
|
+
else:
|
|
205
|
+
gl = _util.as_boundary_complex(gl, "IR coefficients")
|
|
206
|
+
output_c = np.zeros(output_dims, dtype=c_double_complex)
|
|
138
207
|
ret = _lib.spir_ir2dlr_zz(
|
|
139
208
|
self._ptr,
|
|
140
209
|
self._backend,
|
|
@@ -142,12 +211,10 @@ class DiscreteLehmannRepresentation(AbstractBasis):
|
|
|
142
211
|
ndim,
|
|
143
212
|
input_dims.ctypes.data_as(ctypes.POINTER(ctypes.c_int32)),
|
|
144
213
|
target_dim,
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
output.ctypes.data_as(ctypes.POINTER(ctypes.c_double)),
|
|
214
|
+
gl.ctypes.data_as(ctypes.POINTER(c_double_complex)),
|
|
215
|
+
output_c.ctypes.data_as(ctypes.POINTER(c_double_complex)),
|
|
148
216
|
)
|
|
149
|
-
|
|
150
|
-
raise ValueError(f"Unsupported dtype: {gl.dtype}")
|
|
217
|
+
output = output_c['real'] + 1j * output_c['imag']
|
|
151
218
|
if ret != COMPUTATION_SUCCESS:
|
|
152
219
|
raise RuntimeError(f"Failed to convert IR to DLR: {ret}")
|
|
153
220
|
return output
|
|
@@ -169,18 +236,24 @@ class DiscreteLehmannRepresentation(AbstractBasis):
|
|
|
169
236
|
array_like
|
|
170
237
|
Expansion coefficients in IR
|
|
171
238
|
"""
|
|
172
|
-
g_dlr = np.
|
|
239
|
+
g_dlr = np.asarray(g_dlr)
|
|
240
|
+
if g_dlr.ndim == 0:
|
|
241
|
+
raise ValueError("DLR coefficients must be at least one-dimensional")
|
|
242
|
+
axis = _util.normalize_axis(axis, g_dlr.ndim)
|
|
173
243
|
if g_dlr.shape[axis] != self.size:
|
|
174
|
-
raise ValueError(
|
|
175
|
-
|
|
244
|
+
raise ValueError(
|
|
245
|
+
f"DLR coefficients have length {g_dlr.shape[axis]} along axis "
|
|
246
|
+
f"{axis}, expected {self.size}")
|
|
247
|
+
output_dims = list(g_dlr.shape)
|
|
176
248
|
output_dims[axis] = self.basis.size
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
input_dims = np.asarray(g_dlr.shape, dtype=np.int32)
|
|
249
|
+
ndim = g_dlr.ndim
|
|
250
|
+
input_dims = np.ascontiguousarray(g_dlr.shape, dtype=np.int32)
|
|
180
251
|
target_dim = axis
|
|
181
252
|
order = SPIR_ORDER_ROW_MAJOR
|
|
182
253
|
|
|
183
|
-
if g_dlr.dtype.kind
|
|
254
|
+
if g_dlr.dtype.kind != 'c':
|
|
255
|
+
g_dlr = _util.as_boundary_real(g_dlr, "DLR coefficients")
|
|
256
|
+
output = np.zeros(output_dims, dtype=np.float64)
|
|
184
257
|
ret = _lib.spir_dlr2ir_dd(
|
|
185
258
|
self._ptr,
|
|
186
259
|
self._backend,
|
|
@@ -191,7 +264,9 @@ class DiscreteLehmannRepresentation(AbstractBasis):
|
|
|
191
264
|
g_dlr.ctypes.data_as(ctypes.POINTER(ctypes.c_double)),
|
|
192
265
|
output.ctypes.data_as(ctypes.POINTER(ctypes.c_double)),
|
|
193
266
|
)
|
|
194
|
-
|
|
267
|
+
else:
|
|
268
|
+
g_dlr = _util.as_boundary_complex(g_dlr, "DLR coefficients")
|
|
269
|
+
output_c = np.zeros(output_dims, dtype=c_double_complex)
|
|
195
270
|
ret = _lib.spir_dlr2ir_zz(
|
|
196
271
|
self._ptr,
|
|
197
272
|
self._backend,
|
|
@@ -199,12 +274,10 @@ class DiscreteLehmannRepresentation(AbstractBasis):
|
|
|
199
274
|
ndim,
|
|
200
275
|
input_dims.ctypes.data_as(ctypes.POINTER(ctypes.c_int)),
|
|
201
276
|
target_dim,
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
output.ctypes.data_as(ctypes.POINTER(ctypes.c_double)),
|
|
277
|
+
g_dlr.ctypes.data_as(ctypes.POINTER(c_double_complex)),
|
|
278
|
+
output_c.ctypes.data_as(ctypes.POINTER(c_double_complex)),
|
|
205
279
|
)
|
|
206
|
-
|
|
207
|
-
raise ValueError(f"Unsupported dtype: {g_dlr.dtype}")
|
|
280
|
+
output = output_c['real'] + 1j * output_c['imag']
|
|
208
281
|
if ret != COMPUTATION_SUCCESS:
|
|
209
282
|
raise RuntimeError(f"Failed to convert DLR to IR: {ret}")
|
|
210
283
|
return output
|