Skip to content

Commit d4c6a21

Browse files
Fixed #881 Snippets Unit Test Assertion Failure (#892)
* add test function to test_precision, expecting assertion failure * add post processing to have symmetry property of distance without loss of precision * retrieve code, expecting error * reduce arithmetic operation to reduce loss of precision * revise equation to mitigate loss of precision * add test function to check precision for _calculate_squared_distance * minor change * change order of test function in test_precision * add parentheses to make sure the ouput remains the same when values are swapped * avoid fastmath reassoc flag to increase floating point precision * enhance test function * add fatmath flag reassoc and remove ninf * minor change in test function * increase decimal coverage in assertion * remove nnan from fastmath to be consistent with other func in core.py * use assert to make sure two values are identical * re-order arithmetic operation to increase safety and stability of the code regarding precision * add test function for distance symmetry propery in gpu * add parentheses and change order of multiplication * skip testing gpu-based test func if cude is not available * perform assersion with numpy testing instead of assert to be more explicit * reduce precision to pass tests for minimum version * add comment to test function to explain its purpose
1 parent 900b10c commit d4c6a21

File tree

3 files changed

+148
-5
lines changed

3 files changed

+148
-5
lines changed

stumpy/core.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1042,7 +1042,7 @@ def compute_mean_std(T, m):
10421042

