Skip to content

Commit a508bb4

Browse files
author
peng.li24
committed
feat: add np.full, np.flatnonzero, np.isin(int), np.unwrap
- Rename full_like → full (raw pointer API) to match numpy naming - pylike full_like wrapper kept unchanged (correctly implements full_like semantics) - Make isin and intersect1d templates for generic type support - Add flatnonzero template - Add unwrap with numpy-compatible algorithm (cumulative correction added to original values, avoiding floating-point error propagation) - Add corresponding pybind11 wrappers and test bindings - Add tests: test_full, test_flatnonzero, test_isin_int, test_unwrap - Test count: 460 → 466
1 parent 24d9bbf commit a508bb4

4 files changed

Lines changed: 106 additions & 12 deletions

File tree

numpy/core.h

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,9 @@ inline void ones_like(T* dst, size_t n) {
4545
std::fill_n(dst, n, T(1));
4646
}
4747

48-
/// numpy.full_like(a, fill_value, dtype=None, order='K', subok=True, shape=None)
48+
/// numpy.full(shape, fill_value, dtype=None, order='C')
4949
template<typename T>
50-
inline void full_like(T* dst, size_t n, T fill_value) {
50+
inline void full(T* dst, size_t n, T fill_value) {
5151
std::fill_n(dst, n, fill_value);
5252
}
5353

@@ -662,18 +662,29 @@ inline ptrdiff_t argmin(const T* data, size_t n) {
662662
// ============================================================================
663663

664664
/// numpy.isin(element, test_elements, assume_unique=False, invert=False)
665-
inline void isin(const double* arr, bool* dst, size_t n,
666-
const double* values, size_t nv) {
667-
std::unordered_set<double> vs(values, values + nv);
665+
template<typename T>
666+
inline void isin(const T* arr, bool* dst, size_t n,
667+
const T* values, size_t nv) {
668+
std::unordered_set<T> vs(values, values + nv);
668669
for (size_t i = 0; i < n; ++i) dst[i] = vs.count(arr[i]) > 0;
669670
}
670671

672+
/// numpy.flatnonzero(a)
673+
template<typename T>
674+
inline std::vector<size_t> flatnonzero(const T* arr, size_t n) {
675+
std::vector<size_t> idx;
676+
for (size_t i = 0; i < n; ++i)
677+
if (arr[i] != T(0)) idx.push_back(i);
678+
return idx;
679+
}
680+
671681
/// numpy.intersect1d(ar1, ar2, assume_unique=False, return_indices=False)
672-
inline std::vector<double> intersect1d(const double* a, size_t na,
673-
const double* b, size_t nb) {
674-
std::unordered_set<double> sa(a, a + na), sb(b, b + nb);
675-
std::vector<double> inter;
676-
for (double v : sa)
682+
template<typename T>
683+
inline std::vector<T> intersect1d(const T* a, size_t na,
684+
const T* b, size_t nb) {
685+
std::unordered_set<T> sa(a, a + na), sb(b, b + nb);
686+
std::vector<T> inter;
687+
for (T v : sa)
677688
if (sb.count(v)) inter.push_back(v);
678689
return inter;
679690
}
@@ -707,6 +718,26 @@ inline double safe_divide(double a, double b, double default_val = 0.0) {
707718
return b == 0.0 ? default_val : a / b;
708719
}
709720

721+
/// numpy.unwrap(p, discont=None, axis=-1)
722+
/// 1D only: unwrap phase angles by correcting jumps > discont.
723+
template<typename T>
724+
inline void unwrap(const T* src, T* dst, size_t n, T discont = T(M_PI)) {
725+
if (n == 0) return;
726+
T period = T(2) * discont;
727+
dst[0] = src[0];
728+
T cum_correct = T(0);
729+
for (size_t i = 1; i < n; ++i) {
730+
T dd = src[i] - src[i - 1];
731+
T ph_correct = T(0);
732+
if (std::abs(dd) >= discont) {
733+
T ddmod = dd - std::round(dd / period) * period;
734+
ph_correct = ddmod - dd;
735+
}
736+
cum_correct += ph_correct;
737+
dst[i] = src[i] + cum_correct;
738+
}
739+
}
740+
710741
// ============================================================================
711742
// astype conversions
712743
// ============================================================================

pycpp/core_py.h

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ template<typename T>
4343
py::array_t<T> full_like(const py::array_t<T>& arr, T fill_value) {
4444
auto buf = arr.request();
4545
py::array_t<T> result(buf.shape);
46-
full_like(static_cast<T*>(result.request().ptr), buf.size, fill_value);
46+
numpy::full(static_cast<T*>(result.request().ptr), buf.size, fill_value);
4747
return result;
4848
}
4949

@@ -67,6 +67,13 @@ inline py::array_t<double> ones(const std::vector<py::ssize_t>& shape) {
6767
return result;
6868
}
6969

70+
/// numpy.full(shape, fill_value, dtype=float, order='C')
71+
inline py::array_t<double> full(const std::vector<py::ssize_t>& shape, double fill_value) {
72+
py::array_t<double> result(shape);
73+
numpy::full(static_cast<double*>(result.request().ptr), result.request().size, fill_value);
74+
return result;
75+
}
76+
7077
// Bool specializations
7178
// NOTE: _bool suffix — dtype-specific wrappers; pybind11 cannot deduce template
7279
// argument from a Python dtype keyword, so each dtype needs its own binding.
@@ -870,12 +877,42 @@ py::ssize_t argmin(const py::array_t<T>& arr) {
870877
inline py::array_t<bool> isin(const py::array_t<double>& arr, const std::vector<double>& values) {
871878
auto buf = arr.request();
872879
py::array_t<bool> result(buf.shape);
873-
isin(static_cast<const double*>(buf.ptr),
880+
numpy::isin(static_cast<const double*>(buf.ptr),
874881
static_cast<bool*>(result.request().ptr), buf.size,
875882
values.data(), values.size());
876883
return result;
877884
}
878885

886+
/// isin with int test_elements (e.g. np.isin(arr, [1, 3, 5]))
887+
inline py::array_t<bool> isin(const py::array_t<double>& arr, const std::vector<int>& values) {
888+
auto buf = arr.request();
889+
py::array_t<bool> result(buf.shape);
890+
std::vector<double> dv(values.begin(), values.end());
891+
numpy::isin(static_cast<const double*>(buf.ptr),
892+
static_cast<bool*>(result.request().ptr), buf.size,
893+
dv.data(), dv.size());
894+
return result;
895+
}
896+
897+
/// numpy.flatnonzero(a)
898+
inline py::array_t<py::ssize_t> flatnonzero(const py::array_t<double>& arr) {
899+
auto buf = arr.request();
900+
auto idx = numpy::flatnonzero(static_cast<const double*>(buf.ptr), buf.size);
901+
py::array_t<py::ssize_t> result(idx.size());
902+
std::copy(idx.begin(), idx.end(),
903+
static_cast<py::ssize_t*>(result.request().ptr));
904+
return result;
905+
}
906+
907+
/// numpy.unwrap(p, discont=None, axis=-1) — 1D only
908+
inline py::array_t<double> unwrap(const py::array_t<double>& arr, double discont = M_PI) {
909+
auto buf = arr.request();
910+
py::array_t<double> result(buf.shape);
911+
numpy::unwrap(static_cast<const double*>(buf.ptr),
912+
static_cast<double*>(result.request().ptr), buf.size, discont);
913+
return result;
914+
}
915+
879916
/// numpy.intersect1d(ar1, ar2, assume_unique=False, return_indices=False)
880917
inline py::array_t<double> intersect1d(const py::array_t<double>& a, const py::array_t<double>& b) {
881918
auto ba = a.request(), bb = b.request();

tests/module.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ PYBIND11_MODULE(numpycpp, m) {
7070
m.def("ones_like_bool", &numpy::ones_like_bool);
7171
m.def("zeros", &numpy::zeros);
7272
m.def("ones", &numpy::ones);
73+
m.def("full", static_cast<py::array_t<double>(*)(const std::vector<py::ssize_t>&, double)>(&numpy::full));
7374

7475
// -- astype ------------------------------------------------------------
7576
// NOTE: astype_int / astype_bool / astype_bool_from_int instead of a
@@ -213,7 +214,10 @@ PYBIND11_MODULE(numpycpp, m) {
213214

214215
// -- Set operations ----------------------------------------------------
215216
m.def("isin", static_cast<py::array_t<bool>(*)(const py::array_t<double>&, const std::vector<double>&)>(&numpy::isin));
217+
m.def("isin", static_cast<py::array_t<bool>(*)(const py::array_t<double>&, const std::vector<int>&)>(&numpy::isin));
216218
m.def("intersect1d", static_cast<py::array_t<double>(*)(const py::array_t<double>&, const py::array_t<double>&)>(&numpy::intersect1d));
219+
m.def("flatnonzero", static_cast<py::array_t<py::ssize_t>(*)(const py::array_t<double>&)>(&numpy::flatnonzero));
220+
m.def("unwrap", static_cast<py::array_t<double>(*)(const py::array_t<double>&, double)>(&numpy::unwrap), py::arg("arr"), py::arg("discont") = M_PI);
217221

218222
// -- Interpolation -----------------------------------------------------
219223
m.def("interp", static_cast<py::array_t<double>(*)(const py::array_t<double>&, const py::array_t<double>&, const py::array_t<double>&)>(&numpy::interp));

tests/test_all.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,11 @@ def test_zeros(shape, cpp):
430430
def test_ones(shape, cpp):
431431
assert_bit_aligned(cpp.ones(shape), np.ones(shape), f"ones{shape}")
432432

433+
@pytest.mark.parametrize("shape,fill_val", [((5,), 3.14), ((2, 3), -1.0), ((4,), 0.0)])
434+
def test_full(shape, fill_val, cpp):
435+
assert_bit_aligned(cpp.full(list(shape), fill_val),
436+
np.full(shape, fill_val), f"full{shape}_{fill_val}")
437+
433438

434439
# ============================================================================
435440
# 7. Bool-specialized creation
@@ -688,6 +693,23 @@ def test_isin(cpp):
688693
a = np.array([1.0, 2.0, 3.0, 4.0, 5.0])
689694
assert_bit_aligned(cpp.isin(a, [2.0, 4.0, 6.0]), np.isin(a, [2.0, 4.0, 6.0]), "isin")
690695

696+
def test_isin_int(cpp):
697+
a = np.array([1.0, 2.0, 3.0, 4.0, 5.0])
698+
assert_bit_aligned(cpp.isin(a, [2, 4, 6]), np.isin(a, [2, 4, 6]), "isin_int")
699+
700+
def test_flatnonzero(cpp):
701+
a = np.array([0.0, 1.0, 0.0, 2.0, 0.0, 3.0])
702+
assert_bit_aligned(cpp.flatnonzero(a), np.flatnonzero(a), "flatnonzero")
703+
# all zeros
704+
a2 = np.array([0.0, 0.0, 0.0])
705+
assert_bit_aligned(cpp.flatnonzero(a2), np.flatnonzero(a2), "flatnonzero zeros")
706+
707+
def test_unwrap(cpp):
708+
a = np.array([0.0, 0.5, 0.8, -0.9, -0.5, 0.2])
709+
assert_bit_aligned(cpp.unwrap(a), np.unwrap(a), "unwrap")
710+
a2 = np.array([0.0, 2.5, 5.0, -2.5, -5.0]) * np.pi
711+
assert_bit_aligned(cpp.unwrap(a2), np.unwrap(a2), "unwrap_large")
712+
691713
def test_intersect1d(cpp):
692714
a, b = np.array([1.0, 2.0, 3.0, 4.0]), np.array([3.0, 4.0, 5.0, 6.0])
693715
cpp_r = np.sort(np.asarray(cpp.intersect1d(a, b)))

0 commit comments

Comments
 (0)