From 29ea35d29cc28cc6d43c820300c400f43a37c96d Mon Sep 17 00:00:00 2001 From: NimaSarajpoor Date: Fri, 6 Feb 2026 00:31:09 -0500 Subject: [PATCH 1/2] added new module sdp --- stumpy/sdp.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 stumpy/sdp.py diff --git a/stumpy/sdp.py b/stumpy/sdp.py new file mode 100644 index 000000000..e69de29bb From d0b237586cbaea39d0da51f01acef26d529110fe Mon Sep 17 00:00:00 2001 From: NimaSarajpoor Date: Fri, 6 Feb 2026 20:38:23 -0500 Subject: [PATCH 2/2] Move sliding-dot-product from core to new module sdp --- stumpy/core.py | 57 ++++------------------------------------- stumpy/scrump.py | 4 +-- stumpy/sdp.py | 52 +++++++++++++++++++++++++++++++++++++ tests/test_core.py | 7 ----- tests/test_precision.py | 6 ++--- tests/test_sdp.py | 37 ++++++++++++++++++++++++++ 6 files changed, 99 insertions(+), 64 deletions(-) create mode 100644 tests/test_sdp.py diff --git a/stumpy/core.py b/stumpy/core.py index e038a00a0..c393fea86 100644 --- a/stumpy/core.py +++ b/stumpy/core.py @@ -12,10 +12,9 @@ from numba import cuda, njit, prange from scipy import linalg from scipy.ndimage import maximum_filter1d, minimum_filter1d -from scipy.signal import convolve from scipy.spatial.distance import cdist -from . import config +from . import config, sdp try: from numba.cuda.cudadrv.driver import _raise_driver_not_found @@ -649,36 +648,9 @@ def check_window_size(m, max_size=None, n=None): warnings.warn(msg) -@njit(fastmath=config.STUMPY_FASTMATH_TRUE) -def _sliding_dot_product(Q, T): - """ - A Numba JIT-compiled implementation of the sliding window dot product. - - Parameters - ---------- - Q : numpy.ndarray - Query array or subsequence - - T : numpy.ndarray - Time series or sequence - - Returns - ------- - out : numpy.ndarray - Sliding dot product between `Q` and `T`. - """ - m = Q.shape[0] - l = T.shape[0] - m + 1 - out = np.empty(l) - for i in range(l): - out[i] = np.dot(Q, T[i : i + m]) - - return out - - def sliding_dot_product(Q, T): """ - Use FFT convolution to calculate the sliding window dot product. + Calculate the sliding window dot product. Parameters ---------- @@ -692,27 +664,8 @@ def sliding_dot_product(Q, T): ------- output : numpy.ndarray Sliding dot product between `Q` and `T`. - - Notes - ----- - Calculate the sliding dot product - - `DOI: 10.1109/ICDM.2016.0179 \ - `__ - - See Table I, Figure 4 - - Following the inverse FFT, Fig. 4 states that only cells [m-1:n] - contain valid dot products - - Padding is done automatically in fftconvolve step """ - n = T.shape[0] - m = Q.shape[0] - Qr = np.flipud(Q) # Reverse/flip Q - QT = convolve(Qr, T) - - return QT.real[m - 1 : n] + return sdp._sliding_dot_product(Q, T) @njit( @@ -1327,7 +1280,7 @@ def _p_norm_distance_profile(Q, T, p=2.0): T_squared[i] = ( T_squared[i - 1] - T[i - 1] * T[i - 1] + T[i + m - 1] * T[i + m - 1] ) - QT = _sliding_dot_product(Q, T) + QT = sdp._njit_sliding_dot_product(Q, T) for i in range(l): p_norm_profile[i] = Q_squared + T_squared[i] - 2.0 * QT[i] else: @@ -1900,7 +1853,7 @@ def _mass_distance_matrix( if np.any(~np.isfinite(Q[i : i + m])): # pragma: no cover distance_matrix[i, :] = np.inf else: - QT = _sliding_dot_product(Q[i : i + m], T) + QT = sdp._njit_sliding_dot_product(Q[i : i + m], T) distance_matrix[i, :] = _mass( Q[i : i + m], T, diff --git a/stumpy/scrump.py b/stumpy/scrump.py index 4315d3364..03739cdd6 100644 --- a/stumpy/scrump.py +++ b/stumpy/scrump.py @@ -6,7 +6,7 @@ import numpy as np from numba import njit, prange -from . import config, core +from . import config, core, sdp from .scraamp import prescraamp, scraamp from .stump import _stump @@ -235,7 +235,7 @@ def _compute_PI( QT = np.empty(w, dtype=np.float64) for i in indices[start:stop]: Q = T_A[i : i + m] - QT[:] = core._sliding_dot_product(Q, T_B) + QT[:] = sdp._njit_sliding_dot_product(Q, T_B) squared_distance_profile[:] = core._calculate_squared_distance_profile( m, QT, diff --git a/stumpy/sdp.py b/stumpy/sdp.py index e69de29bb..4c43db1fb 100644 --- a/stumpy/sdp.py +++ b/stumpy/sdp.py @@ -0,0 +1,52 @@ +import numpy as np +from numba import njit + +from . import config + + +@njit(fastmath=config.STUMPY_FASTMATH_TRUE) +def _njit_sliding_dot_product(Q, T): + """ + A Numba JIT-compiled implementation of the sliding window dot product. + + Parameters + ---------- + Q : numpy.ndarray + Query array or subsequence + + T : numpy.ndarray + Time series or sequence + + Returns + ------- + out : numpy.ndarray + Sliding dot product between `Q` and `T`. + """ + m = Q.shape[0] + l = T.shape[0] - m + 1 + out = np.empty(l) + for i in range(l): + out[i] = np.dot(Q, T[i : i + m]) + + return out + + +def _sliding_dot_product(Q, T): + """ + A wrapper function for the Numba JIT-compiled implementation of the sliding + window dot product. + + Parameters + ---------- + Q : numpy.ndarray + Query array or subsequence + + T : numpy.ndarray + Time series or sequence + + Returns + ------- + out : numpy.ndarray + Sliding dot product between `Q` and `T`. + """ + return _njit_sliding_dot_product(Q, T) diff --git a/tests/test_core.py b/tests/test_core.py index f0b50566f..a83f70063 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -208,13 +208,6 @@ def test_check_window_size_excl_zone(): core.check_window_size(m, max_size=len(T), n=len(T)) -@pytest.mark.parametrize("Q, T", test_data) -def test_njit_sliding_dot_product(Q, T): - ref_mp = naive_rolling_window_dot_product(Q, T) - comp_mp = core._sliding_dot_product(Q, T) - npt.assert_almost_equal(ref_mp, comp_mp) - - @pytest.mark.parametrize("Q, T", test_data) def test_sliding_dot_product(Q, T): ref_mp = naive_rolling_window_dot_product(Q, T) diff --git a/tests/test_precision.py b/tests/test_precision.py index dadb2802f..12654bd7c 100644 --- a/tests/test_precision.py +++ b/tests/test_precision.py @@ -8,7 +8,7 @@ import pytest from numba import cuda -from stumpy import cache, config, core, fastmath +from stumpy import cache, config, core, fastmath, sdp if cuda.is_available(): from stumpy.gpu_stump import gpu_stump @@ -90,7 +90,7 @@ def test_calculate_squared_distance(): k = n - m + 1 for i in range(k): for j in range(k): - QT_i = core._sliding_dot_product(T[i : i + m], T) + QT_i = sdp._njit_sliding_dot_product(T[i : i + m], T) dist_ij = core._calculate_squared_distance( m, QT_i[j], @@ -102,7 +102,7 @@ def test_calculate_squared_distance(): T_subseq_isconstant[j], ) - QT_j = core._sliding_dot_product(T[j : j + m], T) + QT_j = sdp._njit_sliding_dot_product(T[j : j + m], T) dist_ji = core._calculate_squared_distance( m, QT_j[i], diff --git a/tests/test_sdp.py b/tests/test_sdp.py new file mode 100644 index 000000000..c3597383e --- /dev/null +++ b/tests/test_sdp.py @@ -0,0 +1,37 @@ +import numpy as np +import pytest +from numpy import testing as npt + +from stumpy import sdp + + +def naive_rolling_window_dot_product(Q, T): + window = len(Q) + result = np.zeros(len(T) - window + 1) + for i in range(len(result)): + result[i] = np.dot(T[i : i + window], Q) + return result + + +test_data = [ + (np.array([-1, 1, 2], dtype=np.float64), np.array(range(5), dtype=np.float64)), + ( + np.array([9, 8100, -60], dtype=np.float64), + np.array([584, -11, 23, 79, 1001], dtype=np.float64), + ), + (np.random.uniform(-1000, 1000, [8]), np.random.uniform(-1000, 1000, [64])), +] + + +@pytest.mark.parametrize("Q, T", test_data) +def test_njit_sliding_dot_product(Q, T): + ref_mp = naive_rolling_window_dot_product(Q, T) + comp_mp = sdp._njit_sliding_dot_product(Q, T) + npt.assert_almost_equal(ref_mp, comp_mp) + + +@pytest.mark.parametrize("Q, T", test_data) +def test_sliding_dot_product(Q, T): + ref_mp = naive_rolling_window_dot_product(Q, T) + comp_mp = sdp._sliding_dot_product(Q, T) + npt.assert_almost_equal(ref_mp, comp_mp)