Skip to content

Commit 43f249a

Browse files
committed
Fix: update pyabacus with AI code diagnosis
1 parent 5e17d47 commit 43f249a

5 files changed

Lines changed: 214 additions & 47 deletions

File tree

python/pyabacus/pyproject.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[build-system]
2-
requires = ["scikit-build-core>=0.3.3", "pybind11"]
2+
requires = ["scikit-build-core>=0.3.3", "pybind11>=2.10.0"]
33
build-backend = "scikit_build_core.build"
44

55

@@ -12,16 +12,16 @@ authors = [
1212
{ name = "Jie Li", email = "lij@aisi.ac.cn" },
1313
{ name = "Chenxu Bai", email = "chenxu.bai@stu.pku.edu.cn" },
1414
]
15-
requires-python = ">=3.7"
15+
requires-python = ">=3.8"
1616
classifiers = [
1717
"Development Status :: 4 - Beta",
1818
"License :: OSI Approved :: MIT License",
1919
"Programming Language :: Python :: 3 :: Only",
20-
"Programming Language :: Python :: 3.7",
2120
"Programming Language :: Python :: 3.8",
2221
"Programming Language :: Python :: 3.9",
2322
"Programming Language :: Python :: 3.10",
2423
"Programming Language :: Python :: 3.11",
24+
"Programming Language :: Python :: 3.12",
2525
]
2626

2727
[project.optional-dependencies]

python/pyabacus/src/ModuleBase/py_base_math.cpp

Lines changed: 8 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ void bind_base_math(py::module& m)
7272
throw std::runtime_error("func array must be 1-dimensional");
7373
}
7474
py::buffer_info rab_info = rab.request();
75-
if (rab.ndim() != 1)
75+
if (rab_info.ndim != 1)
7676
{
7777
throw std::runtime_error("rab array must be 1-dimensional");
7878
}
@@ -105,12 +105,12 @@ void bind_base_math(py::module& m)
105105
throw std::runtime_error("func array must be 1-dimensional");
106106
}
107107
py::buffer_info rab_info = rab.request();
108-
if (rab.ndim() != 1)
108+
if (rab_info.ndim != 1)
109109
{
110110
throw std::runtime_error("rab array must be 1-dimensional");
111111
}
112112
py::buffer_info asum_info = asum.request();
113-
if (asum.ndim() != 1)
113+
if (asum_info.ndim != 1)
114114
{
115115
throw std::runtime_error("asum array must be 1-dimensional");
116116
}
@@ -126,33 +126,12 @@ void bind_base_math(py::module& m)
126126
throw std::runtime_error("func array must be 1-dimensional");
127127
}
128128
py::buffer_info rab_info = rab.request();
129-
if (rab.ndim() != 1)
129+
if (rab_info.ndim != 1)
130130
{
131131
throw std::runtime_error("rab array must be 1-dimensional");
132132
}
133133
py::buffer_info asum_info = asum.request();
134-
if (asum.ndim() != 1)
135-
{
136-
throw std::runtime_error("asum array must be 1-dimensional");
137-
}
138-
ModuleBase::Integral::Simpson_Integral_alltoinf(mesh,
139-
static_cast<const double* const>(func_info.ptr),
140-
static_cast<const double* const>(rab_info.ptr),
141-
static_cast<double* const>(asum_info.ptr));
142-
})
143-
.def_static("Simpson_Integral_alltoinf", [](const int mesh, py::array_t<double> func, py::array_t<double> rab, py::array_t<double> asum){
144-
py::buffer_info func_info = func.request();
145-
if (func_info.ndim != 1)
146-
{
147-
throw std::runtime_error("func array must be 1-dimensional");
148-
}
149-
py::buffer_info rab_info = rab.request();
150-
if (rab.ndim() != 1)
151-
{
152-
throw std::runtime_error("rab array must be 1-dimensional");
153-
}
154-
py::buffer_info asum_info = asum.request();
155-
if (asum.ndim() != 1)
134+
if (asum_info.ndim != 1)
156135
{
157136
throw std::runtime_error("asum array must be 1-dimensional");
158137
}
@@ -178,7 +157,7 @@ void bind_base_math(py::module& m)
178157
throw std::runtime_error("f array must be 1-dimensional");
179158
}
180159
py::buffer_info h_info = h.request();
181-
if (h.ndim() != 1)
160+
if (h_info.ndim != 1)
182161
{
183162
throw std::runtime_error("h array must be 1-dimensional");
184163
}
@@ -193,7 +172,7 @@ void bind_base_math(py::module& m)
193172
throw std::runtime_error("x array must be 1-dimensional");
194173
}
195174
py::buffer_info w_info = w.request();
196-
if (w.ndim() != 1)
175+
if (w_info.ndim != 1)
197176
{
198177
throw std::runtime_error("w array must be 1-dimensional");
199178
}
@@ -208,7 +187,7 @@ void bind_base_math(py::module& m)
208187
throw std::runtime_error("x array must be 1-dimensional");
209188
}
210189
py::buffer_info w_info = w.request();
211-
if (w.ndim() != 1)
190+
if (w_info.ndim != 1)
212191
{
213192
throw std::runtime_error("w array must be 1-dimensional");
214193
}

