Skip to content

Commit 0781e5e

Browse files
committed
address review again
1 parent 9ebffc9 commit 0781e5e

File tree

1 file changed

+44
-21
lines changed

1 file changed

+44
-21
lines changed

tests/test_utils/test_moving.py

Lines changed: 44 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@
2929
"max": move_max,
3030
}
3131

32+
# Add std wrapper when bottleneck is available.
33+
if "bottleneck" in _get_available_engines():
34+
TEST_CONVENIENCE_FUNCS["std"] = move_std
35+
3236

3337
class TestMovingWindow:
3438
"""Test moving window operations with different engines."""
@@ -40,7 +44,7 @@ def _validate_basic_result(self, result, original_data):
4044
assert result.shape == original_data.shape
4145
assert result.dtype.kind in ["f", "i", "c"] # float, int, or complex
4246

43-
def _test_finite_properties(self, results, data):
47+
def _test_finite_properties(self, results, data, window):
4448
"""Test mathematical properties of operations."""
4549
# Find where all results are finite
4650
all_finite = np.ones(len(data), dtype=bool)
@@ -50,23 +54,41 @@ def _test_finite_properties(self, results, data):
5054
if not np.any(all_finite):
5155
return # Skip if no finite values
5256

57+
mean_vals = results["mean"]
58+
sum_vals = results["sum"]
59+
min_vals = results["min"]
60+
max_vals = results["max"]
61+
62+
# On truly interior indices, sum should equal mean * window (within tolerance).
63+
# These are indices where the full window is available without edge effects.
64+
if window >= 3 and len(data) >= window:
65+
# True interior: start from window-1 to end-(window-1)
66+
interior_start = window - 1
67+
interior_end = len(data) - (window - 1)
68+
if interior_end > interior_start:
69+
interior_slice = slice(interior_start, interior_end)
70+
interior_finite = all_finite[interior_slice]
71+
if np.any(interior_finite):
72+
mean_interior = mean_vals[interior_slice][interior_finite]
73+
sum_interior = sum_vals[interior_slice][interior_finite]
74+
np.testing.assert_allclose(
75+
sum_interior, mean_interior * window, rtol=1e-6, atol=1e-12
76+
)
77+
78+
# Min should be <= max (test on all finite indices)
5379
finite_indices = np.where(all_finite)[0]
54-
55-
# Test properties only where values are finite
56-
mean_vals = results["mean"][finite_indices]
57-
sum_vals = results["sum"][finite_indices]
58-
min_vals = results["min"][finite_indices]
59-
max_vals = results["max"][finite_indices]
60-
61-
# Sum should be >= mean (for positive window size)
62-
assert np.all(sum_vals >= mean_vals)
63-
# Min should be <= max
64-
assert np.all(min_vals <= max_vals)
80+
if len(finite_indices) > 0:
81+
min_finite = min_vals[finite_indices]
82+
max_finite = max_vals[finite_indices]
83+
assert np.all(min_finite <= max_finite)
6584

6685
def _compare_engine_results(self, result1, result2, window):
6786
"""Compare results from different engines."""
68-
# Compare interior values (avoiding edge effects)
69-
interior_slice = slice(window // 2, -window // 2 if window > 2 else None)
87+
# Skip comparison for tiny windows where a stable interior is ill-defined.
88+
if window < 3:
89+
return
90+
# Symmetric interior (works for odd/even windows and avoids edges)
91+
interior_slice = slice(window // 2, -window // 2)
7092

7193
interior1 = result1[interior_slice]
7294
interior2 = result2[interior_slice]
@@ -80,10 +102,7 @@ def _compare_engine_results(self, result1, result2, window):
80102
vals1 = interior1[common_finite]
81103
vals2 = interior2[common_finite]
82104

83-
# Check that both give reasonable results (within data range)
84-
data_min, data_max = -10, 10 # Reasonable range
85-
assert np.all((vals1 >= data_min) & (vals1 <= data_max))
86-
assert np.all((vals2 >= data_min) & (vals2 <= data_max))
105+
# Value-range guard removed; relative agreement is checked below.
87106

88107
# For most operations, results should be similar
89108
# (allowing for different edge handling)
@@ -153,8 +172,9 @@ def test_multi_axis_operations(self, test_data):
153172
window = 2
154173

155174
for axis in [0, 1]:
156-
result = move_median(data, window, axis=axis)
157-
assert result.shape == data.shape
175+
for func in (move_median, move_mean, move_sum, move_min, move_max):
176+
result = func(data, window, axis=axis)
177+
assert result.shape == data.shape
158178

159179
# Input validation tests
160180
@pytest.mark.parametrize("invalid_window", [0, -1])
@@ -210,6 +230,9 @@ def test_different_dtypes(self, test_data):
210230
data = base_data.astype(dtype)
211231
result = move_mean(data, 3)
212232
assert isinstance(result, np.ndarray)
233+
# Mean should promote to floating dtype for integer inputs.
234+
if dtype in [np.int32]:
235+
assert result.dtype.kind == "f"
213236

214237
# Numerical properties tests
215238
def test_operation_properties(self, test_data):
@@ -222,7 +245,7 @@ def test_operation_properties(self, test_data):
222245
results[operation] = moving_window(data, window, operation)
223246

224247
# Test properties where both results are finite
225-
self._test_finite_properties(results, data)
248+
self._test_finite_properties(results, data, window)
226249

227250
def test_numerical_accuracy(self):
228251
"""Test numerical accuracy with known results."""

0 commit comments

Comments
 (0)