diff --git a/aeon/utils/numba/general.py b/aeon/utils/numba/general.py index 1a2d2f232b..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)) + 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,9 +453,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 = _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) for i_channel in prange(n_channels): _sum = 0 _sum2 = 0 @@ -507,8 +508,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 = _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) for i_mod_dil in prange(dilation): # Array maintaining indices of a dilated subsequence @@ -667,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) + 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 @@ -714,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)) + 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 @@ -764,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)) + 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 diff --git a/aeon/utils/numba/tests/test_general.py b/aeon/utils/numba/tests/test_general.py index 099eb1a790..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, @@ -13,6 +14,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_2d, @@ -185,6 +189,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 = 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) + 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."""