Skip to content

Commit 5385ede

Browse files
committed
Closes #5269: alignment tests for arkouda.numpy.pdarraymanipulation
1 parent f464e45 commit 5385ede

File tree

2 files changed

+230
-0
lines changed

2 files changed

+230
-0
lines changed

pytest.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ testpaths =
5252
tests/numpy/err_test.py
5353
tests/numpy/manipulation_functions_test.py
5454
tests/numpy/alignment_verification/operators_alignment.py
55+
tests/numpy/alignment_verification/pdarraymanipulation_alignment.py
5556
tests/numpy/numeric_test.py
5657
tests/numpy/numpy_test.py
5758
tests/numpy/pdarrayclass_test.py
Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
import numpy as np
2+
import pytest
3+
4+
import arkouda as ak
5+
6+
from arkouda.numpy.pdarrayclass import pdarray
7+
8+
# Import the functions under test from your module location.
9+
# Adjust the import path to match where pdarraymanipulation.py lives in your tree.
10+
from arkouda.numpy.pdarraymanipulation import append, delete, hstack, vstack
11+
12+
13+
# -----------------------------
14+
# Helpers
15+
# -----------------------------
16+
def _ak_to_np(x):
17+
"""
18+
Convert Arkouda pdarray (possibly nested / multi-d) to a NumPy array.
19+
We prefer to_ndarray() when available; fall back to to_list().
20+
"""
21+
# Many Arkouda objects support to_ndarray(); pdarray does.
22+
if hasattr(x, "to_ndarray"):
23+
return x.to_ndarray()
24+
return np.array(x.to_list())
25+
26+
27+
def _assert_np_equal(got, exp):
28+
got_np = _ak_to_np(got) if isinstance(got, pdarray) else np.asarray(got)
29+
exp_np = np.asarray(exp)
30+
31+
assert got_np.shape == exp_np.shape
32+
assert got_np.dtype == exp_np.dtype
33+
34+
# Handle NaNs in a NumPy-version-stable way
35+
if np.issubdtype(exp_np.dtype, np.floating) or np.issubdtype(exp_np.dtype, np.complexfloating):
36+
np.testing.assert_allclose(got_np, exp_np, rtol=0, atol=0, equal_nan=True)
37+
else:
38+
np.testing.assert_array_equal(got_np, exp_np)
39+
40+
41+
def _mk_cases_1d_same_len():
42+
return [
43+
(np.array([1, 2, 3], dtype=np.int64), np.array([4, 5, 6], dtype=np.int64)),
44+
(np.array([1, 2, 3], dtype=np.int64), np.array([4.5, 5.5, 6.5], dtype=np.float64)),
45+
(np.array([np.nan, 1.0], dtype=np.float64), np.array([2.0, np.nan], dtype=np.float64)),
46+
(np.array([True, False], dtype=bool), np.array([False, True], dtype=bool)),
47+
(np.array([], dtype=np.int64), np.array([], dtype=np.int64)),
48+
]
49+
50+
51+
def _mk_cases_2d():
52+
return [
53+
(np.array([[1], [2], [3]], dtype=np.int64), np.array([[4], [5], [6]], dtype=np.int64)),
54+
(np.array([[1, 2]], dtype=np.int64), np.array([[3, 4]], dtype=np.int64)),
55+
(np.array([[1, 2]], dtype=np.int64), np.array([[3.0, 4.0]], dtype=np.float64)),
56+
(np.array([[np.nan, 1.0]], dtype=np.float64), np.array([[2.0, np.nan]], dtype=np.float64)),
57+
]
58+
59+
60+
def _to_ak(x: np.ndarray):
61+
# ak.array handles numpy arrays; for multi-d it yields Arkouda "multi-d" pdarray-like.
62+
return ak.array(x)
63+
64+
65+
# -----------------------------
66+
# hstack alignment
67+
# -----------------------------
68+
@pytest.mark.parametrize("a,b", _mk_cases_1d_same_len())
69+
def test_hstack_1d_alignment(a, b):
70+
ak_a, ak_b = _to_ak(a), _to_ak(b)
71+
72+
got = hstack((ak_a, ak_b))
73+
exp = np.hstack((a, b))
74+
75+
_assert_np_equal(got, exp)
76+
77+
78+
@pytest.mark.parametrize("a,b", _mk_cases_2d())
79+
def test_hstack_2d_alignment(a, b):
80+
ak_a, ak_b = _to_ak(a), _to_ak(b)
81+
82+
got = hstack((ak_a, ak_b))
83+
exp = np.hstack((a, b))
84+
85+
_assert_np_equal(got, exp)
86+
87+
88+
def test_hstack_dim_mismatch_raises():
89+
a = _to_ak(np.array([1, 2, 3], dtype=np.int64))
90+
b = _to_ak(np.array([[4], [5], [6]], dtype=np.int64))
91+
with pytest.raises(ValueError, match="same number of dimensions"):
92+
hstack((a, b))
93+
94+
95+
def test_hstack_casting_not_supported():
96+
a = _to_ak(np.array([1, 2, 3], dtype=np.int64))
97+
b = _to_ak(np.array([4, 5], dtype=np.int64))
98+
with pytest.raises(NotImplementedError):
99+
hstack((a, b), casting="unsafe")
100+
101+
102+
# -----------------------------
103+
# vstack alignment
104+
# -----------------------------
105+
@pytest.mark.parametrize("a,b", _mk_cases_1d_same_len())
106+
def test_vstack_1d_alignment(a, b):
107+
ak_a, ak_b = _to_ak(a), _to_ak(b)
108+
got = vstack((ak_a, ak_b))
109+
exp = np.vstack((a, b))
110+
_assert_np_equal(got, exp)
111+
112+
113+
@pytest.mark.parametrize("a,b", _mk_cases_2d())
114+
def test_vstack_2d_alignment(a, b):
115+
ak_a, ak_b = _to_ak(a), _to_ak(b)
116+
117+
got = vstack((ak_a, ak_b))
118+
exp = np.vstack((a, b))
119+
120+
_assert_np_equal(got, exp)
121+
122+
123+
def test_vstack_dim_mismatch_raises():
124+
a_np = np.array([1, 2, 3], dtype=np.int64)
125+
b_np = np.array([[4], [5], [6]], dtype=np.int64)
126+
127+
a = _to_ak(a_np)
128+
b = _to_ak(b_np)
129+
130+
# NumPy: must raise for mismatched dimensions
131+
with pytest.raises(ValueError):
132+
np.vstack((a_np, b_np))
133+
134+
# Arkouda: currently raises RuntimeError from server; message is shape-related
135+
with pytest.raises((ValueError, RuntimeError), match="same shape|shape except|concatenation axis"):
136+
vstack((a, b))
137+
138+
139+
def test_vstack_casting_not_supported():
140+
a = _to_ak(np.array([1, 2, 3], dtype=np.int64))
141+
b = _to_ak(np.array([4, 5], dtype=np.int64))
142+
with pytest.raises(NotImplementedError):
143+
vstack((a, b), casting="unsafe")
144+
145+
146+
# -----------------------------
147+
# delete alignment
148+
# -----------------------------
149+
@pytest.mark.parametrize(
150+
"arr,obj,axis",
151+
[
152+
# 1D basics
153+
(np.array([1, 2, 3, 4], dtype=np.int64), 0, None),
154+
(np.array([1, 2, 3, 4], dtype=np.int64), -1, None),
155+
(np.array([1, 2, 3, 4], dtype=np.int64), slice(0, 4, 2), None),
156+
(np.array([1, 2, 3, 4], dtype=np.int64), [1, 3], None),
157+
(np.array([1, 2, 3, 4], dtype=np.int64), np.array([True, False, True, False]), None),
158+
# 2D axis cases
159+
(np.array([[1, 2, 3], [4, 5, 6]], dtype=np.int64), 0, 0),
160+
(np.array([[1, 2, 3], [4, 5, 6]], dtype=np.int64), 1, 1),
161+
(np.array([[1, 2, 3], [4, 5, 6]], dtype=np.int64), slice(0, 3, 2), 1),
162+
(np.array([[1, 2, 3], [4, 5, 6]], dtype=np.int64), [0, 1], 0),
163+
],
164+
)
165+
def test_delete_alignment(arr, obj, axis):
166+
ak_arr = _to_ak(arr)
167+
168+
# convert obj to ak where relevant
169+
if isinstance(obj, np.ndarray) and obj.dtype == bool:
170+
ak_obj = ak.array(obj.tolist())
171+
elif isinstance(obj, (list, tuple)):
172+
ak_obj = obj # delete() accepts Sequence[int]/Sequence[bool]
173+
else:
174+
ak_obj = obj # int or slice
175+
176+
got = delete(ak_arr, ak_obj, axis=axis)
177+
exp = np.delete(arr, obj, axis=axis)
178+
179+
_assert_np_equal(got, exp)
180+
181+
182+
def test_delete_axis_none_flattens_like_numpy():
183+
arr = np.array([[1, 2], [3, 4]], dtype=np.int64)
184+
ak_arr = _to_ak(arr)
185+
186+
got = delete(ak_arr, [1, 3], axis=None)
187+
exp = np.delete(arr, [1, 3], axis=None)
188+
189+
_assert_np_equal(got, exp)
190+
191+
192+
# -----------------------------
193+
# append alignment
194+
# -----------------------------
195+
@pytest.mark.parametrize(
196+
"arr,values,axis",
197+
[
198+
# axis=None -> flatten both (NumPy behavior)
199+
(np.array([1, 2, 3], dtype=np.int64), np.array([[4, 5], [6, 7]], dtype=np.int64), None),
200+
(np.array([[1, 2], [3, 4]], dtype=np.int64), np.array([5, 6], dtype=np.int64), None),
201+
# axis specified -> shapes must align except on axis
202+
(np.array([[1, 2], [3, 4]], dtype=np.int64), np.array([[5, 6]], dtype=np.int64), 0),
203+
(np.array([[1, 2], [3, 4]], dtype=np.int64), np.array([[5], [6]], dtype=np.int64), 1),
204+
# dtype promotion
205+
(np.array([1, 2, 3], dtype=np.int64), np.array([4.5], dtype=np.float64), None),
206+
],
207+
)
208+
def test_append_alignment(arr, values, axis):
209+
ak_arr = _to_ak(arr)
210+
ak_values = _to_ak(values)
211+
212+
got = append(ak_arr, ak_values, axis=axis)
213+
exp = np.append(arr, values, axis=axis)
214+
215+
_assert_np_equal(got, exp)
216+
217+
218+
def test_append_axis_dim_mismatch_raises():
219+
arr = _to_ak(np.array([1, 2, 3], dtype=np.int64))
220+
values = _to_ak(np.array([[4], [5]], dtype=np.int64))
221+
with pytest.raises(ValueError, match="same number of dimensions"):
222+
append(arr, values, axis=0)
223+
224+
225+
def test_append_axis_out_of_bounds_raises():
226+
arr = _to_ak(np.array([[1, 2], [3, 4]], dtype=np.int64))
227+
values = _to_ak(np.array([[5, 6]], dtype=np.int64))
228+
with pytest.raises(ValueError, match="out of bounds"):
229+
append(arr, values, axis=5)

0 commit comments

Comments
 (0)