python/pyabacus/src/ModuleNAO/py_m_nao.cpp

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -467,23 +467,35 @@ void bind_m_nao(py::module& m)
467467
.def_property_readonly("sbt", &NumericalRadial::sbt)
468468
.def_property_readonly("rgrid",
469469
[](NumericalRadial& self) {
470+
const int n = self.nr();
470471
const double* rgrid = self.rgrid();
471-
return py::array_t<double>(self.nr(), rgrid);
472+
py::array_t<double> result(n);
473+
std::memcpy(result.mutable_data(), rgrid, n * sizeof(double));
474+
return result;
472475
})
473476
.def_property_readonly("kgrid",
474477
[](NumericalRadial& self) {
478+
const int n = self.nk();
475479
const double* kgrid = self.kgrid();
476-
return py::array_t<double>(self.nk(), kgrid);
480+
py::array_t<double> result(n);
481+
std::memcpy(result.mutable_data(), kgrid, n * sizeof(double));
482+
return result;
477483
})
478484
.def_property_readonly("rvalue",
479485
[](NumericalRadial& self) {
486+
const int n = self.nr();
480487
const double* rvalue = self.rvalue();
481-
return py::array_t<double>(self.nr(), rvalue);
488+
py::array_t<double> result(n);
489+
std::memcpy(result.mutable_data(), rvalue, n * sizeof(double));
490+
return result;
482491
})
483492
.def_property_readonly("kvalue",
484493
[](NumericalRadial& self) {
494+
const int n = self.nk();
485495
const double* kvalue = self.kvalue();
486-
return py::array_t<double>(self.nk(), kvalue);
496+
py::array_t<double> result(n);
497+
std::memcpy(result.mutable_data(), kvalue, n * sizeof(double));
498+
return result;
487499
})
488500
.def_property_readonly("is_fft_compliant", overload_cast_<>()(&NumericalRadial::is_fft_compliant, py::const_));
489501
}

