Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 36 additions & 19 deletions bottleneck/src/move_template.c
Original file line number Diff line number Diff line change
Expand Up @@ -178,25 +178,34 @@ MOVE(move_mean, DTYPE0) {
YI(DTYPE0) = count >= min_count ? asum / count : BN_NAN;
}
count_inv = 1.0 / count;
WHILE2 {
ai = AI(DTYPE0);
aold = AOLD(DTYPE0);
if (ai == ai) {
if (aold == aold) {
asum += ai - aold;
/* Special case for window=1: just copy the input value to avoid
floating-point errors from unnecessary arithmetic */
if (window == 1) {
WHILE2 {
ai = AI(DTYPE0);
YI(DTYPE0) = ai;
}
} else {
WHILE2 {
ai = AI(DTYPE0);
aold = AOLD(DTYPE0);
if (ai == ai) {
if (aold == aold) {
asum += ai - aold;
} else {
asum += ai;
count++;
count_inv = 1.0 / count;
}
} else {
asum += ai;
count++;
count_inv = 1.0 / count;
}
} else {
if (aold == aold) {
asum -= aold;
count--;
count_inv = 1.0 / count;
if (aold == aold) {
asum -= aold;
count--;
count_inv = 1.0 / count;
}
}
YI(DTYPE0) = count >= min_count ? asum * count_inv : BN_NAN;
}
YI(DTYPE0) = count >= min_count ? asum * count_inv : BN_NAN;
}
NEXT2
}
Expand All @@ -222,9 +231,17 @@ MOVE(move_mean, DTYPE0) {
*(npy_DTYPE1*)(it.py + it.i * it.ystride) = (npy_DTYPE1)asum / (it.i + 1);
it.i++;
}
WHILE2 {
asum += AI(DTYPE0) - AOLD(DTYPE0);
YI(DTYPE1) = (npy_DTYPE1)asum * window_inv;
/* Special case for window=1: just copy the input value to avoid
floating-point errors from unnecessary arithmetic */
if (window == 1) {
WHILE2 {
YI(DTYPE1) = (npy_DTYPE1)AI(DTYPE0);
}
} else {
WHILE2 {
asum += AI(DTYPE0) - AOLD(DTYPE0);
YI(DTYPE1) = (npy_DTYPE1)asum * window_inv;
}
}
NEXT2
}
Expand Down
27 changes: 27 additions & 0 deletions bottleneck/tests/move_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,3 +210,30 @@ def test_move_std_sqrt():
a3 = np.array([[a, a], [a, a]])
b = bn.move_std(a3, window=3, axis=2)
assert np.isfinite(b[:, :, 2:]).all(), err_msg % 3


# ----------------------------------------------------------------------------
# Regression test for issue #437


def test_move_mean_window_1():
"""Test move_mean with window=1, min_count=1 (issue #437).

When window=1 and min_count=1, move_mean should return the input array
unchanged, since a moving average with window size 1 is the identity
function. However, the current implementation introduces small floating
point errors.

See: https://github.com/pydata/bottleneck/issues/437
"""
# Minimal test case from issue #437 (simplified from 119 to 3 elements)
a = [0.008196721311475436, -0.01626016260162607, 0.012396694214876205]

b = bn.move_mean(a, window=1, min_count=1)

# With window=1, the result should be exactly equal to the input
err_msg = (
"move_mean with window=1 and min_count=1 should return the input array "
"unchanged, but got differences. This is issue #437."
)
assert_equal(b, a, err_msg)
Loading