10431043
@njit(
10441044
# "f8(i8, f8, f8, f8, f8, f8)",
1045-
fastmath=True
1045+
fastmath={"nsz", "arcp", "contract", "afn", "reassoc"}
10461046
)
10471047
def _calculate_squared_distance(
10481048
m, QT, μ_Q, σ_Q, M_T, Σ_T, Q_subseq_isconstant, T_subseq_isconstant
@@ -1097,10 +1097,10 @@ def _calculate_squared_distance(
10971097
elif Q_subseq_isconstant or T_subseq_isconstant:
10981098
D_squared = m
10991099
else:
1100-
denom = m * σ_Q * Σ_T
1100+
denom = (σ_Q * Σ_T) * m
11011101
denom = max(denom, config.STUMPY_DENOM_THRESHOLD)
11021102

1103-
ρ = (QT - m * μ_Q * M_T) / denom
1103+
ρ = (QT - (μ_Q * M_T) * m) / denom
11041104
ρ = min(ρ, 1.0)
11051105

11061106
D_squared = np.abs(2 * m * (1.0 - ρ))

stumpy/gpu_stump.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,9 +178,9 @@ def _compute_and_update_PI_kernel(
178178
elif Q_subseq_isconstant[i] or T_subseq_isconstant[j]:
179179
p_norm = m
180180
else:
181-
denom = m * σ_Q[i] * Σ_T[j]
181+
denom = (σ_Q[i] * Σ_T[j]) * m
182182
denom = max(denom, config.STUMPY_DENOM_THRESHOLD)
183-
ρ = (QT_out[i] - m * μ_Q[i] * M_T[j]) / denom
183+
ρ = (QT_out[i] - (μ_Q[i] * M_T[j]) * m) / denom
184184
ρ = min(ρ, 1.0)
185185
p_norm = 2 * m * (1.0 - ρ)
186186

tests/test_precision.py

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,22 @@
1+
import functools
2+
from unittest.mock import patch
3+
14
import naive
25
import numpy as np
36
import numpy.testing as npt
7+
import pytest
8+
from numba import cuda
49

510
import stumpy
611
from stumpy import config, core
712

13+
try:
14+
from numba.errors import NumbaPerformanceWarning
15+
except ModuleNotFoundError:
16+
from numba.core.errors import NumbaPerformanceWarning
17+
18+
TEST_THREADS_PER_BLOCK = 10
19+
820

921
def test_mpdist_snippets_s():
1022
# This test function raises an error if the distance between
@@ -55,3 +67,134 @@ def test_distace_profile():
5567
)
5668

5769
npt.assert_almost_equal(D_ref, D_comp)
70+
71+
72+
def test_calculate_squared_distance():
73+
# This test function raises an error if the distance between a subsequence
74+
# and another does not satisfy the symmetry property.
75+
seed = 332
76+
np.random.seed(seed)
77+
T = np.random.uniform(-1000.0, 1000.0, [64])
78+
m = 3
79+
80+
T_subseq_isconstant = core.rolling_isconstant(T, m)
81+
M_T, Σ_T = core.compute_mean_std(T, m)
82+
83+
n = len(T)
84+
k = n - m + 1
85+
for i in range(k):
86+
for j in range(k):
87+
QT_i = core._sliding_dot_product(T[i : i + m], T)
88+
dist_ij = core._calculate_squared_distance(
89+
m,
90+
QT_i[j],
91+
M_T[i],
92+
Σ_T[i],
93+
M_T[j],
94+
Σ_T[j],
95+
T_subseq_isconstant[i],
96+
T_subseq_isconstant[j],
97+
)
98+
99+
QT_j = core._sliding_dot_product(T[j : j + m], T)
100+
dist_ji = core._calculate_squared_distance(
101+
m,
102+
QT_j[i],
103+
M_T[j],
104+
Σ_T[j],
105+
M_T[i],
106+
Σ_T[i],
107+
T_subseq_isconstant[j],
108+
T_subseq_isconstant[i],
109+
)
110+
111+
comp = dist_ij - dist_ji
112+
ref = 0.0
113+
114+
npt.assert_almost_equal(ref, comp, decimal=14)
115+
116+
117+
def test_snippets():
118+
# This test function raises an error if there is a considerable loss of precision
119+
# that violates the symmetry property of a distance measure.
120+
m = 10
121+
k = 3
122+
s = 3
123+
seed = 332
124+
np.random.seed(seed)
125+
T = np.random.uniform(-1000.0, 1000.0, [64])
126+
127+
isconstant_custom_func = functools.partial(
128+
naive.isconstant_func_stddev_threshold, quantile_threshold=0.05
129+
)
130+
(
131+
ref_snippets,
132+
ref_indices,
133+
ref_profiles,
134+
ref_fractions,
135+
ref_areas,
136+
ref_regimes,
137+
) = naive.mpdist_snippets(
138+
T, m, k, s=s, mpdist_T_subseq_isconstant=isconstant_custom_func
139+
)
140+
(
141+
cmp_snippets,
142+
cmp_indices,
143+
cmp_profiles,
144+
cmp_fractions,
145+
cmp_areas,
146+
cmp_regimes,
147+
) = stumpy.snippets(T, m, k, s=s, mpdist_T_subseq_isconstant=isconstant_custom_func)
148+
149+
npt.assert_almost_equal(
150+
ref_snippets, cmp_snippets, decimal=config.STUMPY_TEST_PRECISION
151+
)
152+
npt.assert_almost_equal(
153+
ref_indices, cmp_indices, decimal=config.STUMPY_TEST_PRECISION
154+
)
155+
npt.assert_almost_equal(
156+
ref_profiles, cmp_profiles, decimal=config.STUMPY_TEST_PRECISION
157+
)
158+
npt.assert_almost_equal(
159+
ref_fractions, cmp_fractions, decimal=config.STUMPY_TEST_PRECISION
160+
)
161+
npt.assert_almost_equal(ref_areas, cmp_areas, decimal=config.STUMPY_TEST_PRECISION)
162+
npt.assert_almost_equal(ref_regimes, cmp_regimes)
163+
164+
165+
@pytest.mark.filterwarnings("ignore", category=NumbaPerformanceWarning)
166+
@patch("stumpy.config.STUMPY_THREADS_PER_BLOCK", TEST_THREADS_PER_BLOCK)
167+
def test_distance_symmetry_property_in_gpu():
168+
if not cuda.is_available(): # pragma: no cover
169+
pytest.skip("Skipping Tests No GPUs Available")
170+
171+
# This test function raises an error if the distance between a subsequence
172+
# and another one does not satisfy the symmetry property.
173+
seed = 332
174+
np.random.seed(seed)
175+
T = np.random.uniform(-1000.0, 1000.0, [64])
176+
m = 3
177+
178+
i, j = 2, 10
179+
# M_T, Σ_T = core.compute_mean_std(T, m)
180+
# Σ_T[i] is `650.912209452633`
181+
# Σ_T[j] is `722.0717285148525`
182+
183+
# This test raises an error if arithmetic operation in ...
184+
# ... `gpu_stump._compute_and_update_PI_kernel` does not
185+
# generates the same result if values of variable for mean and std
186+
# are swapped.
187+
188+
T_A = T[i : i + m]
189+
T_B = T[j : j + m]
190+
191+
mp_AB = stumpy.gpu_stump(T_A, m, T_B)
192+
mp_BA = stumpy.gpu_stump(T_B, m, T_A)
193+
194+
d_ij = mp_AB[0, 0]
195+
d_ji = mp_BA[0, 0]
196+
197+
comp = d_ij - d_ji
198+
ref = 0.0
199+
200+
npt.assert_almost_equal(comp, ref, decimal=15)

0 commit comments

Comments
 (0)