python/pyabacus/src/py_numerical_radial.cpp

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ void bind_numerical_radial(py::module& m)
8686
"ngrid"_a,
8787
"cutoff"_a,
8888
"mode"_a = 'i',
89-
"enable+fft"_a = false)
89+
"enable_fft"_a = false)
9090
.def(
9191
"set_value",
9292
[](NumericalRadial& self, const bool for_r_space, py::array_t<double> value, const int p) {
@@ -139,30 +139,37 @@ void bind_numerical_radial(py::module& m)
139139
.def_property_readonly("kcut", &NumericalRadial::kcut)
140140
.def_property_readonly("rgrid",
141141
[](NumericalRadial& self) {
142+
const int n = self.nr();
142143
const double* rgrid = self.rgrid();
143-
return py::array_t<double>({self.nr()}, rgrid);
144+
py::array_t<double> result(n);
145+
std::memcpy(result.mutable_data(), rgrid, n * sizeof(double));
146+
return result;
144147
})
145148
.def_property_readonly("kgrid",
146149
[](NumericalRadial& self) {
150+
const int n = self.nk();
147151
const double* kgrid = self.kgrid();
148-
return py::array_t<double>({self.nk()}, kgrid);
152+
py::array_t<double> result(n);
153+
std::memcpy(result.mutable_data(), kgrid, n * sizeof(double));
154+
return result;
149155
})
150156
.def_property_readonly("rvalue",
151157
[](NumericalRadial& self) {
158+
const int n = self.nr();
152159
const double* rvalue = self.rvalue();
153-
return py::array_t<double>({self.nr()}, rvalue);
160+
py::array_t<double> result(n);
161+
std::memcpy(result.mutable_data(), rvalue, n * sizeof(double));
162+
return result;
154163
})
155164
.def_property_readonly("kvalue",
156165
[](NumericalRadial& self) {
166+
const int n = self.nk();
157167
const double* kvalue = self.kvalue();
158-
return py::array_t<double>({self.nk()}, kvalue);
168+
py::array_t<double> result(n);
169+
std::memcpy(result.mutable_data(), kvalue, n * sizeof(double));
170+
return result;
159171
})
160172
.def_property_readonly("pr", &NumericalRadial::pr)
161173
.def_property_readonly("pk", &NumericalRadial::pk)
162-
.def_property_readonly("is_fft_compliant", overload_cast_<>()(&NumericalRadial::is_fft_compliant, py::const_))
163-
// leave transformer for future
164-
.def_property_readonly("rgrid", overload_cast_<int>()(&NumericalRadial::rgrid, py::const_))
165-
.def_property_readonly("kgrid", overload_cast_<int>()(&NumericalRadial::kgrid, py::const_))
166-
.def_property_readonly("rvalue", overload_cast_<int>()(&NumericalRadial::rvalue, py::const_))
167-
.def_property_readonly("kvalue", overload_cast_<int>()(&NumericalRadial::kvalue, py::const_));
174+
.def_property_readonly("is_fft_compliant", overload_cast_<>()(&NumericalRadial::is_fft_compliant, py::const_));
168175
}
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
from __future__ import annotations
2+
3+
"""
4+
Tests for memory lifetime safety of pyabacus bindings.
5+
6+
These tests verify that numpy arrays returned from C++ objects
7+
are safe to use even after the C++ object is destroyed (because
8+
they are copies, not views).
9+
"""
10+
11+
import pytest
12+
import numpy as np
13+
from pyabacus import ModuleNAO as nao
14+
15+
16+
def test_array_lifetime_after_object_deletion():
17+
"""Test that arrays remain valid after C++ object deletion."""
18+
chi = nao.NumericalRadial()
19+
20+
# Build the object
21+
sz = 100
22+
dr = 0.1
23+
grid = np.array([i * dr for i in range(sz)], dtype=np.float64)
24+
values = np.exp(-grid)
25+
26+
chi.build(
27+
l=0,
28+
for_r_space=True,
29+
ngrid=sz,
30+
grid=grid,
31+
value=values,
32+
p=-1,
33+
izeta=0,
34+
symbol="Test",
35+
itype=0,
36+
init_sbt=True
37+
)
38+
39+
# Get arrays
40+
rgrid = chi.rgrid
41+
rvalue = chi.rvalue
42+
43+
# Save copies for comparison
44+
rgrid_copy = rgrid.copy()
45+
rvalue_copy = rvalue.copy()
46+
47+
# Delete the C++ object
48+
del chi
49+
50+
# Arrays should still be valid and contain correct data
51+
# (because they are copies, not views)
52+
np.testing.assert_array_equal(rgrid, rgrid_copy)
53+
np.testing.assert_array_equal(rvalue, rvalue_copy)
54+
55+
56+
def test_array_modification_isolation():
57+
"""Test that modifying returned arrays doesn't affect original data."""
58+
chi = nao.NumericalRadial()
59+
60+
sz = 100
61+
dr = 0.1
62+
grid = np.array([i * dr for i in range(sz)], dtype=np.float64)
63+
values = np.exp(-grid)
64+
65+
chi.build(
66+
l=0,
67+
for_r_space=True,
68+
ngrid=sz,
69+
grid=grid,
70+
value=values
71+
)
72+
73+
# Get first array and modify it
74+
rgrid1 = chi.rgrid
75+
original_value = rgrid1[0]
76+
rgrid1[0] = 999.0
77+
78+
# Get second array - should have original value
79+
rgrid2 = chi.rgrid
80+
assert rgrid2[0] == original_value, "Modification should not affect original data"
81+
assert rgrid1[0] == 999.0, "Modified array should retain modification"
82+
83+
84+
def test_multiple_array_accesses():
85+
"""Test that multiple accesses return independent copies."""
86+
chi = nao.NumericalRadial()
87+
88+
sz = 50
89+
dr = 0.2
90+
grid = np.array([i * dr for i in range(sz)], dtype=np.float64)
91+
values = np.exp(-grid)
92+
93+
chi.build(
94+
l=1,
95+
for_r_space=True,
96+
ngrid=sz,
97+
grid=grid,
98+
value=values
99+
)
100+
101+
# Get multiple arrays
102+
arrays = [chi.rgrid for _ in range(5)]
103+
104+
# Modify each differently
105+
for i, arr in enumerate(arrays):
106+
arr[0] = float(i)
107+
108+
# Verify each has its own modification
109+
for i, arr in enumerate(arrays):
110+
assert arr[0] == float(i), f"Array {i} should have value {i}"
111+
112+
113+
def test_radial_collection_array_lifetime():
114+
"""Test array lifetime for RadialCollection objects."""
115+
orb_dir = '../../../tests/PP_ORB/'
116+
file_list = [orb_dir + "C_gga_8au_100Ry_2s2p1d.orb"]
117+
118+
try:
119+
orb = nao.RadialCollection()
120+
orb.build(1, file_list, 'o')
121+
122+
# Get a NumericalRadial from the collection
123+
nr = orb(0, 0, 0)
124+
125+
# Get arrays from it
126+
rgrid = nr.rgrid
127+
rvalue = nr.rvalue
128+
129+
# Save copies
130+
rgrid_copy = rgrid.copy()
131+
rvalue_copy = rvalue.copy()
132+
133+
# Delete the collection (which owns the NumericalRadial)
134+
del orb
135+
136+
# Arrays should still be valid
137+
np.testing.assert_array_equal(rgrid, rgrid_copy)
138+
np.testing.assert_array_equal(rvalue, rvalue_copy)
139+
except (FileNotFoundError, RuntimeError):
140+
pytest.skip("Orbital files not available for testing")
141+
142+
143+
def test_empty_array_handling():
144+
"""Test handling of arrays when k-space is not initialized."""
145+
chi = nao.NumericalRadial()
146+
147+
sz = 50
148+
dr = 0.2
149+
grid = np.array([i * dr for i in range(sz)], dtype=np.float64)
150+
values = np.exp(-grid)
151+
152+
chi.build(
153+
l=0,
154+
for_r_space=True,
155+
ngrid=sz,
156+
grid=grid,
157+
value=values,
158+
init_sbt=False # Don't initialize SBT, so k-space won't be set
159+
)
160+
161+
# r-space should be valid
162+
assert chi.nr == sz
163+
rgrid = chi.rgrid
164+
assert len(rgrid) == sz
165+
166+
# k-space should be empty (nk == 0)
167+
assert chi.nk == 0
168+
kgrid = chi.kgrid
169+
assert len(kgrid) == 0

0 commit comments

Comments
 (0)