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 bb36fa7ee..98c40c770 100644 --- a/stumpy/core.py +++ b/stumpy/core.py @@ -648,24 +648,104 @@ 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 + 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 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, + ] + ] - T : numpy.ndarray - Time series or sequence + 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`. - """ - return sdp._sliding_dot_product(Q, 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 + stumpy_default_func = sdp._pyfftw_sliding_dot_product + + 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, config.STUMPY_NJIT_SDP_Q_LENGTH), + (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 = len(Q) + n = 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( 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