Skip to content

Commit 8748994

Browse files
Fixed #880 Add param isconstant to gpu_ostinato and gpu_stimp (#883)
* add param isconstant to gpu_ostinato * add isconstant to gpu_stimp and add test functions * minor changes * minor change * Removed bad test function
1 parent 6597d45 commit 8748994

File tree

4 files changed

+157
-8
lines changed

4 files changed

+157
-8
lines changed

stumpy/gpu_ostinato.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,11 @@
88
from .ostinato import _get_central_motif, _ostinato
99

1010

11-
@core.non_normalized(gpu_aamp_ostinato)
12-
def gpu_ostinato(Ts, m, device_id=0, normalize=True, p=2.0):
11+
@core.non_normalized(
12+
gpu_aamp_ostinato,
13+
exclude=["normalize", "p", "Ts_subseq_isconstant"],
14+
)
15+
def gpu_ostinato(Ts, m, device_id=0, normalize=True, p=2.0, Ts_subseq_isconstant=None):
1316
"""
1417
Find the z-normalized consensus motif of multiple time series with one or more GPU
1518
devices
@@ -43,6 +46,9 @@ def gpu_ostinato(Ts, m, device_id=0, normalize=True, p=2.0):
4346
and the Euclidean distance, respectively. This parameter is ignored when
4447
`normalize == True`.
4548
49+
Ts_subseq_isconstant : list, default None
50+
A list of rolling window isconstant for each time series in `Ts`.
51+
4652
Returns
4753
-------
4854
central_radius : float
@@ -99,11 +105,15 @@ def gpu_ostinato(Ts, m, device_id=0, normalize=True, p=2.0):
99105
if not isinstance(Ts, list): # pragma: no cover
100106
raise ValueError(f"`Ts` is of type `{type(Ts)}` but a `list` is expected")
101107

102-
Ts_subseq_isconstant = [None] * len(Ts)
108+
if Ts_subseq_isconstant is None:
109+
Ts_subseq_isconstant = [None] * len(Ts)
110+
103111
M_Ts = [None] * len(Ts)
104112
Σ_Ts = [None] * len(Ts)
105113
for i, T in enumerate(Ts):
106-
Ts[i], M_Ts[i], Σ_Ts[i], Ts_subseq_isconstant[i] = core.preprocess(T, m)
114+
Ts[i], M_Ts[i], Σ_Ts[i], Ts_subseq_isconstant[i] = core.preprocess(
115+
T, m, T_subseq_isconstant=Ts_subseq_isconstant[i]
116+
)
107117

108118
bsf_radius, bsf_Ts_idx, bsf_subseq_idx = _ostinato(
109119
Ts, m, M_Ts, Σ_Ts, Ts_subseq_isconstant, device_id=device_id, mp_func=gpu_stump

stumpy/gpu_stimp.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
@core.non_normalized(
1212
gpu_aamp_stimp,
13-
exclude=["pre_scrump", "normalize", "p", "pre_scraamp"],
13+
exclude=["pre_scrump", "normalize", "p", "pre_scraamp", "T_subseq_isconstant_func"],
1414
replace={"pre_scrump": "pre_scraamp"},
1515
)
1616
class gpu_stimp(_stimp):
@@ -24,16 +24,16 @@ class gpu_stimp(_stimp):
2424
T : numpy.ndarray
2525
The time series or sequence for which to compute the pan matrix profile
2626
27-
m_start : int, default 3
27+
min_m : int, default 3
2828
The starting (or minimum) subsequence window size for which a matrix profile
2929
may be computed
3030
31-
m_stop : int, default None
31+
max_m : int, default None
3232
The stopping (or maximum) subsequence window size for which a matrix profile
3333
may be computed. When `m_stop = Non`, this is set to the maximum allowable
3434
subsequence window size
3535
36-
m_step : int, default 1
36+
step : int, default 1
3737
The step between subsequence window sizes
3838
3939
device_id : int or list, default 0
@@ -53,6 +53,14 @@ class gpu_stimp(_stimp):
5353
and the Euclidean distance, respectively. This parameter is ignored when
5454
`normalize == True`.
5555
56+
T_subseq_isconstant_func : function, default None
57+
A custom, user-defined function that returns a boolean array that indicates
58+
whether a subsequence in `T` is constant (True). The function must only take
59+
two arguments, `a`, a 1-D array, and `w`, the window size, while additional
60+
arguments may be specified by currying the user-defined function using
61+
`functools.partial`. Any subsequence with at least one np.nan/np.inf will
62+
automatically have its corresponding value set to False in this boolean array.
63+
5664
Attributes
5765
----------
5866
PAN_ : numpy.ndarray
@@ -106,6 +114,7 @@ def __init__(
106114
device_id=0,
107115
normalize=True,
108116
p=2.0,
117+
T_subseq_isconstant_func=None,
109118
):
110119
"""
111120
Initialize the `stimp` object and compute the Pan Matrix Profile
@@ -144,6 +153,15 @@ def __init__(
144153
is typically used with `p` being 1 or 2, which correspond to the Manhattan
145154
distance and the Euclidean distance, respectively. This parameter is ignored
146155
when `normalize == True`.
156+
157+
T_subseq_isconstant_func : function, default None
158+
A custom, user-defined function that returns a boolean array that indicates
159+
whether a subsequence in `T` is constant (True). The function must only take
160+
two arguments, `a`, a 1-D array, and `w`, the window size, while additional
161+
arguments may be specified by currying the user-defined function using
162+
`functools.partial`. Any subsequence with at least one np.nan/np.inf will
163+
automatically have its corresponding value set to False in this boolean
164+
array.
147165
"""
148166
super().__init__(
149167
T,
@@ -153,5 +171,6 @@ def __init__(
153171
percentage=1.0,
154172
pre_scrump=False,
155173
device_id=device_id,
174+
T_subseq_isconstant_func=T_subseq_isconstant_func,
156175
mp_func=gpu_stump,
157176
)

tests/test_gpu_ostinato.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import functools
2+
13
import numpy as np
24
import numpy.testing as npt
35
from numba import cuda
@@ -52,3 +54,63 @@ def test_deterministic_gpu_ostinato(seed):
5254
npt.assert_almost_equal(ref_radius, comp_radius)
5355
npt.assert_almost_equal(ref_Ts_idx, comp_Ts_idx)
5456
npt.assert_almost_equal(ref_subseq_idx, comp_subseq_idx)
57+
58+
59+
@pytest.mark.filterwarnings("ignore", category=NumbaPerformanceWarning)
60+
@pytest.mark.parametrize(
61+
"seed", np.random.choice(np.arange(10000), size=25, replace=False)
62+
)
63+
@patch("stumpy.config.STUMPY_THREADS_PER_BLOCK", TEST_THREADS_PER_BLOCK)
64+
def test_random_gpu_ostinato_with_isconstant(seed):
65+
isconstant_custom_func = functools.partial(
66+
naive.isconstant_func_stddev_threshold, quantile_threshold=0.05
67+
)
68+
69+
m = 50
70+
np.random.seed(seed)
71+
Ts = [np.random.rand(n) for n in [64, 128, 256]]
72+
Ts_subseq_isconstant = [isconstant_custom_func for _ in range(len(Ts))]
73+
74+
ref_radius, ref_Ts_idx, ref_subseq_idx = naive.ostinato(
75+
Ts, m, Ts_subseq_isconstant=Ts_subseq_isconstant
76+
)
77+
comp_radius, comp_Ts_idx, comp_subseq_idx = gpu_ostinato(
78+
Ts, m, Ts_subseq_isconstant=Ts_subseq_isconstant
79+
)
80+
81+
npt.assert_almost_equal(ref_radius, comp_radius)
82+
npt.assert_almost_equal(ref_Ts_idx, comp_Ts_idx)
83+
npt.assert_almost_equal(ref_subseq_idx, comp_subseq_idx)
84+
85+
86+
@pytest.mark.filterwarnings("ignore", category=NumbaPerformanceWarning)
87+
@pytest.mark.parametrize("seed", [79, 109, 112, 133, 151, 161, 251, 275, 309, 355])
88+
@patch("stumpy.config.STUMPY_THREADS_PER_BLOCK", TEST_THREADS_PER_BLOCK)
89+
def test_deterministic_gpu_ostinato_with_isconstant(seed):
90+
isconstant_custom_func = functools.partial(
91+
naive.isconstant_func_stddev_threshold, quantile_threshold=0.05
92+
)
93+
94+
m = 50
95+
np.random.seed(seed)
96+
Ts = [np.random.rand(n) for n in [64, 128, 256]]
97+
98+
l = 64 - m + 1
99+
subseq_isconsant = np.full(l, 0, dtype=bool)
100+
subseq_isconsant[np.random.randint(0, l)] = True
101+
Ts_subseq_isconstant = [
102+
subseq_isconsant,
103+
None,
104+
isconstant_custom_func,
105+
]
106+
107+
ref_radius, ref_Ts_idx, ref_subseq_idx = naive.ostinato(
108+
Ts, m, Ts_subseq_isconstant=Ts_subseq_isconstant
109+
)
110+
comp_radius, comp_Ts_idx, comp_subseq_idx = gpu_ostinato(
111+
Ts, m, Ts_subseq_isconstant=Ts_subseq_isconstant
112+
)
113+
114+
npt.assert_almost_equal(ref_radius, comp_radius)
115+
npt.assert_almost_equal(ref_Ts_idx, comp_Ts_idx)
116+
npt.assert_almost_equal(ref_subseq_idx, comp_subseq_idx)

tests/test_gpu_stimp.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import functools
12
from unittest.mock import patch
23

34
import numpy as np
@@ -71,3 +72,60 @@ def test_gpu_stimp(T):
7172
naive.replace_inf(cmp_pan)
7273

7374
npt.assert_almost_equal(ref_pan, cmp_pan)
75+
76+
77+
@pytest.mark.filterwarnings("ignore", category=NumbaPerformanceWarning)
78+
@patch("stumpy.config.STUMPY_THREADS_PER_BLOCK", TEST_THREADS_PER_BLOCK)
79+
def test_gpu_stimp_with_isconstant():
80+
T = np.random.uniform(-1, 1, [64])
81+
isconstant_func = functools.partial(
82+
naive.isconstant_func_stddev_threshold, stddev_threshold=0.5
83+
)
84+
85+
threshold = 0.2
86+
min_m = 3
87+
n = T.shape[0] - min_m + 1
88+
89+
pan = gpu_stimp(
90+
T,
91+
min_m=min_m,
92+
max_m=None,
93+
step=1,
94+
# normalize=True,
95+
T_subseq_isconstant_func=isconstant_func,
96+
)
97+
98+
for i in range(n):
99+
pan.update()
100+
101+
ref_PAN = np.full((pan.M_.shape[0], T.shape[0]), fill_value=np.inf)
102+
103+
for idx, m in enumerate(pan.M_[:n]):
104+
zone = int(np.ceil(m / 4))
105+
ref_mp = naive.stump(
106+
T,
107+
m,
108+
T_B=None,
109+
exclusion_zone=zone,
110+
T_A_subseq_isconstant=isconstant_func,
111+
)
112+
ref_PAN[pan._bfs_indices[idx], : ref_mp.shape[0]] = ref_mp[:, 0]
113+
114+
# Compare raw pan
115+
cmp_PAN = pan._PAN
116+
117+
naive.replace_inf(ref_PAN)
118+
naive.replace_inf(cmp_PAN)
119+
120+
npt.assert_almost_equal(ref_PAN, cmp_PAN)
121+
122+
# Compare transformed pan
123+
cmp_pan = pan.PAN_
124+
ref_pan = naive.transform_pan(
125+
pan._PAN, pan._M, threshold, pan._bfs_indices, pan._n_processed
126+
)
127+
128+
naive.replace_inf(ref_pan)
129+
naive.replace_inf(cmp_pan)
130+
131+
npt.assert_almost_equal(ref_pan, cmp_pan)

0 commit comments

Comments
 (0)