Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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
25 changes: 16 additions & 9 deletions aeon/utils/numba/general.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not "pretty", but doing things like :

def _as_normalised_float_dtype(input_dtype):
   if input_dtype == np.float32 return np.float64 else return np.float32

Is not possible inside numba functions (or at least not without overcomplicating things), so we'll go with this for now.

idx = i_start
for i_length in prange(length):
values[:, i_length] = X[:, idx]
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
26 changes: 26 additions & 0 deletions aeon/utils/numba/tests/test_general.py
Original file line number Diff line number Diff line change
Expand Up @@ -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_2d,
Expand Down Expand Up @@ -185,6 +188,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)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you just replace this with the testing.utils function to generate data ? (and cast it to the 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."""
Expand Down
Loading