Skip to content

Commit bd6aed8

Browse files
Refactor/sdp structure cleanup (#1126)
* added new module sdp * Move sliding-dot-product from core to new module sdp * move convolve_sdp and add test * refactor naive function
1 parent f7af69a commit bd6aed8

File tree

7 files changed

+132
-73
lines changed

7 files changed

+132
-73
lines changed

stumpy/core.py

Lines changed: 5 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,9 @@
1212
from numba import cuda, njit, prange
1313
from scipy import linalg
1414
from scipy.ndimage import maximum_filter1d, minimum_filter1d
15-
from scipy.signal import convolve
1615
from scipy.spatial.distance import cdist
1716

18-
from . import config
17+
from . import config, sdp
1918

2019
try:
2120
from numba.cuda.cudadrv.driver import _raise_driver_not_found
@@ -649,36 +648,9 @@ def check_window_size(m, max_size=None, n=None):
649648
warnings.warn(msg)
650649

651650

652-
@njit(fastmath=config.STUMPY_FASTMATH_TRUE)
653-
def _sliding_dot_product(Q, T):
654-
"""
655-
A Numba JIT-compiled implementation of the sliding window dot product.
656-
657-
Parameters
658-
----------
659-
Q : numpy.ndarray
660-
Query array or subsequence
661-
662-
T : numpy.ndarray
663-
Time series or sequence
664-
665-
Returns
666-
-------
667-
out : numpy.ndarray
668-
Sliding dot product between `Q` and `T`.
669-
"""
670-
m = Q.shape[0]
671-
l = T.shape[0] - m + 1
672-
out = np.empty(l)
673-
for i in range(l):
674-
out[i] = np.dot(Q, T[i : i + m])
675-
676-
return out
677-
678-
679651
def sliding_dot_product(Q, T):
680652
"""
681-
Use FFT convolution to calculate the sliding window dot product.
653+
Calculate the sliding window dot product.
682654
683655
Parameters
684656
----------
@@ -692,27 +664,8 @@ def sliding_dot_product(Q, T):
692664
-------
693665
output : numpy.ndarray
694666
Sliding dot product between `Q` and `T`.
695-
696-
Notes
697-
-----
698-
Calculate the sliding dot product
699-
700-
`DOI: 10.1109/ICDM.2016.0179 \
701-
<https://www.cs.ucr.edu/~eamonn/PID4481997_extend_Matrix%20Profile_I.pdf>`__
702-
703-
See Table I, Figure 4
704-
705-
Following the inverse FFT, Fig. 4 states that only cells [m-1:n]
706-
contain valid dot products
707-
708-
Padding is done automatically in fftconvolve step
709667
"""
710-
n = T.shape[0]
711-
m = Q.shape[0]
712-
Qr = np.flipud(Q) # Reverse/flip Q
713-
QT = convolve(Qr, T)
714-
715-
return QT.real[m - 1 : n]
668+
return sdp._sliding_dot_product(Q, T)
716669

717670

718671
@njit(
@@ -1327,7 +1280,7 @@ def _p_norm_distance_profile(Q, T, p=2.0):
13271280
T_squared[i] = (
13281281
T_squared[i - 1] - T[i - 1] * T[i - 1] + T[i + m - 1] * T[i + m - 1]
13291282
)
1330-
QT = _sliding_dot_product(Q, T)
1283+
QT = sdp._njit_sliding_dot_product(Q, T)
13311284
for i in range(l):
13321285
p_norm_profile[i] = Q_squared + T_squared[i] - 2.0 * QT[i]
13331286
else:
@@ -1900,7 +1853,7 @@ def _mass_distance_matrix(
19001853
if np.any(~np.isfinite(Q[i : i + m])): # pragma: no cover
19011854
distance_matrix[i, :] = np.inf
19021855
else:
1903-
QT = _sliding_dot_product(Q[i : i + m], T)
1856+
QT = sdp._njit_sliding_dot_product(Q[i : i + m], T)
19041857
distance_matrix[i, :] = _mass(
19051858
Q[i : i + m],
19061859
T,

stumpy/scrump.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import numpy as np
77
from numba import njit, prange
88

9-
from . import config, core
9+
from . import config, core, sdp
1010
from .scraamp import prescraamp, scraamp
1111
from .stump import _stump
1212

@@ -235,7 +235,7 @@ def _compute_PI(
235235
QT = np.empty(w, dtype=np.float64)
236236
for i in indices[start:stop]:
237237
Q = T_A[i : i + m]
238-
QT[:] = core._sliding_dot_product(Q, T_B)
238+
QT[:] = sdp._njit_sliding_dot_product(Q, T_B)
239239
squared_distance_profile[:] = core._calculate_squared_distance_profile(
240240
m,
241241
QT,

stumpy/sdp.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import numpy as np
2+
from numba import njit
3+
from scipy.signal import convolve
4+
5+
from . import config
6+
7+
8+
@njit(fastmath=config.STUMPY_FASTMATH_TRUE)
9+
def _njit_sliding_dot_product(Q, T):
10+
"""
11+
A Numba JIT-compiled implementation of the sliding window dot product.
12+
13+
Parameters
14+
----------
15+
Q : numpy.ndarray
16+
Query array or subsequence
17+
18+
T : numpy.ndarray
19+
Time series or sequence
20+
21+
Returns
22+
-------
23+
out : numpy.ndarray
24+
Sliding dot product between `Q` and `T`.
25+
"""
26+
m = Q.shape[0]
27+
l = T.shape[0] - m + 1
28+
out = np.empty(l)
29+
for i in range(l):
30+
out[i] = np.dot(Q, T[i : i + m])
31+
32+
return out
33+
34+
35+
def _convolve_sliding_dot_product(Q, T):
36+
"""
37+
Use (direct or FFT) convolution to calculate the sliding window dot product.
38+
39+
Parameters
40+
----------
41+
Q : numpy.ndarray
42+
Query array or subsequence
43+
44+
T : numpy.ndarray
45+
Time series or sequence
46+
47+
Returns
48+
-------
49+
output : numpy.ndarray
50+
Sliding dot product between `Q` and `T`.
51+
"""
52+
n = T.shape[0]
53+
m = Q.shape[0]
54+
Qr = np.flipud(Q) # Reverse/flip Q
55+
QT = convolve(Qr, T)
56+
57+
return QT.real[m - 1 : n]
58+
59+
60+
def _sliding_dot_product(Q, T):
61+
"""
62+
Compute the sliding dot product between `Q` and `T`
63+
64+
Parameters
65+
----------
66+
Q : numpy.ndarray
67+
Query array or subsequence
68+
69+
T : numpy.ndarray
70+
Time series or sequence
71+
72+
Returns
73+
-------
74+
out : numpy.ndarray
75+
Sliding dot product between `Q` and `T`.
76+
"""
77+
return _convolve_sliding_dot_product(Q, T)

tests/naive.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2379,3 +2379,11 @@ def mpdist_custom_func(P_ABBA, m, percentage, n_A, n_B):
23792379
MPdist = P_ABBA[k]
23802380

23812381
return MPdist
2382+
2383+
2384+
def rolling_window_dot_product(Q, T):
2385+
window = len(Q)
2386+
result = np.zeros(len(T) - window + 1)
2387+
for i in range(len(result)):
2388+
result[i] = np.dot(T[i : i + window], Q)
2389+
return result

tests/test_core.py

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,6 @@ def _gpu_searchsorted_kernel(a, v, bfs, nlevel, is_left, idx):
3535
TEST_THREADS_PER_BLOCK = 10
3636

3737

38-
def naive_rolling_window_dot_product(Q, T):
39-
window = len(Q)
40-
result = np.zeros(len(T) - window + 1)
41-
for i in range(len(result)):
42-
result[i] = np.dot(T[i : i + window], Q)
43-
return result
44-
45-
4638
def naive_compute_mean_std_multidimensional(T, m):
4739
n = T.shape[1]
4840
nrows, ncols = T.shape
@@ -208,16 +200,9 @@ def test_check_window_size_excl_zone():
208200
core.check_window_size(m, max_size=len(T), n=len(T))
209201

210202

211-
@pytest.mark.parametrize("Q, T", test_data)
212-
def test_njit_sliding_dot_product(Q, T):
213-
ref_mp = naive_rolling_window_dot_product(Q, T)
214-
comp_mp = core._sliding_dot_product(Q, T)
215-
npt.assert_almost_equal(ref_mp, comp_mp)
216-
217-
218203
@pytest.mark.parametrize("Q, T", test_data)
219204
def test_sliding_dot_product(Q, T):
220-
ref_mp = naive_rolling_window_dot_product(Q, T)
205+
ref_mp = naive.rolling_window_dot_product(Q, T)
221206
comp_mp = core.sliding_dot_product(Q, T)
222207
npt.assert_almost_equal(ref_mp, comp_mp)
223208

tests/test_precision.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import pytest
99
from numba import cuda
1010

11-
from stumpy import cache, config, core, fastmath
11+
from stumpy import cache, config, core, fastmath, sdp
1212

1313
if cuda.is_available():
1414
from stumpy.gpu_stump import gpu_stump
@@ -90,7 +90,7 @@ def test_calculate_squared_distance():
9090
k = n - m + 1
9191
for i in range(k):
9292
for j in range(k):
93-
QT_i = core._sliding_dot_product(T[i : i + m], T)
93+
QT_i = sdp._njit_sliding_dot_product(T[i : i + m], T)
9494
dist_ij = core._calculate_squared_distance(
9595
m,
9696
QT_i[j],
@@ -102,7 +102,7 @@ def test_calculate_squared_distance():
102102
T_subseq_isconstant[j],
103103
)
104104

105-
QT_j = core._sliding_dot_product(T[j : j + m], T)
105+
QT_j = sdp._njit_sliding_dot_product(T[j : j + m], T)
106106
dist_ji = core._calculate_squared_distance(
107107
m,
108108
QT_j[i],

tests/test_sdp.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import naive
2+
import numpy as np
3+
import pytest
4+
from numpy import testing as npt
5+
6+
from stumpy import sdp
7+
8+
test_data = [
9+
(np.array([-1, 1, 2], dtype=np.float64), np.array(range(5), dtype=np.float64)),
10+
(
11+
np.array([9, 8100, -60], dtype=np.float64),
12+
np.array([584, -11, 23, 79, 1001], dtype=np.float64),
13+
),
14+
(np.random.uniform(-1000, 1000, [8]), np.random.uniform(-1000, 1000, [64])),
15+
]
16+
17+
18+
@pytest.mark.parametrize("Q, T", test_data)
19+
def test_njit_sliding_dot_product(Q, T):
20+
ref_mp = naive.rolling_window_dot_product(Q, T)
21+
comp_mp = sdp._njit_sliding_dot_product(Q, T)
22+
npt.assert_almost_equal(ref_mp, comp_mp)
23+
24+
25+
@pytest.mark.parametrize("Q, T", test_data)
26+
def test_convolve_sliding_dot_product(Q, T):
27+
ref_mp = naive.rolling_window_dot_product(Q, T)
28+
comp_mp = sdp._convolve_sliding_dot_product(Q, T)
29+
npt.assert_almost_equal(ref_mp, comp_mp)
30+
31+
32+
@pytest.mark.parametrize("Q, T", test_data)
33+
def test_sliding_dot_product(Q, T):
34+
ref_mp = naive.rolling_window_dot_product(Q, T)
35+
comp_mp = sdp._sliding_dot_product(Q, T)
36+
npt.assert_almost_equal(ref_mp, comp_mp)

0 commit comments

Comments
 (0)