From b2221b18fdb48961c6eb5cf8bc81982c5a58feb3 Mon Sep 17 00:00:00 2001 From: NimaSarajpoor Date: Mon, 24 Aug 2026 22:28:57 -0400 Subject: [PATCH 1/5] add branching logic to sdp func in core --- stumpy/core.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/stumpy/core.py b/stumpy/core.py index 09fd80d8f..5db05b66d 100644 --- a/stumpy/core.py +++ b/stumpy/core.py @@ -665,7 +665,16 @@ def sliding_dot_product(Q, T): output : numpy.ndarray Sliding dot product between `Q` and `T`. """ - return sdp._sliding_dot_product(Q, T) + if len(Q) == len(T): + func = np.dot + elif len(Q) <= 128: # 2 ** 7 + func = sdp._njit_sliding_dot_product + elif sdp.PYFFTW_IS_AVAILABLE: + func = sdp._pyfftw_sliding_dot_product + else: + func = sdp._sliding_dot_product + + return func(Q, T) @njit( From 545055729cfd4a2605a3e492d424d5c2ca4a8730 Mon Sep 17 00:00:00 2001 From: NimaSarajpoor Date: Tue, 25 Aug 2026 01:15:29 -0400 Subject: [PATCH 2/5] add test function to improve coverage --- stumpy/config.py | 2 ++ stumpy/core.py | 14 +++++++------- tests/test_core.py | 10 ++++++++++ 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/stumpy/config.py b/stumpy/config.py index f4378fbe6..e82d1bf31 100644 --- a/stumpy/config.py +++ b/stumpy/config.py @@ -17,6 +17,7 @@ "STUMPY_MAX_P_NORM_DISTANCE": np.finfo(np.float64).max, "STUMPY_MAX_DISTANCE": np.sqrt(np.finfo(np.float64).max), "STUMPY_EXCL_ZONE_DENOM": 4, + "STUMPY_NJIT_SDP_Q_LENGTH": 128, # 2 ** 7 "STUMPY_FASTMATH_TRUE": True, "STUMPY_FASTMATH_FLAGS": {"nsz", "arcp", "contract", "afn", "reassoc"}, "STUMPY_FASTMATH_FASTMATH._ADD_ASSOC": True, @@ -38,6 +39,7 @@ STUMPY_MAX_P_NORM_DISTANCE = _STUMPY_DEFAULTS["STUMPY_MAX_P_NORM_DISTANCE"] STUMPY_MAX_DISTANCE = _STUMPY_DEFAULTS["STUMPY_MAX_DISTANCE"] STUMPY_EXCL_ZONE_DENOM = _STUMPY_DEFAULTS["STUMPY_EXCL_ZONE_DENOM"] +STUMPY_NJIT_SDP_Q_LENGTH = _STUMPY_DEFAULTS["STUMPY_NJIT_SDP_Q_LENGTH"] STUMPY_FASTMATH_TRUE = _STUMPY_DEFAULTS["STUMPY_FASTMATH_TRUE"] STUMPY_FASTMATH_FLAGS = _STUMPY_DEFAULTS["STUMPY_FASTMATH_FLAGS"] diff --git a/stumpy/core.py b/stumpy/core.py index 5db05b66d..601b68912 100644 --- a/stumpy/core.py +++ b/stumpy/core.py @@ -666,15 +666,15 @@ def sliding_dot_product(Q, T): Sliding dot product between `Q` and `T`. """ if len(Q) == len(T): - func = np.dot - elif len(Q) <= 128: # 2 ** 7 - func = sdp._njit_sliding_dot_product - elif sdp.PYFFTW_IS_AVAILABLE: - func = sdp._pyfftw_sliding_dot_product + out = np.array([np.dot(Q, T)]) + elif len(Q) <= config.STUMPY_NJIT_SDP_Q_LENGTH: + out = sdp._njit_sliding_dot_product(Q, T) + elif sdp.PYFFTW_IS_AVAILABLE: # pragma: no cover + out = sdp._pyfftw_sliding_dot_product(Q, T) else: - func = sdp._sliding_dot_product + out = sdp._sliding_dot_product(Q, T) - return func(Q, T) + return out @njit( diff --git a/tests/test_core.py b/tests/test_core.py index 28589cfa3..c92339574 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -208,6 +208,16 @@ def test_sliding_dot_product(Q, T): npt.assert_allclose(cmp_mp, ref_mp, atol=1.5e-07) +def test_sliding_dot_product_large_Q(): + # Set len(T) > len(Q) > config.STUMPY_NJIT_SDP_Q_LENGTH, + # so that it triggers a certain code flow + Q = rng.RNG.rand(config.STUMPY_NJIT_SDP_Q_LENGTH + 1) + T = rng.RNG.rand(config.STUMPY_NJIT_SDP_Q_LENGTH + 2) + ref_mp = naive.rolling_window_dot_product(Q, T) + cmp_mp = core.sliding_dot_product(Q, T) + npt.assert_allclose(cmp_mp, ref_mp, atol=1.5e-07) + + def test_welford_nanvar(): T = rng.RNG.rand(64) m = 10 From d01573d2b3cb6ef505ddb059898055863370f124 Mon Sep 17 00:00:00 2001 From: NimaSarajpoor Date: Sun, 6 Sep 2026 12:43:07 -0400 Subject: [PATCH 3/5] Use closure to allow user to set the values of some variables --- stumpy/core.py | 84 ++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 68 insertions(+), 16 deletions(-) diff --git a/stumpy/core.py b/stumpy/core.py index 0b445fffd..e12fea1a7 100644 --- a/stumpy/core.py +++ b/stumpy/core.py @@ -648,33 +648,85 @@ def check_window_size(m, max_size=None, n=None): warnings.warn(msg) -def sliding_dot_product(Q, T): +def make_sliding_dot_product(boundaries=None, default_func=None): """ - Calculate the sliding window dot product. + A closure to compute the sliding dot product that allows users + to use different methods in different cases Parameters ---------- - Q : numpy.ndarray - Query array or subsequence - - T : numpy.ndarray - Time series or sequence + boundaries : nested list + A list of items, where each item is a list of 3 elements: + * index 0: (LB_m, UB_m) + * index 1: (LB_n, UB_n) + * index 2: func + where, fucn is the sdp function for computing + the sliding dot product of ``Q`` and ``T``, when + m=len(Q) falls into [LB_m, UB_m], and n=len(T) falls + into [LB_n, UB_n]. + + default_func : class 'function' + A callable object for Returns ------- output : numpy.ndarray Sliding dot product between `Q` and `T`. """ - if len(Q) == len(T): - out = np.array([np.dot(Q, T)]) - elif len(Q) <= config.STUMPY_NJIT_SDP_Q_LENGTH: - out = sdp._njit_sliding_dot_product(Q, T) - elif sdp.PYFFTW_IS_AVAILABLE: # pragma: no cover - out = sdp._pyfftw_sliding_dot_product(Q, T) - else: - out = sdp._sliding_dot_product(Q, T) + stumpy_default_func = sdp._sliding_dot_product + if sdp.PYFFTW_IS_AVAILABLE: # pragma: no cover + stumpy_default_func = sdp._pyfftw_sliding_dot_product - return out + if default_func is None: + default_func = stumpy_default_func + + if boundaries is None: + boundaries = [ + # [ + # (LB_m, UB_m), + # (LB_n, UB_n), + # func + # ] + [ + (3, 2**6), + (3, np.inf), + sdp._njit_sliding_dot_product, + ], + ] + + def sliding_dot_product(Q, T): + """ + Compute the sliding dot product between ``Q`` and ``T`` + by using different methods according to len(Q) and len(T) + + 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, n = len(Q), len(T) + if m == n: + return np.array([np.dot(Q, T)]) + + for item in boundaries: + (LB_m, UB_m), (LB_n, UB_n), func = item + if LB_m <= m <= UB_m and LB_n <= n <= UB_n: + return func(Q, T) + + return default_func(Q, T) + + return sliding_dot_product + + +sliding_dot_product = make_sliding_dot_product() @njit( From a3538d19f22bdff6e4c7041378f31db33ac03abb Mon Sep 17 00:00:00 2001 From: NimaSarajpoor Date: Sun, 6 Sep 2026 13:16:27 -0400 Subject: [PATCH 4/5] avoid using hard-coded value --- stumpy/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stumpy/core.py b/stumpy/core.py index e12fea1a7..e06042451 100644 --- a/stumpy/core.py +++ b/stumpy/core.py @@ -688,7 +688,7 @@ def make_sliding_dot_product(boundaries=None, default_func=None): # func # ] [ - (3, 2**6), + (3, config.STUMPY_NJIT_SDP_Q_LENGTH), (3, np.inf), sdp._njit_sliding_dot_product, ], From 6a48340f03af4c90fd160e3329ee5e34ac4451fb Mon Sep 17 00:00:00 2001 From: NimaSarajpoor Date: Sun, 6 Sep 2026 13:32:54 -0400 Subject: [PATCH 5/5] improve docstrings --- stumpy/core.py | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/stumpy/core.py b/stumpy/core.py index e06042451..98c40c770 100644 --- a/stumpy/core.py +++ b/stumpy/core.py @@ -655,23 +655,41 @@ def make_sliding_dot_product(boundaries=None, default_func=None): Parameters ---------- - boundaries : nested list + boundaries : nested list, default None A list of items, where each item is a list of 3 elements: * index 0: (LB_m, UB_m) * index 1: (LB_n, UB_n) * index 2: func where, fucn is the sdp function for computing the sliding dot product of ``Q`` and ``T``, when - m=len(Q) falls into [LB_m, UB_m], and n=len(T) falls - into [LB_n, UB_n]. + m=len(Q) falls into range [LB_m, UB_m], and n=len(T) + falls into range [LB_n, UB_n]. + When this is None (default), it will automatically be set to + the following value: + [ + [ + (3, config.STUMPY_NJIT_SDP_Q_LENGTH), + (3, np.inf), + sdp._njit_sliding_dot_product, + ] + ] - default_func : class 'function' - A callable object for + default_func : class 'function', default None + A callable object that is used for computing + the sliding dot product between a query `Q` and + time series `T`. This is used when len(Q) and len(T) + values do not fall into any boundary provided + in `boundaries`. When None (default), this will + automatically be set to `sdp._pyfftw_sliding_dot_product` + if available. If not, it will be automatically set to + `sdp._sliding_dot_product`. Returns ------- - output : numpy.ndarray - Sliding dot product between `Q` and `T`. + sliding_dot_product : callable + A callable object that computes the sliding dot product between ``Q`` + and ``T`` using different methods based on len(Q) and len(T). It + internally checks the boundary in `boundaries` and choose a function """ stumpy_default_func = sdp._sliding_dot_product if sdp.PYFFTW_IS_AVAILABLE: # pragma: no cover @@ -712,7 +730,8 @@ def sliding_dot_product(Q, T): out : numpy.ndarray Sliding dot product between ``Q`` and ``T``. """ - m, n = len(Q), len(T) + m = len(Q) + n = len(T) if m == n: return np.array([np.dot(Q, T)])