import functools
import math
import os
from unittest.mock import patch
import mersenne
import naive
import numpy as np
import numpy.testing as npt
import pandas as pd
import pytest
from numba import cuda
from scipy.spatial.distance import cdist
from stumpy import config, core, rng
from stumpy.stump import stump
if cuda.is_available():
@cuda.jit("(f8[:, :], f8[:], i8[:], i8, b1, i8[:])")
def _gpu_searchsorted_kernel(a, v, bfs, nlevel, is_left, idx):
i = cuda.grid(1)
if i < a.shape[0]:
if is_left:
idx[i] = core._gpu_searchsorted_left(a[i], v[i], bfs, nlevel)
else:
idx[i] = core._gpu_searchsorted_right(a[i], v[i], bfs, nlevel)
try:
from numba.errors import NumbaPerformanceWarning
except ModuleNotFoundError:
from numba.core.errors import NumbaPerformanceWarning
TEST_THREADS_PER_BLOCK = 10
def naive_compute_mean_std_multidimensional(T, m):
n = T.shape[1]
nrows, ncols = T.shape
cumsum_T = np.empty((nrows, ncols + 1))
np.cumsum(T, axis=1, out=cumsum_T[:, 1:])
cumsum_T[:, 0] = 0
cumsum_T_squared = np.empty((nrows, ncols + 1))
np.cumsum(np.square(T), axis=1, out=cumsum_T_squared[:, 1:])
cumsum_T_squared[:, 0] = 0
subseq_sum_T = cumsum_T[:, m:] - cumsum_T[:, : n - m + 1]
subseq_sum_T_squared = cumsum_T_squared[:, m:] - cumsum_T_squared[:, : n - m + 1]
M_T = subseq_sum_T / m
Σ_T = np.abs((subseq_sum_T_squared / m) - np.square(M_T))
Σ_T = np.sqrt(Σ_T)
return M_T, Σ_T
def naive_idx_to_mp(I, T, m, normalize=True, p=2.0, T_subseq_isconstant=None):
I = I.astype(np.int64)
T = T.copy()
if normalize:
if T_subseq_isconstant is None:
T_subseq_isconstant = naive.rolling_isconstant(T, m)
T_isfinite = np.isfinite(T)
T_subseq_isfinite = np.all(core.rolling_window(T_isfinite, m), axis=1)
T[~T_isfinite] = 0.0
T_subseqs = core.rolling_window(T, m)
nn_subseqs = T_subseqs[I]
if normalize:
P = naive.distance(
naive.z_norm(T_subseqs, axis=1), naive.z_norm(nn_subseqs, axis=1), axis=1
)
for i, nn_i in enumerate(I):
if T_subseq_isconstant[i] and T_subseq_isconstant[nn_i]:
P[i] = 0
elif T_subseq_isconstant[i] or T_subseq_isconstant[nn_i]:
P[i] = np.sqrt(m)
else:
pass
else:
P = naive.distance(T_subseqs, nn_subseqs, axis=1, p=p)
P[~T_subseq_isfinite] = np.inf
P[I < 0] = np.inf
return P
def split(node, out):
mid = len(node) // 2
out.append(node[mid])
return node[:mid], node[mid + 1 :]
def naive_bfs_indices(n, fill_value=None):
a = np.arange(n)
nodes = [a.tolist()]
out = []
while nodes:
tmp = []
for node in nodes:
for n in split(node, out):
if n:
tmp.append(n)
nodes = tmp
out = np.array(out)
if fill_value is not None:
remainder = out.shape[0]
level = 0
count = np.power(2, level)
while remainder >= count:
remainder -= count
level += 1
count = np.power(2, level)
if remainder > 0:
out = out[:-remainder]
last_level = np.empty(np.power(2, level), dtype=np.int64)
last_level[0::2] = out[-np.power(2, level - 1) :] - 1
last_level[1::2] = out[-np.power(2, level - 1) :] + 1
mask = np.isin(last_level, out)
last_level[mask] = fill_value
n = len(a)
last_level[last_level >= n] = fill_value
out = np.concatenate([out, last_level])
return out
test_data = [
(np.array([-1, 1, 2], dtype=np.float64), np.array(range(5), dtype=np.float64)),
(
np.array([9, 8100, -60], dtype=np.float64),
np.array([584, -11, 23, 79, 1001], dtype=np.float64),
),
(rng.RNG.uniform(-1000, 1000, [8]), rng.RNG.uniform(-1000, 1000, [64])),
]
n = list(range(1, 50))
def test_check_bad_dtype():
for dtype in [np.int32, np.int64, np.float32]:
with pytest.raises(TypeError):
core.check_dtype(rng.RNG.rand(10).astype(dtype))
def test_check_dtype_float64():
assert core.check_dtype(rng.RNG.rand(10))
def test_get_max_window_size():
for n in range(3, 10):
ref_max_m = (
int(
n
- math.floor(
(n + (config.STUMPY_EXCL_ZONE_DENOM - 1))
// (config.STUMPY_EXCL_ZONE_DENOM + 1)
)
)
- 1
)
cmp_max_m = core.get_max_window_size(n)
assert ref_max_m == cmp_max_m
def test_check_window_size():
for m in range(-1, 3):
with pytest.raises(ValueError):
core.check_window_size(m)
def test_check_max_window_size():
for m in range(4, 7):
with pytest.raises(ValueError):
core.check_window_size(m, max_size=3)
def test_check_window_size_excl_zone():
T = rng.RNG.rand(10)
m = 7
with pytest.warns(UserWarning):
core.check_window_size(m, max_size=len(T), n=len(T))
@pytest.mark.parametrize("Q, T", test_data)
def test_sliding_dot_product(Q, T):
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
ref_var = np.nanvar(T)
cmp_var = core.welford_nanvar(T)
npt.assert_allclose(cmp_var, ref_var, atol=1.5e-07)
ref_var = np.nanvar(core.rolling_window(T, m), axis=1)
cmp_var = core.welford_nanvar(T, m)
npt.assert_allclose(cmp_var, ref_var, atol=1.5e-07)
def test_welford_nanvar_catastrophic_cancellation():
T = np.array([4.0, 7.0, 13.0, 16.0, 10.0]) + 10**8
m = 4
ref_var = np.nanvar(core.rolling_window(T, m), axis=1)
cmp_var = core.welford_nanvar(T, m)
npt.assert_allclose(cmp_var, ref_var, atol=1.5e-07)
def test_welford_nanvar_nan():
T = rng.RNG.rand(64)
m = 10
T[1] = np.nan
T[10] = np.nan
T[13:18] = np.nan
ref_var = np.nanvar(T)
cmp_var = core.welford_nanvar(T)
npt.assert_allclose(cmp_var, ref_var, atol=1.5e-07)
ref_var = np.nanvar(core.rolling_window(T, m), axis=1)
cmp_var = core.welford_nanvar(T, m)
npt.assert_allclose(cmp_var, ref_var, atol=1.5e-07)
def test_welford_nanstd():
T = rng.RNG.rand(64)
m = 10
ref_var = np.nanstd(T)
cmp_var = core.welford_nanstd(T)
npt.assert_allclose(cmp_var, ref_var, atol=1.5e-07)
ref_var = np.nanstd(core.rolling_window(T, m), axis=1)
cmp_var = core.welford_nanstd(T, m)
npt.assert_allclose(cmp_var, ref_var, atol=1.5e-07)
def test_rolling_std_1d():
a = rng.RNG.rand(64)
for w in range(3, 6):
ref_std = naive.rolling_nanstd(a, w)
cmp_std = core.rolling_nanstd(a, w)
npt.assert_allclose(cmp_std, ref_std, atol=1.5e-07)
cmp_std = core.rolling_nanstd(a, w, welford=True)
npt.assert_allclose(cmp_std, ref_std, atol=1.5e-07)
def test_rolling_std_2d():
w = 5
for n_rows in range(1, 4):
a = rng.RNG.rand(n_rows * 64).reshape(n_rows, 64)
ref_std = naive.rolling_nanstd(a, w)
cmp_std = core.rolling_nanstd(a, w)
npt.assert_allclose(cmp_std, ref_std, atol=1.5e-07)
cmp_std = core.rolling_nanstd(a, w, welford=True)
npt.assert_allclose(cmp_std, ref_std, atol=1.5e-07)
def test_rolling_nanmin_1d():
T = rng.RNG.rand(64)
for m in range(1, 12):
ref_min = np.nanmin(T)
cmp_min = core._rolling_nanmin_1d(T)
npt.assert_allclose(cmp_min, ref_min, atol=1.5e-07)
ref_min = np.nanmin(T)
cmp_min = core._rolling_nanmin_1d(T)
npt.assert_allclose(cmp_min, ref_min, atol=1.5e-07)
def test_rolling_nanmin():
T = rng.RNG.rand(64)
for m in range(1, 12):
ref_min = np.nanmin(core.rolling_window(T, m), axis=1)
cmp_min = core.rolling_nanmin(T, m)
npt.assert_allclose(cmp_min, ref_min, atol=1.5e-07)
ref_min = np.nanmin(core.rolling_window(T, m), axis=1)
cmp_min = core.rolling_nanmin(T, m)
npt.assert_allclose(cmp_min, ref_min, atol=1.5e-07)
def test_rolling_nanmax_1d():
T = rng.RNG.rand(64)
for m in range(1, 12):
ref_max = np.nanmax(T)
cmp_max = core._rolling_nanmax_1d(T)
npt.assert_allclose(cmp_max, ref_max, atol=1.5e-07)
ref_max = np.nanmax(T)
cmp_max = core._rolling_nanmax_1d(T)
npt.assert_allclose(cmp_max, ref_max, atol=1.5e-07)
def test_rolling_nanmax():
T = rng.RNG.rand(64)
for m in range(1, 12):
ref_max = np.nanmax(core.rolling_window(T, m), axis=1)
cmp_max = core.rolling_nanmax(T, m)
npt.assert_allclose(cmp_max, ref_max, atol=1.5e-07)
ref_max = np.nanmax(core.rolling_window(T, m), axis=1)
cmp_max = core.rolling_nanmax(T, m)
npt.assert_allclose(cmp_max, ref_max, atol=1.5e-07)
@pytest.mark.parametrize("Q, T", test_data)
def test_compute_mean_std(Q, T):
m = Q.shape[0]
ref_μ_Q, ref_σ_Q = naive.compute_mean_std(Q, m)
ref_M_T, ref_Σ_T = naive.compute_mean_std(T, m)
cmp_μ_Q, cmp_σ_Q = core.compute_mean_std(Q, m)
cmp_M_T, cmp_Σ_T = core.compute_mean_std(T, m)
npt.assert_allclose(cmp_μ_Q, ref_μ_Q, atol=1.5e-07)
npt.assert_allclose(cmp_σ_Q, ref_σ_Q, atol=1.5e-07)
npt.assert_allclose(cmp_M_T, ref_M_T, atol=1.5e-07)
npt.assert_allclose(cmp_Σ_T, ref_Σ_T, atol=1.5e-07)
@pytest.mark.parametrize("Q, T", test_data)
def test_compute_mean_std_chunked(Q, T):
m = Q.shape[0]
with patch("stumpy.config.STUMPY_MEAN_STD_NUM_CHUNKS", 2):
ref_μ_Q, ref_σ_Q = naive.compute_mean_std(Q, m)
ref_M_T, ref_Σ_T = naive.compute_mean_std(T, m)
cmp_μ_Q, cmp_σ_Q = core.compute_mean_std(Q, m)
cmp_M_T, cmp_Σ_T = core.compute_mean_std(T, m)
npt.assert_allclose(cmp_μ_Q, ref_μ_Q, atol=1.5e-07)
npt.assert_allclose(cmp_σ_Q, ref_σ_Q, atol=1.5e-07)
npt.assert_allclose(cmp_M_T, ref_M_T, atol=1.5e-07)
npt.assert_allclose(cmp_Σ_T, ref_Σ_T, atol=1.5e-07)
@pytest.mark.parametrize("Q, T", test_data)
def test_compute_mean_std_chunked_many(Q, T):
m = Q.shape[0]
with patch("stumpy.config.STUMPY_MEAN_STD_NUM_CHUNKS", 128):
ref_μ_Q, ref_σ_Q = naive.compute_mean_std(Q, m)
ref_M_T, ref_Σ_T = naive.compute_mean_std(T, m)
cmp_μ_Q, cmp_σ_Q = core.compute_mean_std(Q, m)
cmp_M_T, cmp_Σ_T = core.compute_mean_std(T, m)
npt.assert_allclose(cmp_μ_Q, ref_μ_Q, atol=1.5e-07)
npt.assert_allclose(cmp_σ_Q, ref_σ_Q, atol=1.5e-07)
npt.assert_allclose(cmp_M_T, ref_M_T, atol=1.5e-07)
npt.assert_allclose(cmp_Σ_T, ref_Σ_T, atol=1.5e-07)
@pytest.mark.parametrize("Q, T", test_data)
def test_compute_mean_std_multidimensional(Q, T):
m = Q.shape[0]
Q = np.array([Q, rng.RNG.uniform(-1000, 1000, [Q.shape[0]])])
T = np.array([T, T, rng.RNG.uniform(-1000, 1000, [T.shape[0]])])
ref_μ_Q, ref_σ_Q = naive_compute_mean_std_multidimensional(Q, m)
ref_M_T, ref_Σ_T = naive_compute_mean_std_multidimensional(T, m)
cmp_μ_Q, cmp_σ_Q = core.compute_mean_std(Q, m)
cmp_M_T, cmp_Σ_T = core.compute_mean_std(T, m)
npt.assert_allclose(cmp_μ_Q, ref_μ_Q, atol=1.5e-07)
npt.assert_allclose(cmp_σ_Q, ref_σ_Q, atol=1.5e-07)
npt.assert_allclose(cmp_M_T, ref_M_T, atol=1.5e-07)
npt.assert_allclose(cmp_Σ_T, ref_Σ_T, atol=1.5e-07)
@pytest.mark.parametrize("Q, T", test_data)
def test_compute_mean_std_multidimensional_chunked(Q, T):
m = Q.shape[0]
Q = np.array([Q, rng.RNG.uniform(-1000, 1000, [Q.shape[0]])])
T = np.array([T, T, rng.RNG.uniform(-1000, 1000, [T.shape[0]])])
with patch("stumpy.config.STUMPY_MEAN_STD_NUM_CHUNKS", 2):
ref_μ_Q, ref_σ_Q = naive_compute_mean_std_multidimensional(Q, m)
ref_M_T, ref_Σ_T = naive_compute_mean_std_multidimensional(T, m)
cmp_μ_Q, cmp_σ_Q = core.compute_mean_std(Q, m)
cmp_M_T, cmp_Σ_T = core.compute_mean_std(T, m)
npt.assert_allclose(cmp_μ_Q, ref_μ_Q, atol=1.5e-07)
npt.assert_allclose(cmp_σ_Q, ref_σ_Q, atol=1.5e-07)
npt.assert_allclose(cmp_M_T, ref_M_T, atol=1.5e-07)
npt.assert_allclose(cmp_Σ_T, ref_Σ_T, atol=1.5e-07)
@pytest.mark.parametrize("Q, T", test_data)
def test_compute_mean_std_multidimensional_chunked_many(Q, T):
m = Q.shape[0]
Q = np.array([Q, rng.RNG.uniform(-1000, 1000, [Q.shape[0]])])
T = np.array([T, T, rng.RNG.uniform(-1000, 1000, [T.shape[0]])])
with patch("stumpy.config.STUMPY_MEAN_STD_NUM_CHUNKS", 128):
ref_μ_Q, ref_σ_Q = naive_compute_mean_std_multidimensional(Q, m)
ref_M_T, ref_Σ_T = naive_compute_mean_std_multidimensional(T, m)
cmp_μ_Q, cmp_σ_Q = core.compute_mean_std(Q, m)
cmp_M_T, cmp_Σ_T = core.compute_mean_std(T, m)
npt.assert_allclose(cmp_μ_Q, ref_μ_Q, atol=1.5e-07)
npt.assert_allclose(cmp_σ_Q, ref_σ_Q, atol=1.5e-07)
npt.assert_allclose(cmp_M_T, ref_M_T, atol=1.5e-07)
npt.assert_allclose(cmp_Σ_T, ref_Σ_T, atol=1.5e-07)
@pytest.mark.parametrize("Q, T", test_data)
def test_calculate_squared_distance_profile(Q, T):
m = Q.shape[0]
ref = (
np.linalg.norm(
core.z_norm(core.rolling_window(T, m), 1) - core.z_norm(Q), axis=1
)
** 2
)
QT = core.sliding_dot_product(Q, T)
Q_subseq_isconstant = core.rolling_isconstant(Q, m)[0]
μ_Q, σ_Q = [arr[0] for arr in core.compute_mean_std(Q, m)]
T_subseq_isconstant = core.rolling_isconstant(T, m)
M_T, Σ_T = core.compute_mean_std(T, m)
cmp = core._calculate_squared_distance_profile(
m,
QT,
μ_Q,
σ_Q,
M_T,
Σ_T,
Q_subseq_isconstant,
T_subseq_isconstant,
)
npt.assert_allclose(cmp, ref, atol=1.5e-07)
@pytest.mark.parametrize("Q, T", test_data)
def test_calculate_distance_profile(Q, T):
m = Q.shape[0]
ref = np.linalg.norm(
core.z_norm(core.rolling_window(T, m), 1) - core.z_norm(Q), axis=1
)
QT = core.sliding_dot_product(Q, T)
Q_subseq_isconstant = core.rolling_isconstant(Q, m)[0]
μ_Q, σ_Q = [arr[0] for arr in core.compute_mean_std(Q, m)]
T_subseq_isconstant = core.rolling_isconstant(T, m)
M_T, Σ_T = core.compute_mean_std(T, m)
cmp = core.calculate_distance_profile(
m,
QT,
μ_Q,
σ_Q,
M_T,
Σ_T,
Q_subseq_isconstant,
T_subseq_isconstant,
)
npt.assert_allclose(cmp, ref, atol=1.5e-07)
@pytest.mark.parametrize("Q, T", test_data)
def test_mueen_calculate_distance_profile(Q, T):
m = Q.shape[0]
ref = np.linalg.norm(
core.z_norm(core.rolling_window(T, m), 1) - core.z_norm(Q), axis=1
)
cmp = core.mueen_calculate_distance_profile(Q, T)
npt.assert_allclose(cmp, ref, atol=1.5e-07)
@pytest.mark.parametrize("Q, T", test_data)
def test_mass(Q, T):
Q = Q.copy()
T = T.copy()
m = Q.shape[0]
ref = np.linalg.norm(
core.z_norm(core.rolling_window(T, m), 1) - core.z_norm(Q), axis=1
)
cmp = core.mass(Q, T)
npt.assert_allclose(cmp, ref, atol=1.5e-07)
@pytest.mark.parametrize("Q, T", test_data)
def test_mass_Q_nan(Q, T):
Q = Q.copy()
Q[1] = np.nan
T = T.copy()
m = Q.shape[0]
ref = np.linalg.norm(
core.z_norm(core.rolling_window(T, m), 1) - core.z_norm(Q), axis=1
)
ref[np.isnan(ref)] = np.inf
cmp = core.mass(Q, T)
npt.assert_allclose(cmp, ref, atol=1.5e-07)
@pytest.mark.parametrize("Q, T", test_data)
def test_mass_Q_inf(Q, T):
Q = Q.copy()
Q[1] = np.inf
T = T.copy()
m = Q.shape[0]
ref = np.linalg.norm(
core.z_norm(core.rolling_window(T, m), 1) - core.z_norm(Q), axis=1
)
ref[np.isnan(ref)] = np.inf
cmp = core.mass(Q, T)
npt.assert_allclose(cmp, ref, atol=1.5e-07)
T[1] = 1e10
@pytest.mark.parametrize("Q, T", test_data)
def test_mass_T_nan(Q, T):
Q = Q.copy()
T = T.copy()
T[1] = np.nan
m = Q.shape[0]
ref = np.linalg.norm(
core.z_norm(core.rolling_window(T, m), 1) - core.z_norm(Q), axis=1
)
ref[np.isnan(ref)] = np.inf
cmp = core.mass(Q, T)
npt.assert_allclose(cmp, ref, atol=1.5e-07)
@pytest.mark.parametrize("Q, T", test_data)
def test_mass_T_inf(Q, T):
Q = Q.copy()
T = T.copy()
T[1] = np.inf
m = Q.shape[0]
ref = np.linalg.norm(
core.z_norm(core.rolling_window(T, m), 1) - core.z_norm(Q), axis=1
)
ref[np.isnan(ref)] = np.inf
cmp = core.mass(Q, T)
npt.assert_allclose(cmp, ref, atol=1.5e-07)
T[1] = 1e10
@pytest.mark.parametrize("Q, T", test_data)
def test_p_norm_distance_profile(Q, T):
Q = Q.copy()
T = T.copy()
m = Q.shape[0]
for p in [1.0, 1.5, 2.0]:
ref = cdist(
core.rolling_window(Q, m),
core.rolling_window(T, m),
metric="minkowski",
p=p,
).flatten()
ref = np.power(ref, p)
cmp = core._p_norm_distance_profile(Q, T, p)
npt.assert_allclose(cmp, ref, atol=1.5e-07)
@pytest.mark.parametrize("Q, T", test_data)
def test_mass_absolute(Q, T):
Q = Q.copy()
T = T.copy()
m = Q.shape[0]
for p in [1.0, 2.0, 3.0]:
ref = np.linalg.norm(core.rolling_window(T, m) - Q, axis=1, ord=p)
cmp = core.mass_absolute(Q, T, p=p)
npt.assert_allclose(cmp, ref, atol=1.5e-07)
@pytest.mark.parametrize("Q, T", test_data)
def test_mass_absolute_Q_nan(Q, T):
Q = Q.copy()
Q[1] = np.nan
T = T.copy()
m = Q.shape[0]
ref = np.linalg.norm(core.rolling_window(T, m) - Q, axis=1)
ref[np.isnan(ref)] = np.inf
cmp = core.mass_absolute(Q, T)
npt.assert_allclose(cmp, ref, atol=1.5e-07)
@pytest.mark.parametrize("Q, T", test_data)
def test_mass_absolute_Q_inf(Q, T):
Q = Q.copy()
Q[1] = np.inf
T = T.copy()
m = Q.shape[0]
ref = np.linalg.norm(core.rolling_window(T, m) - Q, axis=1)
ref[np.isnan(ref)] = np.inf
cmp = core.mass_absolute(Q, T)
npt.assert_allclose(cmp, ref, atol=1.5e-07)
@pytest.mark.parametrize("Q, T", test_data)
def test_mass_absolute_T_nan(Q, T):
Q = Q.copy()
T = T.copy()
T[1] = np.nan
m = Q.shape[0]
ref = np.linalg.norm(core.rolling_window(T, m) - Q, axis=1)
ref[np.isnan(ref)] = np.inf
cmp = core.mass_absolute(Q, T)
npt.assert_allclose(cmp, ref, atol=1.5e-07)
@pytest.mark.parametrize("Q, T", test_data)
def test_mass_absolute_T_inf(Q, T):
Q = Q.copy()
T = T.copy()
T[1] = np.inf
m = Q.shape[0]
ref = np.linalg.norm(core.rolling_window(T, m) - Q, axis=1)
ref[np.isnan(ref)] = np.inf
cmp = core.mass_absolute(Q, T)
npt.assert_allclose(cmp, ref, atol=1.5e-07)
def test_mass_absolute_sqrt_input_negative():
Q = np.array(
[
-13.09,
-14.1,
-15.08,
-16.31,
-17.13,
-17.5,
-18.07,
-18.07,
-17.48,
-16.24,
-14.88,
-13.56,
-12.65,
-11.93,
-11.48,
-11.06,
-10.83,
-10.67,
-10.59,
-10.81,
-10.92,
-11.15,
-11.37,
-11.53,
-11.19,
-11.08,
-10.48,
-10.14,
-9.92,
-9.99,
-10.11,
-9.92,
-9.7,
-9.47,
-9.06,
-9.01,
-8.79,
-8.67,
-8.33,
-8.0,
-8.26,
-8.0,
-7.54,
-7.32,
-7.13,
-7.24,
-7.43,
-7.93,
-8.8,
-9.71,
]
)
ref = 0.0
cmp = core.mass_absolute(Q, Q)
npt.assert_allclose(cmp, ref, atol=1.5e-07)
@pytest.mark.parametrize("T_A, T_B", test_data)
def test_mass_distance_matrix(T_A, T_B):
m = 3
ref_distance_matrix = naive.distance_matrix(T_A, T_B, m)
k = T_A.shape[0] - m + 1
l = T_B.shape[0] - m + 1
cmp_distance_matrix = np.full((k, l), np.inf)
core.mass_distance_matrix(T_A, T_B, m, cmp_distance_matrix)
npt.assert_allclose(cmp_distance_matrix, ref_distance_matrix, atol=1.5e-07)
@pytest.mark.parametrize("T_A, T_B", test_data)
def test_mass_absolute_distance_matrix(T_A, T_B):
m = 3
ref_distance_matrix = cdist(
core.rolling_window(T_A, m), core.rolling_window(T_B, m)
)
k = T_A.shape[0] - m + 1
l = T_B.shape[0] - m + 1
cmp_distance_matrix = np.full((k, l), np.inf)
core._mass_absolute_distance_matrix(T_A, T_B, m, cmp_distance_matrix)
npt.assert_allclose(cmp_distance_matrix, ref_distance_matrix, atol=1.5e-07)
def test_apply_exclusion_zone():
T = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=np.float64)
ref = np.empty(T.shape, dtype=np.float64)
cmp = np.empty(T.shape, dtype=np.float64)
exclusion_zone = 2
for i in range(T.shape[0]):
ref[:] = T[:]
naive.apply_exclusion_zone(ref, i, exclusion_zone, np.inf)
cmp[:] = T[:]
core.apply_exclusion_zone(cmp, i, exclusion_zone, np.inf)
naive.replace_inf(ref)
naive.replace_inf(cmp)
npt.assert_array_equal(cmp, ref)
def test_apply_exclusion_zone_int():
T = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=np.int64)
ref = np.empty(T.shape, dtype=np.int64)
cmp = np.empty(T.shape, dtype=np.int64)
exclusion_zone = 2
for i in range(T.shape[0]):
ref[:] = T[:]
naive.apply_exclusion_zone(ref, i, exclusion_zone, -1)
cmp[:] = T[:]
core.apply_exclusion_zone(cmp, i, exclusion_zone, -1)
naive.replace_inf(ref)
naive.replace_inf(cmp)
npt.assert_array_equal(cmp, ref)
def test_apply_exclusion_zone_bool():
T = np.ones(10, dtype=bool)
ref = np.empty(T.shape, dtype=bool)
cmp = np.empty(T.shape, dtype=bool)
exclusion_zone = 2
for i in range(T.shape[0]):
ref[:] = T[:]
naive.apply_exclusion_zone(ref, i, exclusion_zone, False)
cmp[:] = T[:]
core.apply_exclusion_zone(cmp, i, exclusion_zone, False)
naive.replace_inf(ref)
naive.replace_inf(cmp)
npt.assert_array_equal(cmp, ref)
def test_apply_exclusion_zone_multidimensional():
T = np.array(
[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]],
dtype=np.float64,
)
ref = np.empty(T.shape, dtype=np.float64)
cmp = np.empty(T.shape, dtype=np.float64)
exclusion_zone = 2
for i in range(T.shape[1]):
ref[:, :] = T[:, :]
naive.apply_exclusion_zone(ref, i, exclusion_zone, np.inf)
cmp[:, :] = T[:, :]
core.apply_exclusion_zone(cmp, i, exclusion_zone, np.inf)
naive.replace_inf(ref)
naive.replace_inf(cmp)
npt.assert_array_equal(cmp, ref)
def test_preprocess():
T = np.array([0, np.nan, 2, 3, 4, 5, 6, 7, np.inf, 9])
m = 3
ref_T = np.array([0, 0, 2, 3, 4, 5, 6, 7, 0, 9], dtype=float)
ref_subseq_isconstant = naive.rolling_isconstant(T, m)
ref_M, ref_Σ = naive.compute_mean_std(T, m)
cmp_T, cmp_M, cmp_Σ, cmp_subseq_isconstant = core.preprocess(T, m)
npt.assert_allclose(cmp_T, ref_T, atol=1.5e-07)
npt.assert_allclose(cmp_M, ref_M, atol=1.5e-07)
npt.assert_allclose(cmp_Σ, ref_Σ, atol=1.5e-07)
npt.assert_allclose(cmp_subseq_isconstant, ref_subseq_isconstant, atol=1.5e-07)
T = pd.Series(T)
cmp_T, cmp_M, cmp_Σ, cmp_subseq_isconstant = core.preprocess(T, m)
npt.assert_allclose(cmp_T, ref_T, atol=1.5e-07)
npt.assert_allclose(cmp_M, ref_M, atol=1.5e-07)
npt.assert_allclose(cmp_Σ, ref_Σ, atol=1.5e-07)
npt.assert_allclose(cmp_subseq_isconstant, ref_subseq_isconstant, atol=1.5e-07)
def test_preprocess_non_normalized():
T = np.array([0, np.nan, 2, 3, 4, 5, 6, 7, np.inf, 9])
m = 3
ref_T_subseq_isfinite = np.full(T.shape[0] - m + 1, False, dtype=bool)
for i in range(T.shape[0] - m + 1):
if np.all(np.isfinite(T[i : i + m])):
ref_T_subseq_isfinite[i] = True
ref_T = np.array([0, 0, 2, 3, 4, 5, 6, 7, 0, 9], dtype=float)
cmp_T, cmp_T_subseq_isfinite = core.preprocess_non_normalized(T, m)
npt.assert_allclose(cmp_T, ref_T, atol=1.5e-07)
npt.assert_allclose(cmp_T_subseq_isfinite, ref_T_subseq_isfinite, atol=1.5e-07)
T = pd.Series(T)
cmp_T, cmp_T_subseq_isfinite = core.preprocess_non_normalized(T, m)
npt.assert_allclose(cmp_T, ref_T, atol=1.5e-07)
npt.assert_allclose(cmp_T_subseq_isfinite, ref_T_subseq_isfinite, atol=1.5e-07)
def test_preprocess_diagonal():
T = np.array([0, np.nan, 2, 3, 4, 5, 6, 7, np.inf, 9])
m = 3
ref_T = np.array([0, 0, 2, 3, 4, 5, 6, 7, 0, 9], dtype=float)
ref_M, ref_Σ = naive.compute_mean_std(ref_T, m)
ref_Σ_inverse = 1.0 / ref_Σ
ref_M_m_1, _ = naive.compute_mean_std(ref_T, m - 1)
(
cmp_T,
cmp_M,
cmp_Σ_inverse,
cmp_M_m_1,
cmp_T_subseq_isfinite,
cmp_T_subseq_isconstant,
) = core.preprocess_diagonal(T, m)
npt.assert_allclose(cmp_T, ref_T, atol=1.5e-07)
npt.assert_allclose(cmp_M, ref_M, atol=1.5e-07)
npt.assert_allclose(cmp_Σ_inverse, ref_Σ_inverse, atol=1.5e-07)
npt.assert_allclose(cmp_M_m_1, ref_M_m_1, atol=1.5e-07)
T = pd.Series(T)
(
cmp_T,
cmp_M,
cmp_Σ_inverse,
cmp_M_m_1,
cmp_T_subseq_isfinite,
cmp_T_subseq_isconstant,
) = core.preprocess_diagonal(T, m)
npt.assert_allclose(cmp_T, ref_T, atol=1.5e-07)
npt.assert_allclose(cmp_M, ref_M, atol=1.5e-07)
npt.assert_allclose(cmp_Σ_inverse, ref_Σ_inverse, atol=1.5e-07)
npt.assert_allclose(cmp_M_m_1, ref_M_m_1, atol=1.5e-07)
def test_replace_distance():
right = rng.RNG.rand(30).reshape(5, 6)
left = right.copy()
np.fill_diagonal(right, config.STUMPY_MAX_DISTANCE - 1e-9)
np.fill_diagonal(left, np.inf)
core.replace_distance(right, config.STUMPY_MAX_DISTANCE, np.inf, 1e-6)
def test_array_to_temp_file():
ref_val = rng.RNG.rand()
fname = core.array_to_temp_file(ref_val)
cmp_val = np.load(fname, allow_pickle=False)
os.remove(fname)
npt.assert_allclose(cmp_val, ref_val, atol=1.5e-07)
def test_count_diagonal_ndist():
for n_A in range(10, 15):
for n_B in range(10, 15):
for m in range(3, 6):
diags = rng.RNG.permutation(
range(-(n_A - m + 1) + 1, n_B - m + 1)
).astype(np.int64)
ones_matrix = np.ones((n_A - m + 1, n_B - m + 1), dtype=np.int64)
ref_ndist_counts = np.empty(len(diags))
for i, diag in enumerate(diags):
ref_ndist_counts[i] = ones_matrix.diagonal(offset=diag).sum()
cmp_ndist_counts = core._count_diagonal_ndist(diags, m, n_A, n_B)
npt.assert_allclose(cmp_ndist_counts, ref_ndist_counts, atol=1.5e-07)
def test_get_array_ranges():
x = np.array([3, 9, 2, 1, 5, 4, 7, 7, 8, 6], dtype=np.int64)
for n_chunks in range(2, 5):
ref = naive.get_array_ranges(x, n_chunks, False)
cmp = core._get_array_ranges(x, n_chunks, False)
npt.assert_allclose(cmp, ref, atol=1.5e-07)
def test_get_array_ranges_exhausted():
x = np.array([3, 3, 3, 11, 11, 11], dtype=np.int64)
n_chunks = 6
ref = naive.get_array_ranges(x, n_chunks, False)
cmp = core._get_array_ranges(x, n_chunks, False)
npt.assert_allclose(cmp, ref, atol=1.5e-07)
def test_get_array_ranges_exhausted_truncated():
x = np.array([3, 3, 3, 11, 11, 11], dtype=np.int64)
n_chunks = 6
ref = naive.get_array_ranges(x, n_chunks, True)
cmp = core._get_array_ranges(x, n_chunks, True)
npt.assert_allclose(cmp, ref, atol=1.5e-07)
def test_get_array_ranges_empty_array():
x = np.array([], dtype=np.int64)
n_chunks = 6
ref = naive.get_array_ranges(x, n_chunks, False)
cmp = core._get_array_ranges(x, n_chunks, False)
npt.assert_allclose(cmp, ref, atol=1.5e-07)
def test_get_ranges():
ref = np.array([[0, 3], [3, 6]])
size = 6
n_chunks = 2
cmp = core._get_ranges(size, n_chunks, False)
npt.assert_allclose(cmp, ref, atol=1.5e-07)
def test_get_ranges_exhausted():
ref = np.array([[0, 1], [1, 2], [2, 3], [3, 3], [3, 4], [4, 5], [5, 6], [6, 6]])
size = 6
n_chunks = 8
cmp = core._get_ranges(size, n_chunks, False)
npt.assert_allclose(cmp, ref, atol=1.5e-07)
def test_get_ranges_exhausted_truncated():
ref = np.array([[0, 1], [1, 2], [2, 3], [3, 4], [4, 5], [5, 6]])
size = 6
n_chunks = 8
cmp = core._get_ranges(size, n_chunks, True)
npt.assert_allclose(cmp, ref, atol=1.5e-07)
def test_get_ranges_zero_size():
ref = np.empty((0, 2))
size = 0
n_chunks = 8
cmp = core._get_ranges(size, n_chunks, True)
npt.assert_allclose(cmp, ref, atol=1.5e-07)
def test_rolling_isfinite():
a = np.arange(12).astype(np.float64)
w = 3
a[1] = np.nan
a[5] = np.nan
a[9] = np.nan
ref = np.all(core.rolling_window(np.isfinite(a), w), axis=1)
cmp = core.rolling_isfinite(a, w)
npt.assert_allclose(cmp, ref, atol=1.5e-07)
cmp = core.rolling_isfinite(np.isfinite(a), w)
npt.assert_allclose(cmp, ref, atol=1.5e-07)
def test_rolling_isconstant():
a = np.arange(12).astype(np.float64)
w = 3
a[:3] = 77.0
a[1] = np.inf
a[4:7] = 77.0
a[9:12] = [77.0, np.nan, 77.0]
ref = naive.rolling_isconstant(a, w)
cmp = core.rolling_isconstant(a, w)
npt.assert_allclose(cmp, ref, atol=1.5e-07)
def test_compare_parameters():
assert (
core._compare_parameters(core.rolling_window, core.z_norm, exclude=[]) is False
)
def test_jagged_list_to_array():
arr = [np.array([0, 1]), np.array([0]), np.array([0, 1, 2, 3])]
ref = np.array([[0, 1, -1, -1], [0, -1, -1, -1], [0, 1, 2, 3]], dtype="int64")
cmp = core._jagged_list_to_array(arr, fill_value=-1, dtype="int64")
npt.assert_array_equal(cmp, ref)
ref = np.array(
[[0, 1, np.nan, np.nan], [0, np.nan, np.nan, np.nan], [0, 1, 2, 3]],
dtype="float64",
)
cmp = core._jagged_list_to_array(arr, fill_value=np.nan, dtype="float64")
npt.assert_array_equal(cmp, ref)
def test_jagged_list_to_array_empty():
arr = []
ref = np.array([[]], dtype="int64")
cmp = core._jagged_list_to_array(arr, fill_value=-1, dtype="int64")
npt.assert_array_equal(cmp, ref)
ref = np.array([[]], dtype="float64")
cmp = core._jagged_list_to_array(arr, fill_value=np.nan, dtype="float64")
npt.assert_array_equal(cmp, ref)
def test_get_mask_slices():
bool_list = [False, True]
mask_cases = [
[x, y, z, w]
for x in bool_list
for y in bool_list
for z in bool_list
for w in bool_list
]
for mask in mask_cases:
ref_slices = naive._get_mask_slices(mask)
cmp_slices = core._get_mask_slices(mask)
npt.assert_array_equal(cmp_slices, ref_slices)
def test_idx_to_mp():
n = 64
m = 5
T = rng.RNG.rand(n)
l = n - m + 1
I = rng.RNG.randint(0, l, l)
ref_mp = naive_idx_to_mp(I, T, m)
cmp_mp = core._idx_to_mp(I, T, m)
npt.assert_allclose(cmp_mp, ref_mp, atol=1.5e-07)
T_subseq_isconstant = rng.RNG.choice([True, False], l, replace=True)
ref_mp = naive_idx_to_mp(I, T, m, T_subseq_isconstant=T_subseq_isconstant)
cmp_mp = core._idx_to_mp(I, T, m, T_subseq_isconstant=T_subseq_isconstant)
npt.assert_allclose(cmp_mp, ref_mp, atol=1.5e-07)
for p in range(1, 4):
ref_mp = naive_idx_to_mp(I, T, m, normalize=False, p=p)
cmp_mp = core._idx_to_mp(I, T, m, normalize=False, p=p)
npt.assert_allclose(cmp_mp, ref_mp, atol=1.5e-07)
def test_total_diagonal_ndists():
tile_height = 9
tile_width = 11
for tile_lower_diag in range(-tile_height - 2, tile_width + 2):
for tile_upper_diag in range(tile_lower_diag, tile_width + 2):
assert naive._total_diagonal_ndists(
tile_lower_diag, tile_upper_diag, tile_height, tile_width
) == core._total_diagonal_ndists(
tile_lower_diag, tile_upper_diag, tile_height, tile_width
)
tile_height = 11
tile_width = 9
for tile_lower_diag in range(-tile_height - 2, tile_width + 2):
for tile_upper_diag in range(tile_lower_diag, tile_width + 2):
assert naive._total_diagonal_ndists(
tile_lower_diag, tile_upper_diag, tile_height, tile_width
) == core._total_diagonal_ndists(
tile_lower_diag, tile_upper_diag, tile_height, tile_width
)
@pytest.mark.parametrize("n", n)
def test_bfs_indices(n):
ref_bfs_indices = naive_bfs_indices(n)
cmp_bfs_indices = np.array(list(core._bfs_indices(n)))
npt.assert_allclose(cmp_bfs_indices, ref_bfs_indices, atol=1.5e-07)
@pytest.mark.parametrize("n", n)
def test_bfs_indices_fill_value(n):
ref_bfs_indices = naive_bfs_indices(n, -1)
cmp_bfs_indices = np.array(list(core._bfs_indices(n, -1)))
npt.assert_allclose(cmp_bfs_indices, ref_bfs_indices, atol=1.5e-07)
def test_select_P_ABBA_val_inf():
P_ABBA = rng.RNG.rand(10)
k = 2
P_ABBA[k:] = np.inf
p_abba = P_ABBA.copy()
cmp = core._select_P_ABBA_value(P_ABBA, k=k)
p_abba.sort()
ref = p_abba[k - 1]
npt.assert_allclose(cmp, ref, atol=1.5e-07)
def test_merge_topk_PI_without_overlap():
n = 50
for k in range(1, 6):
PA = rng.RNG.rand(n * k).reshape(n, k)
PA[:, :] = np.sort(PA, axis=1)
PB = rng.RNG.rand(n * k).reshape(n, k)
col_idx = rng.RNG.randint(0, k, size=n)
for i in range(n):
val = rng.RNG.choice(PA[i], size=1, replace=False)
PB[i, col_idx[i]] = val.item()
PB[:, :] = np.sort(PB, axis=1)
IA = np.arange(n * k).reshape(n, k)
IB = IA + n * k
ref_P = PA.copy()
ref_I = IA.copy()
cmp_P = PA.copy()
cmp_I = IA.copy()
naive.merge_topk_PI(ref_P, PB.copy(), ref_I, IB.copy())
core._merge_topk_PI(cmp_P, PB.copy(), cmp_I, IB.copy())
npt.assert_allclose(cmp_P, ref_P, atol=1.5e-07)
npt.assert_allclose(cmp_I, ref_I, atol=1.5e-07)
def test_merge_topk_PI_with_overlap():
n = 50
for k in range(1, 6):
PA = rng.RNG.rand(n * k).reshape(n, k)
PB = rng.RNG.rand(n * k).reshape(n, k)
IA = np.arange(n * k).reshape(n, k)
IB = IA + n * k
num_overlaps = rng.RNG.randint(1, k + 1, size=n)
for i in range(n):
col_IDX = rng.RNG.choice(np.arange(k), num_overlaps[i], replace=False)
imprecision = rng.RNG.uniform(low=-1e-06, high=1e-06, size=len(col_IDX))
PB[i, col_IDX] = PA[i, col_IDX] + imprecision
IB[i, col_IDX] = IA[i, col_IDX]
IDX = np.argsort(PA, axis=1)
PA[:, :] = np.take_along_axis(PA, IDX, axis=1)
IA[:, :] = np.take_along_axis(IA, IDX, axis=1)
IDX = np.argsort(PB, axis=1)
PB[:, :] = np.take_along_axis(PB, IDX, axis=1)
IB[:, :] = np.take_along_axis(IB, IDX, axis=1)
ref_P = PA.copy()
ref_I = IA.copy()
cmp_P = PA.copy()
cmp_I = IA.copy()
naive.merge_topk_PI(ref_P, PB.copy(), ref_I, IB.copy())
core._merge_topk_PI(cmp_P, PB.copy(), cmp_I, IB.copy())
npt.assert_allclose(cmp_P, ref_P, atol=1.5e-07)
npt.assert_allclose(cmp_I, ref_I, atol=1.5e-07)
def test_merge_topk_PI_with_1D_input():
n = 50
PA = rng.RNG.rand(n)
PB = rng.RNG.rand(n)
IA = np.arange(n)
IB = IA + n
n_overlaps = rng.RNG.randint(1, n + 1)
IDX_rows_with_overlaps = rng.RNG.choice(np.arange(n), n_overlaps, replace=False)
imprecision = rng.RNG.uniform(low=-1e-06, high=1e-06, size=n_overlaps)
PB[IDX_rows_with_overlaps] = PA[IDX_rows_with_overlaps] + imprecision
IB[IDX_rows_with_overlaps] = IA[IDX_rows_with_overlaps]
ref_P = PA.copy()
ref_I = IA.copy()
cmp_P = PA.copy()
cmp_I = IA.copy()
naive.merge_topk_PI(ref_P, PB.copy(), ref_I, IB.copy())
core._merge_topk_PI(cmp_P, PB.copy(), cmp_I, IB.copy())
npt.assert_allclose(cmp_P, ref_P, atol=1.5e-07)
npt.assert_allclose(cmp_I, ref_I, atol=1.5e-07)
def test_merge_topk_PI_with_1D_input_hardcoded():
PA = np.array([0.1, 0.3, 0.5, 0.7, 0.9])
PB = np.array([0.2, 0.3, 0.6, 0.8, 1.0])
IA = np.array([0, 1, 2, 3, 4])
IB = np.array([10, 1, 12, 13, 14])
ref_P = PA.copy()
ref_I = IA.copy()
cmp_P = PA.copy()
cmp_I = IA.copy()
naive.merge_topk_PI(ref_P, PB.copy(), ref_I, IB.copy())
core._merge_topk_PI(cmp_P, PB.copy(), cmp_I, IB.copy())
npt.assert_allclose(cmp_P, ref_P, atol=1.5e-07)
npt.assert_allclose(cmp_I, ref_I, atol=1.5e-07)
def test_merge_topk_ρI_without_overlap():
n = 50
for k in range(1, 6):
ρA = rng.RNG.rand(n * k).reshape(n, k)
ρA[:, :] = np.sort(ρA, axis=1)
ρB = rng.RNG.rand(n * k).reshape(n, k)
col_idx = rng.RNG.randint(0, k, size=n)
for i in range(n):
val = rng.RNG.choice(ρA[i], size=1, replace=False)
ρB[i, col_idx[i]] = val.item()
ρB[:, :] = np.sort(ρB, axis=1)
IA = np.arange(n * k).reshape(n, k)
IB = IA + n * k
ref_ρ = ρA.copy()
ref_I = IA.copy()
cmp_ρ = ρA.copy()
cmp_I = IA.copy()
naive.merge_topk_ρI(ref_ρ, ρB.copy(), ref_I, IB.copy())
core._merge_topk_ρI(cmp_ρ, ρB.copy(), cmp_I, IB.copy())
npt.assert_allclose(cmp_ρ, ref_ρ, atol=1.5e-07)
npt.assert_allclose(cmp_I, ref_I, atol=1.5e-07)
def test_merge_topk_ρI_with_overlap():
n = 50
for k in range(1, 6):
ρA = rng.RNG.rand(n * k).reshape(n, k)
ρB = rng.RNG.rand(n * k).reshape(n, k)
IA = np.arange(n * k).reshape(n, k)
IB = IA + n * k
num_overlaps = rng.RNG.randint(1, k + 1, size=n)
for i in range(n):
col_IDX = rng.RNG.choice(np.arange(k), num_overlaps[i], replace=False)
imprecision = rng.RNG.uniform(low=-1e-06, high=1e-06, size=len(col_IDX))
ρB[i, col_IDX] = ρA[i, col_IDX] + imprecision
IB[i, col_IDX] = IA[i, col_IDX]
IDX = np.argsort(ρA, axis=1)
ρA[:, :] = np.take_along_axis(ρA, IDX, axis=1)
IA[:, :] = np.take_along_axis(IA, IDX, axis=1)
IDX = np.argsort(ρB, axis=1)
ρB[:, :] = np.take_along_axis(ρB, IDX, axis=1)
IB[:, :] = np.take_along_axis(IB, IDX, axis=1)
ref_ρ = ρA.copy()
ref_I = IA.copy()
cmp_ρ = ρA.copy()
cmp_I = IA.copy()
naive.merge_topk_ρI(ref_ρ, ρB.copy(), ref_I, IB.copy())
core._merge_topk_ρI(cmp_ρ, ρB.copy(), cmp_I, IB.copy())
npt.assert_allclose(cmp_ρ, ref_ρ, atol=1.5e-07)
npt.assert_allclose(cmp_I, ref_I, atol=1.5e-07)
def test_merge_topk_ρI_with_1D_input():
n = 50
ρA = rng.RNG.rand(n)
ρB = rng.RNG.rand(n)
IA = np.arange(n)
IB = IA + n
n_overlaps = rng.RNG.randint(1, n + 1)
IDX_rows_with_overlaps = rng.RNG.choice(np.arange(n), n_overlaps, replace=False)
imprecision = rng.RNG.uniform(low=-1e-06, high=1e-06, size=n_overlaps)
ρB[IDX_rows_with_overlaps] = ρA[IDX_rows_with_overlaps] + imprecision
IB[IDX_rows_with_overlaps] = IA[IDX_rows_with_overlaps]
ref_ρ = ρA.copy()
ref_I = IA.copy()
cmp_ρ = ρA.copy()
cmp_I = IA.copy()
naive.merge_topk_ρI(ref_ρ, ρB.copy(), ref_I, IB.copy())
core._merge_topk_ρI(cmp_ρ, ρB.copy(), cmp_I, IB.copy())
npt.assert_allclose(cmp_ρ, ref_ρ, atol=1.5e-07)
npt.assert_allclose(cmp_I, ref_I, atol=1.5e-07)
def test_merge_topk_ρI_with_1D_input_hardcoded():
ρA = np.array([0.1, 0.3, 0.5, 0.7, 0.9])
ρB = np.array([0.2, 0.3, 0.6, 0.8, 1.0])
IA = np.array([0, 1, 2, 3, 4])
IB = np.array([10, 1, 12, 13, 14])
ref_ρ = ρA.copy()
ref_I = IA.copy()
cmp_ρ = ρA.copy()
cmp_I = IA.copy()
naive.merge_topk_ρI(ref_ρ, ρB.copy(), ref_I, IB.copy())
core._merge_topk_ρI(cmp_ρ, ρB.copy(), cmp_I, IB.copy())
npt.assert_allclose(cmp_ρ, ref_ρ, atol=1.5e-07)
npt.assert_allclose(cmp_I, ref_I, atol=1.5e-07)
def test_shift_insert_at_index():
for k in range(1, 6):
a = rng.RNG.rand(k)
ref = np.empty(k, dtype=np.float64)
cmp = np.empty(k, dtype=np.float64)
indices = np.arange(k + 1)
values = rng.RNG.rand(k + 1)
for idx, v in zip(indices, values):
ref[:] = a
cmp[:] = a
ref = np.insert(ref, idx, v)[:-1]
core._shift_insert_at_index(
cmp, idx, v, shift="right"
)
npt.assert_allclose(cmp, ref, atol=1.5e-07)
for idx, v in zip(indices, values):
ref[:] = a
cmp[:] = a
ref = np.insert(ref, idx, v)[1:]
core._shift_insert_at_index(
cmp, idx, v, shift="left"
)
npt.assert_allclose(cmp, ref, atol=1.5e-07)
def test_check_P():
with pytest.raises(ValueError):
core._check_P(rng.RNG.rand(10).reshape(2, 5))
def test_find_matches_all():
max_distance = np.inf
D = rng.RNG.rand(64)
for excl_zone in range(3):
ref = naive.find_matches(D, excl_zone, max_distance, max_matches=None)
cmp = core._find_matches(D, excl_zone, max_distance, max_matches=None)
npt.assert_allclose(
cmp.astype(np.float64), ref.astype(np.float64), atol=1.5e-07
)
def test_find_matches_maxmatch():
max_distance = np.inf
D = rng.RNG.rand(64)
for excl_zone in range(3):
max_matches = rng.RNG.randint(0, 100)
ref = naive.find_matches(D, excl_zone, max_distance, max_matches)
cmp = core._find_matches(D, excl_zone, max_distance, max_matches)
npt.assert_allclose(
cmp.astype(np.float64), ref.astype(np.float64), atol=1.5e-07
)
@pytest.mark.filterwarnings("ignore", category=NumbaPerformanceWarning)
@patch("stumpy.config.STUMPY_THREADS_PER_BLOCK", TEST_THREADS_PER_BLOCK)
def test_gpu_searchsorted():
if not cuda.is_available():
pytest.skip("Skipping Tests No GPUs Available")
n = 3 * config.STUMPY_THREADS_PER_BLOCK + 1
V = np.empty(n, dtype=np.float64)
threads_per_block = config.STUMPY_THREADS_PER_BLOCK
blocks_per_grid = math.ceil(n / threads_per_block)
for k in range(1, 32):
device_bfs = cuda.to_device(core._bfs_indices(k, fill_value=-1))
nlevel = np.floor(np.log2(k) + 1).astype(np.int64)
A = np.sort(rng.RNG.rand(n, k), axis=1)
device_A = cuda.to_device(A)
V[:] = rng.RNG.rand(n)
for i, idx in enumerate(rng.RNG.choice(np.arange(n), size=k, replace=False)):
V[idx] = A[idx, i]
device_V = cuda.to_device(V)
is_left = True
ref_IDX = [np.searchsorted(A[i], V[i], side="left") for i in range(n)]
ref_IDX = np.asarray(ref_IDX, dtype=np.int64)
cmp_IDX = np.full(n, -1, dtype=np.int64)
device_cmp_IDX = cuda.to_device(cmp_IDX)
_gpu_searchsorted_kernel[blocks_per_grid, threads_per_block](
device_A, device_V, device_bfs, nlevel, is_left, device_cmp_IDX
)
cmp_IDX = device_cmp_IDX.copy_to_host()
npt.assert_array_equal(cmp_IDX, ref_IDX)
is_left = False
ref_IDX = [np.searchsorted(A[i], V[i], side="right") for i in range(n)]
ref_IDX = np.asarray(ref_IDX, dtype=np.int64)
cmp_IDX = np.full(n, -1, dtype=np.int64)
device_cmp_IDX = cuda.to_device(cmp_IDX)
_gpu_searchsorted_kernel[blocks_per_grid, threads_per_block](
device_A, device_V, device_bfs, nlevel, is_left, device_cmp_IDX
)
cmp_IDX = device_cmp_IDX.copy_to_host()
npt.assert_array_equal(cmp_IDX, ref_IDX)
def test_client_to_func():
with pytest.raises(NotImplementedError):
core._client_to_func(core)
def test_apply_include():
D = rng.RNG.uniform(-1000, 1000, [10, 20]).astype(np.float64)
ref_D = np.empty(D.shape)
cmp_D = np.empty(D.shape)
for width in range(D.shape[0]):
for i in range(D.shape[0] - width):
ref_D[:, :] = D[:, :]
cmp_D[:, :] = D[:, :]
include = np.asarray(range(i, i + width + 1))
naive.apply_include(D, include)
core._apply_include(D, include)
npt.assert_allclose(cmp_D, ref_D, atol=1.5e-07)
@pytest.mark.parametrize("T_A, T_B", test_data)
def test_mpdist_custom_func(T_A, T_B):
m = 3
percentage = 0.05
n_A = T_A.shape[0]
n_B = T_B.shape[0]
ref_mpdist = naive.mpdist(T_A, T_B, m)
partial_stump = functools.partial(stump)
partial_k_func = functools.partial(
naive.mpdist_custom_func, m=m, percentage=percentage, n_A=n_A, n_B=n_B
)
cmp_mpdist = core._mpdist(T_A, T_B, m, partial_stump, custom_func=partial_k_func)
npt.assert_allclose(cmp_mpdist, ref_mpdist, atol=1.5e-07)
@pytest.mark.parametrize("T_A, T_B", test_data)
def test_mpdist_with_isconstant(T_A, T_B):
isconstant_custom_func = functools.partial(
naive.isconstant_func_stddev_threshold, quantile_threshold=0.05
)
m = 3
T_A_subseq_isconstant = isconstant_custom_func
T_B_subseq_isconstant = isconstant_custom_func
ref_mpdist = naive.mpdist(
T_A,
T_B,
m,
T_A_subseq_isconstant=T_A_subseq_isconstant,
T_B_subseq_isconstant=T_B_subseq_isconstant,
)
partial_stump = functools.partial(
stump,
T_A_subseq_isconstant=T_A_subseq_isconstant,
T_B_subseq_isconstant=T_B_subseq_isconstant,
)
cmp_mpdist = core._mpdist(T_A, T_B, m, partial_stump)
npt.assert_allclose(cmp_mpdist, ref_mpdist, atol=1.5e-07)
@pytest.mark.parametrize("T_A, T_B", test_data)
def test_compute_P_ABBA(T_A, T_B):
m = 3
n_A = T_A.shape[0]
n_B = T_B.shape[0]
ref_P_ABBA = np.empty(n_A - m + 1 + n_B - m + 1, dtype=np.float64)
cmp_P_ABBA = np.empty(n_A - m + 1 + n_B - m + 1, dtype=np.float64)
ref_P_ABBA[: n_A - m + 1] = naive.stump(T_A, m, T_B)[:, 0]
ref_P_ABBA[n_A - m + 1 :] = naive.stump(T_B, m, T_A)[:, 0]
partial_stump = functools.partial(stump)
core._compute_P_ABBA(T_A, T_B, m, cmp_P_ABBA, partial_stump)
npt.assert_allclose(cmp_P_ABBA, ref_P_ABBA, atol=1.5e-07)
@pytest.mark.parametrize("T_A, T_B", test_data)
def test_compute_P_ABBA_with_isconstant(T_A, T_B):
isconstant_custom_func = functools.partial(
naive.isconstant_func_stddev_threshold, quantile_threshold=0.05
)
m = 3
n_A = T_A.shape[0]
n_B = T_B.shape[0]
T_A_subseq_isconstant = isconstant_custom_func
T_B_subseq_isconstant = isconstant_custom_func
ref_P_ABBA = np.empty(n_A - m + 1 + n_B - m + 1, dtype=np.float64)
cmp_P_ABBA = np.empty(n_A - m + 1 + n_B - m + 1, dtype=np.float64)
ref_P_ABBA[: n_A - m + 1] = naive.stump(
T_A,
m,
T_B,
T_A_subseq_isconstant=T_A_subseq_isconstant,
T_B_subseq_isconstant=T_B_subseq_isconstant,
)[:, 0]
ref_P_ABBA[n_A - m + 1 :] = naive.stump(
T_B,
m,
T_A,
T_A_subseq_isconstant=T_B_subseq_isconstant,
T_B_subseq_isconstant=T_A_subseq_isconstant,
)[:, 0]
mp_func = functools.partial(
stump,
T_A_subseq_isconstant=T_A_subseq_isconstant,
T_B_subseq_isconstant=T_B_subseq_isconstant,
)
core._compute_P_ABBA(
T_A,
T_B,
m,
cmp_P_ABBA,
mp_func,
)
npt.assert_allclose(cmp_P_ABBA, ref_P_ABBA, atol=1.5e-07)
def test_process_isconstant_1d():
isconstant_custom_func = functools.partial(
naive.isconstant_func_stddev_threshold, quantile_threshold=0.05
)
n = 64
m = 8
T = rng.RNG.rand(n)
ref_T_subseq_isconstant = naive.rolling_isconstant(T, m, isconstant_custom_func)
cmp_T_subseq_isconstant = core.process_isconstant(T, m, isconstant_custom_func)
npt.assert_allclose(cmp_T_subseq_isconstant, ref_T_subseq_isconstant, atol=1.5e-07)
T = rng.RNG.rand(n)
idx = rng.RNG.randint(n)
T[idx] = np.nan
T_subseq_isconstant = rng.RNG.choice([True, False], n - m + 1, replace=True)
T_subseq_isfinite = core.rolling_isfinite(T, m)
ref_T_subseq_isconstant = T_subseq_isconstant & T_subseq_isfinite
cmp_T_subseq_isconstant = core.process_isconstant(T, m, T_subseq_isconstant)
npt.assert_allclose(cmp_T_subseq_isconstant, ref_T_subseq_isconstant, atol=1.5e-07)
def test_process_isconstant_2d():
isconstant_custom_func = functools.partial(
naive.isconstant_func_stddev_threshold, quantile_threshold=0.05
)
n = 64
m = 8
d = 3
T = rng.RNG.rand(d, n)
T_subseq_isconstant = [
None,
isconstant_custom_func,
rng.RNG.choice([True, False], n - m + 1, replace=True),
]
ref_T_subseq_isconstant = np.array(
[naive.rolling_isconstant(T[i], m, T_subseq_isconstant[i]) for i in range(d)]
)
cmp_T_subseq_isconstant = core.process_isconstant(T, m, T_subseq_isconstant)
npt.assert_allclose(cmp_T_subseq_isconstant, ref_T_subseq_isconstant, atol=1.5e-07)
T = rng.RNG.rand(d, n)
i, j = rng.RNG.choice(np.arange(n - m + 1), size=2, replace=False)
T[-1, i : i + m] = 0.0
T[-1, j : j + m] = 0.0
T[-1, j] = np.nan
T_subseq_isconstant = [
None,
isconstant_custom_func,
np.full(n - m + 1, 0, dtype=bool),
]
T_subseq_isconstant[-1][i] = True
T_subseq_isconstant[-1][j] = True
ref_T_subseq_isconstant = np.array(
[naive.rolling_isconstant(T[i], m, T_subseq_isconstant[i]) for i in range(d)]
)
ref_T_subseq_isconstant = ref_T_subseq_isconstant & core.rolling_isfinite(T, m)
cmp_T_subseq_isconstant = core.process_isconstant(T, m, T_subseq_isconstant)
npt.assert_allclose(cmp_T_subseq_isconstant, ref_T_subseq_isconstant, atol=1.5e-07)
def test_process_isconstant_1d_default():
n = 64
m = 8
T = rng.RNG.rand(n)
T[:m] = 0.5
ref_T_subseq_isconstant = naive.rolling_isconstant(T, m, a_subseq_isconstant=None)
cmp_T_subseq_isconstant = core.process_isconstant(T, m, T_subseq_isconstant=None)
npt.assert_allclose(cmp_T_subseq_isconstant, ref_T_subseq_isconstant, atol=1.5e-07)
T = rng.RNG.rand(n)
T[:m] = 0.5
T[-m:] = np.nan
ref_T_subseq_isconstant = naive.rolling_isconstant(T, m, a_subseq_isconstant=None)
cmp_T_subseq_isconstant = core.process_isconstant(T, m, T_subseq_isconstant=None)
npt.assert_allclose(cmp_T_subseq_isconstant, ref_T_subseq_isconstant, atol=1.5e-07)
def test_update_incremental_PI_egressFalse():
T = rng.RNG.rand(64)
t = rng.RNG.rand()
T_new = np.append(T, t)
m = 3
excl_zone = int(np.ceil(m / config.STUMPY_EXCL_ZONE_DENOM))
for k in range(1, 4):
ref_mp = naive.stump(T_new, m, row_wise=True, k=k)
ref_P = ref_mp[:, :k].astype(np.float64)
ref_I = ref_mp[:, k : 2 * k].astype(np.int64)
mp = naive.stump(T, m, row_wise=True, k=k)
cmp_P = mp[:, :k].astype(np.float64)
cmp_I = mp[:, k : 2 * k].astype(np.int64)
cmp_P = np.pad(
cmp_P,
[(0, 1), (0, 0)],
mode="constant",
constant_values=np.inf,
)
cmp_I = np.pad(
cmp_I,
[(0, 1), (0, 0)],
mode="constant",
constant_values=-1,
)
D = core.mass(T_new[-m:], T_new)
core._update_incremental_PI(D, cmp_P, cmp_I, excl_zone, n_appended=0)
npt.assert_allclose(cmp_P, ref_P, atol=1.5e-07)
npt.assert_allclose(cmp_I, ref_I, atol=1.5e-07)
def test_update_incremental_PI_egressTrue():
T = rng.RNG.rand(64)
t = rng.RNG.rand()
m = 3
excl_zone = int(np.ceil(m / config.STUMPY_EXCL_ZONE_DENOM))
for k in range(1, 4):
T_with_t = np.append(T, t)
D = naive.distance_matrix(T_with_t, T_with_t, m)
D[-1, 0] = np.inf
D[0, -1] = np.inf
l = len(T_with_t) - m + 1
P = np.empty((l, k), dtype=np.float64)
I = np.empty((l, k), dtype=np.int64)
for i in range(l):
core.apply_exclusion_zone(D[i], i, excl_zone, np.inf)
IDX = np.argsort(D[i], kind="mergesort")[:k]
I[i] = IDX
P[i] = D[i, IDX]
ref_P = P[1:].copy()
ref_I = I[1:].copy()
mp = naive.stump(T, m, row_wise=True, k=k)
cmp_P = mp[:, :k].astype(np.float64)
cmp_I = mp[:, k : 2 * k].astype(np.int64)
cmp_P[:-1] = cmp_P[1:]
cmp_P[-1] = np.inf
cmp_I[:-1] = cmp_I[1:]
cmp_I[-1] = -1
T_new = np.append(T[1:], t)
D = core.mass(T_new[-m:], T_new)
core._update_incremental_PI(D, cmp_P, cmp_I, excl_zone, n_appended=1)
npt.assert_allclose(cmp_P, ref_P, atol=1.5e-07)
npt.assert_allclose(cmp_I, ref_I, atol=1.5e-07)
def test_update_incremental_PI_egressTrue_MemoryCheck():
state = mersenne.seed_to_state(0)
with rng.fix_state(state):
T = rng.RNG.rand(64)
m = 3
excl_zone = int(np.ceil(m / config.STUMPY_EXCL_ZONE_DENOM))
s = rng.RNG.rand(m)
T[:m] = s
T[30 : 30 + m] = s
T[-m:] = s
t = rng.RNG.rand()
T_with_t = np.append(T, t)
D = naive.distance_matrix(T_with_t, T_with_t, m)
D[-1, 0] = np.inf
D[0, -1] = np.inf
l = len(T_with_t) - m + 1
for i in range(l):
core.apply_exclusion_zone(D[i], i, excl_zone, np.inf)
T_new = np.append(T[1:], t)
dist_profile = naive.distance_profile(T_new[-m:], T_new, m)
core.apply_exclusion_zone(
dist_profile, len(dist_profile) - 1, excl_zone, np.inf
)
for k in range(1, 4):
P = np.empty((l, k), dtype=np.float64)
I = np.empty((l, k), dtype=np.int64)
for i in range(l):
IDX = np.argsort(D[i], kind="mergesort")[:k]
I[i] = IDX
P[i] = D[i, IDX]
ref_P = P[1:].copy()
ref_I = I[1:].copy()
mp = naive.stump(T, m, row_wise=True, k=k)
cmp_P = mp[:, :k].astype(np.float64)
cmp_I = mp[:, k : 2 * k].astype(np.int64)
cmp_P[:-1] = cmp_P[1:]
cmp_P[-1] = np.inf
cmp_I[:-1] = cmp_I[1:]
cmp_I[-1] = -1
core._update_incremental_PI(
dist_profile, cmp_P, cmp_I, excl_zone, n_appended=1
)
npt.assert_allclose(cmp_P, ref_P, atol=1.5e-07)
npt.assert_allclose(cmp_I, ref_I, atol=1.5e-07)
def test_check_self_join():
with pytest.warns(UserWarning):
ignore_trivial = False
core.check_self_join(ignore_trivial)