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.
Files changed (35) hide show
  1. {sparse_ir-2.1.2/src/sparse_ir.egg-info → sparse_ir-2.1.3}/PKG-INFO +1 -1
  2. {sparse_ir-2.1.2 → sparse_ir-2.1.3}/pyproject.toml +1 -1
  3. {sparse_ir-2.1.2 → sparse_ir-2.1.3}/src/sparse_ir/dlr.py +18 -9
  4. {sparse_ir-2.1.2 → sparse_ir-2.1.3}/src/sparse_ir/sampling.py +10 -1
  5. {sparse_ir-2.1.2 → sparse_ir-2.1.3/src/sparse_ir.egg-info}/PKG-INFO +1 -1
  6. {sparse_ir-2.1.2 → sparse_ir-2.1.3}/tests/test_dlr.py +34 -0
  7. {sparse_ir-2.1.2 → sparse_ir-2.1.3}/tests/test_sampling.py +50 -0
  8. {sparse_ir-2.1.2 → sparse_ir-2.1.3}/LICENSE.txt +0 -0
  9. {sparse_ir-2.1.2 → sparse_ir-2.1.3}/MANIFEST.in +0 -0
  10. {sparse_ir-2.1.2 → sparse_ir-2.1.3}/README.rst +0 -0
  11. {sparse_ir-2.1.2 → sparse_ir-2.1.3}/setup.cfg +0 -0
  12. {sparse_ir-2.1.2 → sparse_ir-2.1.3}/src/sparse_ir/__init__.py +0 -0
  13. {sparse_ir-2.1.2 → sparse_ir-2.1.3}/src/sparse_ir/_gauss.py +0 -0
  14. {sparse_ir-2.1.2 → sparse_ir-2.1.3}/src/sparse_ir/_util.py +0 -0
  15. {sparse_ir-2.1.2 → sparse_ir-2.1.3}/src/sparse_ir/abstract.py +0 -0
  16. {sparse_ir-2.1.2 → sparse_ir-2.1.3}/src/sparse_ir/augment.py +0 -0
  17. {sparse_ir-2.1.2 → sparse_ir-2.1.3}/src/sparse_ir/basis.py +0 -0
  18. {sparse_ir-2.1.2 → sparse_ir-2.1.3}/src/sparse_ir/basis_set.py +0 -0
  19. {sparse_ir-2.1.2 → sparse_ir-2.1.3}/src/sparse_ir/kernel.py +0 -0
  20. {sparse_ir-2.1.2 → sparse_ir-2.1.3}/src/sparse_ir/poly.py +0 -0
  21. {sparse_ir-2.1.2 → sparse_ir-2.1.3}/src/sparse_ir/sve.py +0 -0
  22. {sparse_ir-2.1.2 → sparse_ir-2.1.3}/src/sparse_ir.egg-info/SOURCES.txt +0 -0
  23. {sparse_ir-2.1.2 → sparse_ir-2.1.3}/src/sparse_ir.egg-info/dependency_links.txt +0 -0
  24. {sparse_ir-2.1.2 → sparse_ir-2.1.3}/src/sparse_ir.egg-info/requires.txt +0 -0
  25. {sparse_ir-2.1.2 → sparse_ir-2.1.3}/src/sparse_ir.egg-info/top_level.txt +0 -0
  26. {sparse_ir-2.1.2 → sparse_ir-2.1.3}/tests/test_advanced_features.py +0 -0
  27. {sparse_ir-2.1.2 → sparse_ir-2.1.3}/tests/test_augment.py +0 -0
  28. {sparse_ir-2.1.2 → sparse_ir-2.1.3}/tests/test_basis.py +0 -0
  29. {sparse_ir-2.1.2 → sparse_ir-2.1.3}/tests/test_basis_set.py +0 -0
  30. {sparse_ir-2.1.2 → sparse_ir-2.1.3}/tests/test_core.py +0 -0
  31. {sparse_ir-2.1.2 → sparse_ir-2.1.3}/tests/test_kernel.py +0 -0
  32. {sparse_ir-2.1.2 → sparse_ir-2.1.3}/tests/test_poly.py +0 -0
  33. {sparse_ir-2.1.2 → sparse_ir-2.1.3}/tests/test_sampling_advanced.py +0 -0
  34. {sparse_ir-2.1.2 → sparse_ir-2.1.3}/tests/test_sve.py +0 -0
  35. {sparse_ir-2.1.2 → sparse_ir-2.1.3}/tests/test_sve_advanced.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sparse-ir
3
- Version: 2.1.2
3
+ Version: 2.1.3
4
4
  Summary: Python bindings for the libsparseir library, providing efficient sparse intermediate representation for many-body physics calculations
5
5
  Author: SpM-lab
6
6
  License-Expression: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sparse-ir"
3
- version = "2.1.2"
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 _lib, COMPUTATION_SUCCESS, get_default_blas_backend
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
- # TODO: use complex data
146
- gl.ctypes.data_as(ctypes.POINTER(ctypes.c_double)),
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
- # TODO: use complex data
203
- g_dlr.ctypes.data_as(ctypes.POINTER(ctypes.c_double)),
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.ascontiguousarray(ax)
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)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sparse-ir
3
- Version: 2.1.2
3
+ Version: 2.1.3
4
4
  Summary: Python bindings for the libsparseir library, providing efficient sparse intermediate representation for many-body physics calculations
5
5
  Author: SpM-lab
6
6
  License-Expression: MIT
@@ -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