From bc418dc95128a8b9c0a141a0ccc339162fdf263a Mon Sep 17 00:00:00 2001 From: cakeni <2150015994@qq.com> Date: Mon, 10 Aug 2026 07:13:39 +0800 Subject: [PATCH 1/4] [BUG] Preserve float precision in Numba helpers This pull request includes code written with the assistance of AI. The code has **not yet been reviewed** by a human. --- aeon/utils/numba/general.py | 20 +++++++++++--------- aeon/utils/numba/tests/test_general.py | 26 ++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/aeon/utils/numba/general.py b/aeon/utils/numba/general.py index c0cf4c71de..a5f2b6c1e7 100644 --- a/aeon/utils/numba/general.py +++ b/aeon/utils/numba/general.py @@ -397,7 +397,7 @@ def get_subsequence( The resulting subsequence. """ n_channels, _ = X.shape - values = np.zeros((n_channels, length)) + values = np.zeros((n_channels, length), dtype=(X[:0] / 1).dtype) idx = i_start for i_length in prange(length): values[:, i_length] = X[:, idx] @@ -433,9 +433,10 @@ def get_subsequence_with_mean_std( The std of each channel """ n_channels, _ = X.shape - values = np.zeros((n_channels, length), dtype=np.float64) - means = np.zeros(n_channels, dtype=np.float64) - stds = np.zeros(n_channels, dtype=np.float64) + output_dtype = (X[:0] / 1).dtype + values = np.zeros((n_channels, length), dtype=output_dtype) + means = np.zeros(n_channels, dtype=output_dtype) + stds = np.zeros(n_channels, dtype=output_dtype) for i_channel in prange(n_channels): _sum = 0 _sum2 = 0 @@ -527,8 +528,9 @@ def sliding_mean_std_one_series( raise ValueError( "Invalid input parameter for sliding mean and std computations" ) - mean = np.zeros((n_channels, n_subs)) - std = np.zeros((n_channels, n_subs)) + output_dtype = (X[:0] / 1).dtype + mean = np.zeros((n_channels, n_subs), dtype=output_dtype) + std = np.zeros((n_channels, n_subs), dtype=output_dtype) for i_mod_dil in prange(dilation): # Array mainting indexes of a dilated subsequence @@ -684,7 +686,7 @@ def slope_derivative(X: np.ndarray) -> np.ndarray: """ if X.shape[0] < 3: raise ValueError("Time series must have at least 3 points.") - result = np.zeros(X.shape[0] - 2) + result = np.zeros(X.shape[0] - 2, dtype=(X[:0] / 1).dtype) for i in range(1, X.shape[0] - 1): result[i - 1] = ((X[i] - X[i - 1]) + (X[i + 1] - X[i - 1]) / 2.0) / 2.0 return result @@ -731,7 +733,7 @@ def slope_derivative_2d(X: np.ndarray) -> np.ndarray: >>> X = np.array([[1, 2, 2, 3, 3, 3, 4, 4, 4, 4], [5, 6, 6, 7, 7, 7, 8, 8, 8, 8]]) >>> X_der = slope_derivative_2d(X) """ - arr = np.zeros((X.shape[0], X.shape[1] - 2)) + arr = np.zeros((X.shape[0], X.shape[1] - 2), dtype=(X[:0] / 1).dtype) for i in range(X.shape[0]): arr[i] = slope_derivative(X[i]) return arr @@ -781,7 +783,7 @@ def slope_derivative_3d(X: np.ndarray) -> np.ndarray: ... ]) >>> X_der = slope_derivative_3d(X) """ - arr = np.zeros((X.shape[0], X.shape[1], X.shape[2] - 2)) + arr = np.zeros((X.shape[0], X.shape[1], X.shape[2] - 2), dtype=(X[:0] / 1).dtype) for i in range(X.shape[0]): arr[i] = slope_derivative_2d(X[i]) return arr diff --git a/aeon/utils/numba/tests/test_general.py b/aeon/utils/numba/tests/test_general.py index 84a12b1fdd..2a6fe5f695 100644 --- a/aeon/utils/numba/tests/test_general.py +++ b/aeon/utils/numba/tests/test_general.py @@ -13,6 +13,9 @@ normalise_subsequences, prime_up_to, sliding_mean_std_one_series, + slope_derivative, + slope_derivative_2d, + slope_derivative_3d, unique_count, z_normalise_series, z_normalise_series_with_mean_std, @@ -106,6 +109,29 @@ def test_sliding_mean_std_one_series(dtype): mean, std = sliding_mean_std_one_series(X, 100, 3) +@pytest.mark.parametrize("dtype", DATATYPES) +def test_float_output_dtype_follows_input_precision(dtype): + """Test helpers preserve float precision and promote integer input.""" + X = np.arange(24, dtype=dtype).reshape(2, 12) + expected_dtype = np.float32 if dtype == "float32" else np.float64 + + subsequence = get_subsequence(X, 1, 4, 2) + subsequence_with_stats = get_subsequence_with_mean_std(X, 1, 4, 2) + sliding_stats = sliding_mean_std_one_series(X, 4, 2) + outputs = { + "get_subsequence": (subsequence,), + "get_subsequence_with_mean_std": subsequence_with_stats, + "sliding_mean_std_one_series": sliding_stats, + "slope_derivative": (slope_derivative(X[0]),), + "slope_derivative_2d": (slope_derivative_2d(X),), + "slope_derivative_3d": (slope_derivative_3d(X[np.newaxis]),), + } + + for function_name, function_outputs in outputs.items(): + for output in function_outputs: + assert output.dtype == expected_dtype, function_name + + @pytest.mark.parametrize("dtype", DATATYPES) def test_combinations_1d(dtype): """Test combinations of elements from two 1D arrays.""" From a006b63ffcf9fbc6c173236c2601d8bb50560652 Mon Sep 17 00:00:00 2001 From: cakeni <211545599+cakeni@users.noreply.github.com> Date: Thu, 27 Aug 2026 10:12:40 +0800 Subject: [PATCH 2/4] [BUG] Reuse normalised dtype helper This pull request includes code written with the assistance of AI.\nThe code has **not yet been reviewed** by a human. --- aeon/utils/numba/general.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/aeon/utils/numba/general.py b/aeon/utils/numba/general.py index 56e49dca73..2617ae14a2 100644 --- a/aeon/utils/numba/general.py +++ b/aeon/utils/numba/general.py @@ -417,7 +417,7 @@ def get_subsequence( The resulting subsequence. """ n_channels, _ = X.shape - values = np.zeros((n_channels, length), dtype=(X[:0] / 1).dtype) + values = np.zeros((n_channels, length), dtype=_as_normalised_float(X[:0]).dtype) idx = i_start for i_length in prange(length): values[:, i_length] = X[:, idx] @@ -453,7 +453,7 @@ def get_subsequence_with_mean_std( The std of each channel """ n_channels, _ = X.shape - output_dtype = (X[:0] / 1).dtype + output_dtype = _as_normalised_float(X[:0]).dtype values = np.zeros((n_channels, length), dtype=output_dtype) means = np.zeros(n_channels, dtype=output_dtype) stds = np.zeros(n_channels, dtype=output_dtype) @@ -508,7 +508,7 @@ def sliding_mean_std_one_series( raise ValueError( "Invalid input parameter for sliding mean and std computations" ) - output_dtype = (X[:0] / 1).dtype + output_dtype = _as_normalised_float(X[:0]).dtype mean = np.zeros((n_channels, n_subs), dtype=output_dtype) std = np.zeros((n_channels, n_subs), dtype=output_dtype) @@ -669,7 +669,7 @@ def slope_derivative(X: np.ndarray) -> np.ndarray: """ if X.shape[0] < 3: raise ValueError("Time series must have at least 3 points.") - result = np.zeros(X.shape[0] - 2, dtype=(X[:0] / 1).dtype) + result = np.zeros(X.shape[0] - 2, dtype=_as_normalised_float(X[:0]).dtype) for i in range(1, X.shape[0] - 1): result[i - 1] = ((X[i] - X[i - 1]) + (X[i + 1] - X[i - 1]) / 2.0) / 2.0 return result @@ -716,7 +716,9 @@ def slope_derivative_2d(X: np.ndarray) -> np.ndarray: >>> X = np.array([[1, 2, 2, 3, 3, 3, 4, 4, 4, 4], [5, 6, 6, 7, 7, 7, 8, 8, 8, 8]]) >>> X_der = slope_derivative_2d(X) """ - arr = np.zeros((X.shape[0], X.shape[1] - 2), dtype=(X[:0] / 1).dtype) + arr = np.zeros( + (X.shape[0], X.shape[1] - 2), dtype=_as_normalised_float(X[:0]).dtype + ) for i in range(X.shape[0]): arr[i] = slope_derivative(X[i]) return arr @@ -766,7 +768,10 @@ def slope_derivative_3d(X: np.ndarray) -> np.ndarray: ... ]) >>> X_der = slope_derivative_3d(X) """ - arr = np.zeros((X.shape[0], X.shape[1], X.shape[2] - 2), dtype=(X[:0] / 1).dtype) + arr = np.zeros( + (X.shape[0], X.shape[1], X.shape[2] - 2), + dtype=_as_normalised_float(X[:0]).dtype, + ) for i in range(X.shape[0]): arr[i] = slope_derivative_2d(X[i]) return arr From ab60811a76170134938593ea5afd07b696649366 Mon Sep 17 00:00:00 2001 From: cakeni <2150015994@qq.com> Date: Wed, 2 Sep 2026 15:38:45 +0800 Subject: [PATCH 3/4] test(numba): use generated data for dtype checks --- aeon/utils/numba/tests/test_general.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aeon/utils/numba/tests/test_general.py b/aeon/utils/numba/tests/test_general.py index 99bbaa0072..5b6aad7d70 100644 --- a/aeon/utils/numba/tests/test_general.py +++ b/aeon/utils/numba/tests/test_general.py @@ -191,7 +191,7 @@ def test_sliding_mean_std_one_series(dtype): @pytest.mark.parametrize("dtype", DATATYPES) def test_float_output_dtype_follows_input_precision(dtype): """Test helpers preserve float precision and promote integer input.""" - X = np.arange(24, dtype=dtype).reshape(2, 12) + X = make_example_2d_numpy_series(n_channels=2, random_state=0).astype(dtype) expected_dtype = np.float32 if dtype == "float32" else np.float64 subsequence = get_subsequence(X, 1, 4, 2) From 7944be9fca6927020d9843d769c2b6c669f73f6c Mon Sep 17 00:00:00 2001 From: cakeni <2150015994@qq.com> Date: Wed, 2 Sep 2026 15:39:18 +0800 Subject: [PATCH 4/4] test(numba): import generated series helper --- aeon/utils/numba/tests/test_general.py | 1 + 1 file changed, 1 insertion(+) diff --git a/aeon/utils/numba/tests/test_general.py b/aeon/utils/numba/tests/test_general.py index 5b6aad7d70..513624314e 100644 --- a/aeon/utils/numba/tests/test_general.py +++ b/aeon/utils/numba/tests/test_general.py @@ -4,6 +4,7 @@ import pytest from numpy.testing import assert_array_almost_equal, assert_array_equal +from aeon.testing.data_generation import make_example_2d_numpy_series from aeon.utils.numba.general import ( combinations_1d, get_all_subsequences,