sparse-ir 2.1.2__tar.gz → 2.1.3__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.3}/PKG-INFO +1 -1
- {sparse_ir-2.1.2 → sparse_ir-2.1.3}/pyproject.toml +1 -1
- {sparse_ir-2.1.2 → sparse_ir-2.1.3}/src/sparse_ir/dlr.py +18 -9
- {sparse_ir-2.1.2 → sparse_ir-2.1.3}/src/sparse_ir/sampling.py +10 -1
- {sparse_ir-2.1.2 → sparse_ir-2.1.3/src/sparse_ir.egg-info}/PKG-INFO +1 -1
- {sparse_ir-2.1.2 → sparse_ir-2.1.3}/tests/test_dlr.py +34 -0
- {sparse_ir-2.1.2 → sparse_ir-2.1.3}/tests/test_sampling.py +50 -0
- {sparse_ir-2.1.2 → sparse_ir-2.1.3}/LICENSE.txt +0 -0
- {sparse_ir-2.1.2 → sparse_ir-2.1.3}/MANIFEST.in +0 -0
- {sparse_ir-2.1.2 → sparse_ir-2.1.3}/README.rst +0 -0
- {sparse_ir-2.1.2 → sparse_ir-2.1.3}/setup.cfg +0 -0
- {sparse_ir-2.1.2 → sparse_ir-2.1.3}/src/sparse_ir/__init__.py +0 -0
- {sparse_ir-2.1.2 → sparse_ir-2.1.3}/src/sparse_ir/_gauss.py +0 -0
- {sparse_ir-2.1.2 → sparse_ir-2.1.3}/src/sparse_ir/_util.py +0 -0
- {sparse_ir-2.1.2 → sparse_ir-2.1.3}/src/sparse_ir/abstract.py +0 -0
- {sparse_ir-2.1.2 → sparse_ir-2.1.3}/src/sparse_ir/augment.py +0 -0
- {sparse_ir-2.1.2 → sparse_ir-2.1.3}/src/sparse_ir/basis.py +0 -0
- {sparse_ir-2.1.2 → sparse_ir-2.1.3}/src/sparse_ir/basis_set.py +0 -0
- {sparse_ir-2.1.2 → sparse_ir-2.1.3}/src/sparse_ir/kernel.py +0 -0
- {sparse_ir-2.1.2 → sparse_ir-2.1.3}/src/sparse_ir/poly.py +0 -0
- {sparse_ir-2.1.2 → sparse_ir-2.1.3}/src/sparse_ir/sve.py +0 -0
- {sparse_ir-2.1.2 → sparse_ir-2.1.3}/src/sparse_ir.egg-info/SOURCES.txt +0 -0
- {sparse_ir-2.1.2 → sparse_ir-2.1.3}/src/sparse_ir.egg-info/dependency_links.txt +0 -0
- {sparse_ir-2.1.2 → sparse_ir-2.1.3}/src/sparse_ir.egg-info/requires.txt +0 -0
- {sparse_ir-2.1.2 → sparse_ir-2.1.3}/src/sparse_ir.egg-info/top_level.txt +0 -0
- {sparse_ir-2.1.2 → sparse_ir-2.1.3}/tests/test_advanced_features.py +0 -0
- {sparse_ir-2.1.2 → sparse_ir-2.1.3}/tests/test_augment.py +0 -0
- {sparse_ir-2.1.2 → sparse_ir-2.1.3}/tests/test_basis.py +0 -0
- {sparse_ir-2.1.2 → sparse_ir-2.1.3}/tests/test_basis_set.py +0 -0
- {sparse_ir-2.1.2 → sparse_ir-2.1.3}/tests/test_core.py +0 -0
- {sparse_ir-2.1.2 → sparse_ir-2.1.3}/tests/test_kernel.py +0 -0
- {sparse_ir-2.1.2 → sparse_ir-2.1.3}/tests/test_poly.py +0 -0
- {sparse_ir-2.1.2 → sparse_ir-2.1.3}/tests/test_sampling_advanced.py +0 -0
- {sparse_ir-2.1.2 → sparse_ir-2.1.3}/tests/test_sve.py +0 -0
- {sparse_ir-2.1.2 → sparse_ir-2.1.3}/tests/test_sve_advanced.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "sparse-ir"
|
|
3
|
-
version = "2.1.
|
|
3
|
+
version = "2.1.3"
|
|
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"
|
|
@@ -11,7 +11,12 @@ 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
|
+
)
|
|
15
20
|
from pylibsparseir.constants import SPIR_ORDER_ROW_MAJOR
|
|
16
21
|
|
|
17
22
|
class DiscreteLehmannRepresentation(AbstractBasis):
|
|
@@ -116,7 +121,6 @@ class DiscreteLehmannRepresentation(AbstractBasis):
|
|
|
116
121
|
|
|
117
122
|
output_dims = list(gl.shape)
|
|
118
123
|
output_dims[axis] = self.size
|
|
119
|
-
output = np.zeros(output_dims, dtype=gl.dtype)
|
|
120
124
|
|
|
121
125
|
ndim = len(gl.shape)
|
|
122
126
|
input_dims = np.asarray(gl.shape, dtype=np.int32)
|
|
@@ -124,6 +128,7 @@ class DiscreteLehmannRepresentation(AbstractBasis):
|
|
|
124
128
|
order = SPIR_ORDER_ROW_MAJOR
|
|
125
129
|
|
|
126
130
|
if gl.dtype.kind == 'f':
|
|
131
|
+
output = np.zeros(output_dims, dtype=np.float64)
|
|
127
132
|
ret = _lib.spir_ir2dlr_dd(
|
|
128
133
|
self._ptr,
|
|
129
134
|
self._backend,
|
|
@@ -135,6 +140,8 @@ class DiscreteLehmannRepresentation(AbstractBasis):
|
|
|
135
140
|
output.ctypes.data_as(ctypes.POINTER(ctypes.c_double)),
|
|
136
141
|
)
|
|
137
142
|
elif gl.dtype.kind == 'c':
|
|
143
|
+
gl = np.ascontiguousarray(gl, dtype=np.complex128)
|
|
144
|
+
output_c = np.zeros(output_dims, dtype=c_double_complex)
|
|
138
145
|
ret = _lib.spir_ir2dlr_zz(
|
|
139
146
|
self._ptr,
|
|
140
147
|
self._backend,
|
|
@@ -142,10 +149,10 @@ class DiscreteLehmannRepresentation(AbstractBasis):
|
|
|
142
149
|
ndim,
|
|
143
150
|
input_dims.ctypes.data_as(ctypes.POINTER(ctypes.c_int32)),
|
|
144
151
|
target_dim,
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
output.ctypes.data_as(ctypes.POINTER(ctypes.c_double)),
|
|
152
|
+
gl.ctypes.data_as(ctypes.POINTER(c_double_complex)),
|
|
153
|
+
output_c.ctypes.data_as(ctypes.POINTER(c_double_complex)),
|
|
148
154
|
)
|
|
155
|
+
output = output_c['real'] + 1j * output_c['imag']
|
|
149
156
|
else:
|
|
150
157
|
raise ValueError(f"Unsupported dtype: {gl.dtype}")
|
|
151
158
|
if ret != COMPUTATION_SUCCESS:
|
|
@@ -174,13 +181,13 @@ class DiscreteLehmannRepresentation(AbstractBasis):
|
|
|
174
181
|
raise ValueError(f"Input array has wrong size along dimension {axis}")
|
|
175
182
|
output_dims = np.asarray(g_dlr.shape, dtype=np.int32)
|
|
176
183
|
output_dims[axis] = self.basis.size
|
|
177
|
-
output = np.zeros(output_dims, dtype=g_dlr.dtype)
|
|
178
184
|
ndim = len(g_dlr.shape)
|
|
179
185
|
input_dims = np.asarray(g_dlr.shape, dtype=np.int32)
|
|
180
186
|
target_dim = axis
|
|
181
187
|
order = SPIR_ORDER_ROW_MAJOR
|
|
182
188
|
|
|
183
189
|
if g_dlr.dtype.kind == 'f':
|
|
190
|
+
output = np.zeros(output_dims, dtype=np.float64)
|
|
184
191
|
ret = _lib.spir_dlr2ir_dd(
|
|
185
192
|
self._ptr,
|
|
186
193
|
self._backend,
|
|
@@ -192,6 +199,8 @@ class DiscreteLehmannRepresentation(AbstractBasis):
|
|
|
192
199
|
output.ctypes.data_as(ctypes.POINTER(ctypes.c_double)),
|
|
193
200
|
)
|
|
194
201
|
elif g_dlr.dtype.kind == 'c':
|
|
202
|
+
g_dlr = np.ascontiguousarray(g_dlr, dtype=np.complex128)
|
|
203
|
+
output_c = np.zeros(output_dims, dtype=c_double_complex)
|
|
195
204
|
ret = _lib.spir_dlr2ir_zz(
|
|
196
205
|
self._ptr,
|
|
197
206
|
self._backend,
|
|
@@ -199,10 +208,10 @@ class DiscreteLehmannRepresentation(AbstractBasis):
|
|
|
199
208
|
ndim,
|
|
200
209
|
input_dims.ctypes.data_as(ctypes.POINTER(ctypes.c_int)),
|
|
201
210
|
target_dim,
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
output.ctypes.data_as(ctypes.POINTER(ctypes.c_double)),
|
|
211
|
+
g_dlr.ctypes.data_as(ctypes.POINTER(c_double_complex)),
|
|
212
|
+
output_c.ctypes.data_as(ctypes.POINTER(c_double_complex)),
|
|
205
213
|
)
|
|
214
|
+
output = output_c['real'] + 1j * output_c['imag']
|
|
206
215
|
else:
|
|
207
216
|
raise ValueError(f"Unsupported dtype: {g_dlr.dtype}")
|
|
208
217
|
if ret != COMPUTATION_SUCCESS:
|
|
@@ -313,7 +313,16 @@ class MatsubaraSampling:
|
|
|
313
313
|
"""
|
|
314
314
|
Fit basis coefficients from Matsubara frequency values.
|
|
315
315
|
"""
|
|
316
|
-
ax = np.
|
|
316
|
+
ax = np.asarray(ax)
|
|
317
|
+
if ax.dtype.kind not in ("f", "c"):
|
|
318
|
+
raise ValueError(f"Unsupported dtype: {ax.dtype}")
|
|
319
|
+
# The underlying C entry point (spir_sampling_fit_zz) always expects
|
|
320
|
+
# complex128 data. Real-valued input must be normalized to
|
|
321
|
+
# complex128 here; otherwise the raw buffer of a float64 array would
|
|
322
|
+
# be reinterpreted as complex128 (reading twice as many bytes as
|
|
323
|
+
# were allocated), producing an out-of-bounds read and silent
|
|
324
|
+
# garbage output instead of a clear error.
|
|
325
|
+
ax = np.ascontiguousarray(ax, dtype=np.complex128)
|
|
317
326
|
ndim = len(ax.shape)
|
|
318
327
|
input_dims = np.asarray(ax.shape, dtype=np.int32)
|
|
319
328
|
output_dims = list(ax.shape)
|
|
@@ -76,3 +76,37 @@ def test_boson():
|
|
|
76
76
|
gl_pole2 = sp.to_IR(coeff)
|
|
77
77
|
|
|
78
78
|
np.testing.assert_allclose(gl_pole, gl_pole2, atol=300*eps, rtol=0)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def test_complex_roundtrip():
|
|
82
|
+
"""Regression test: from_IR/to_IR must support complex128 coefficients
|
|
83
|
+
(e.g. off-diagonal Green's functions) instead of raising a ctypes
|
|
84
|
+
TypeError when the complex path is exercised.
|
|
85
|
+
|
|
86
|
+
Mirrors test_compression's poles-based construction (so that the IR
|
|
87
|
+
coefficients actually lie in the DLR's representable subspace and the
|
|
88
|
+
round trip is expected to be near-exact), but with complex coefficients.
|
|
89
|
+
"""
|
|
90
|
+
beta = 10_000
|
|
91
|
+
wmax = 1
|
|
92
|
+
eps = 1e-12
|
|
93
|
+
basis = FiniteTempBasis("F", beta, wmax, eps=eps)
|
|
94
|
+
|
|
95
|
+
dlr = DiscreteLehmannRepresentation(basis)
|
|
96
|
+
|
|
97
|
+
rng = np.random.default_rng(42)
|
|
98
|
+
num_poles = 10
|
|
99
|
+
poles = wmax * (2 * rng.random(num_poles) - 1)
|
|
100
|
+
coeffs = (2 * rng.random(num_poles) - 1) + 1j * (2 * rng.random(num_poles) - 1)
|
|
101
|
+
|
|
102
|
+
Gl = DiscreteLehmannRepresentation(basis, poles).to_IR(coeffs)
|
|
103
|
+
assert np.iscomplexobj(Gl)
|
|
104
|
+
|
|
105
|
+
g_dlr = dlr.from_IR(Gl)
|
|
106
|
+
assert np.iscomplexobj(g_dlr)
|
|
107
|
+
|
|
108
|
+
Gl_recovered = dlr.to_IR(g_dlr)
|
|
109
|
+
assert np.iscomplexobj(Gl_recovered)
|
|
110
|
+
|
|
111
|
+
np.testing.assert_allclose(Gl, Gl_recovered, atol=300 * eps, rtol=0)
|
|
112
|
+
np.testing.assert_allclose(Gl.imag, Gl_recovered.imag, atol=300 * eps, rtol=0)
|
|
@@ -56,6 +56,21 @@ class TestTauSampling:
|
|
|
56
56
|
error = np.max(np.abs(al_original - al_recovered))
|
|
57
57
|
assert error < 1e-12, f"Roundtrip error too large: {error}"
|
|
58
58
|
|
|
59
|
+
def test_evaluate_fit_roundtrip_complex(self, basis):
|
|
60
|
+
"""Complex coefficients (e.g. off-diagonal G) must roundtrip with
|
|
61
|
+
their imaginary part intact."""
|
|
62
|
+
sampling = sparse_ir.TauSampling(basis)
|
|
63
|
+
rng = np.random.default_rng(42)
|
|
64
|
+
al_original = rng.normal(size=basis.size) + 1j * rng.normal(size=basis.size)
|
|
65
|
+
|
|
66
|
+
ax = sampling.evaluate(al_original)
|
|
67
|
+
assert np.iscomplexobj(ax)
|
|
68
|
+
al_recovered = sampling.fit(ax)
|
|
69
|
+
|
|
70
|
+
assert np.iscomplexobj(al_recovered)
|
|
71
|
+
assert np.max(np.abs(al_original - al_recovered)) < 1e-12
|
|
72
|
+
assert np.max(np.abs(al_original.imag - al_recovered.imag)) < 1e-12
|
|
73
|
+
|
|
59
74
|
def test_evaluate_shape(self, basis):
|
|
60
75
|
"""Test evaluate output shape."""
|
|
61
76
|
sampling = sparse_ir.TauSampling(basis)
|
|
@@ -114,6 +129,41 @@ class TestMatsubaraSampling:
|
|
|
114
129
|
assert len(sampling.wn) == len(custom_wn)
|
|
115
130
|
np.testing.assert_array_equal(sampling.wn, custom_wn)
|
|
116
131
|
|
|
132
|
+
def test_evaluate_fit_roundtrip_complex(self, basis):
|
|
133
|
+
"""Complex coefficients (e.g. off-diagonal G) must roundtrip with
|
|
134
|
+
their imaginary part intact."""
|
|
135
|
+
sampling = sparse_ir.MatsubaraSampling(basis)
|
|
136
|
+
rng = np.random.default_rng(42)
|
|
137
|
+
al_original = rng.normal(size=basis.size) + 1j * rng.normal(size=basis.size)
|
|
138
|
+
|
|
139
|
+
ax = sampling.evaluate(al_original)
|
|
140
|
+
al_recovered = sampling.fit(ax)
|
|
141
|
+
|
|
142
|
+
assert np.iscomplexobj(al_recovered)
|
|
143
|
+
assert np.max(np.abs(al_original - al_recovered)) < 1e-12
|
|
144
|
+
assert np.max(np.abs(al_original.imag - al_recovered.imag)) < 1e-12
|
|
145
|
+
|
|
146
|
+
def test_fit_real_input_matches_complex_reference(self, basis):
|
|
147
|
+
"""Regression test: MatsubaraSampling.fit with real float64 input
|
|
148
|
+
must not be silently misread as complex128 data (out-of-bounds
|
|
149
|
+
read producing garbage). The real-input result must agree with
|
|
150
|
+
the complex128 reference obtained by fitting the same values cast
|
|
151
|
+
to complex128."""
|
|
152
|
+
sampling = sparse_ir.MatsubaraSampling(basis)
|
|
153
|
+
rng = np.random.default_rng(42)
|
|
154
|
+
al_original = rng.normal(size=basis.size)
|
|
155
|
+
|
|
156
|
+
ax = sampling.evaluate(al_original)
|
|
157
|
+
# ax is complex (Matsubara values are always complex); take the
|
|
158
|
+
# real part to exercise the real-dtype input code path of fit().
|
|
159
|
+
ax_real = np.ascontiguousarray(ax.real)
|
|
160
|
+
ax_complex_ref = np.ascontiguousarray(ax_real, dtype=np.complex128)
|
|
161
|
+
|
|
162
|
+
al_from_real = sampling.fit(ax_real)
|
|
163
|
+
al_from_complex_ref = sampling.fit(ax_complex_ref)
|
|
164
|
+
|
|
165
|
+
assert np.max(np.abs(al_from_real - al_from_complex_ref)) < 1e-12
|
|
166
|
+
|
|
117
167
|
def test_repr(self, basis):
|
|
118
168
|
"""Test string representation."""
|
|
119
169
|
sampling = sparse_ir.MatsubaraSampling(basis)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|