Skip to content

Commit efdaec3

Browse files
committed
Closes #5269: alignment tests for arkouda.numpy.pdarraymanipulation
1 parent 8a95e0c commit efdaec3

File tree

2 files changed

+237
-0
lines changed

2 files changed

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

0 commit comments

Comments
 (0)