Skip to content

Commit 3e51c96

Browse files
authored
Fixes #1153, Fixes 707 Track Random Seed (#1154)
* Initial commit * Changed dtype * Added scraamp-related files * Completed up to test_aampi.py * Completed seed tracking for all modules * Force histogram distribution to use custom RNG * Added no cover * Addressed comments * Added STUMPY_SEED support
1 parent bccb485 commit 3e51c96

49 files changed

Lines changed: 3385 additions & 3354 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

conftest.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,13 @@
44
# to fix eventual module import errors that can arise, for example when
55
# running tests from inside VS code.
66
# See https://stackoverflow.com/a/34520971
7+
8+
from stumpy import rng
9+
10+
11+
def pytest_configure(config):
12+
"""
13+
Called after command line options have been parsed
14+
and all plugins and initial conftest files been loaded.
15+
"""
16+
print(f"STUMPY_SEED={rng.SEED} pixi run tests custom {config.args[0]}")

stumpy/floss.py

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import numpy as np
88
import scipy.stats
99

10-
from . import config, core
10+
from . import config, core, rng
1111

1212

1313
def _nnmark(I):
@@ -84,34 +84,36 @@ def _iac(
8484
IAC : numpy.ndarray
8585
Idealized arc curve (IAC)
8686
"""
87-
np.random.seed(seed)
87+
with rng.fix_seed(seed):
88+
I = rng.RNG.randint(0, width, size=width, dtype=np.int64)
89+
if bidirectional is False: # Idealized 1-dimensional matrix profile index
90+
I[:-1] = width
91+
for i in range(width - 1):
92+
I[i] = rng.RNG.randint(i + 1, width, dtype=np.int64)
93+
94+
target_AC = _nnmark(I)
95+
96+
params = np.empty((n_iter, 2), dtype=np.float64)
97+
for i in range(n_iter):
98+
hist_dist = scipy.stats.rv_histogram(
99+
(target_AC, np.append(np.arange(width), width))
100+
)
101+
hist_dist.random_state = rng.RNG
102+
data = hist_dist.rvs(size=n_samples)
103+
a, b, c, d = scipy.stats.beta.fit(data, floc=0, fscale=width)
88104

89-
I = np.random.randint(0, width, size=width, dtype=np.int64)
90-
if bidirectional is False: # Idealized 1-dimensional matrix profile index
91-
I[:-1] = width
92-
for i in range(width - 1):
93-
I[i] = np.random.randint(i + 1, width, dtype=np.int64)
105+
params[i, 0] = a
106+
params[i, 1] = b
94107

95-
target_AC = _nnmark(I)
108+
a_mean = np.round(np.mean(params[:, 0]), 2)
109+
b_mean = np.round(np.mean(params[:, 1]), 2)
96110

97-
params = np.empty((n_iter, 2), dtype=np.float64)
98-
for i in range(n_iter):
99-
hist_dist = scipy.stats.rv_histogram(
100-
(target_AC, np.append(np.arange(width), width))
111+
IAC = scipy.stats.beta.pdf(np.arange(width), a_mean, b_mean, loc=0, scale=width)
112+
slope, _, _, _ = np.linalg.lstsq(
113+
np.expand_dims(IAC, axis=1), target_AC, rcond=None
101114
)
102-
data = hist_dist.rvs(size=n_samples)
103-
a, b, c, d = scipy.stats.beta.fit(data, floc=0, fscale=width)
104-
105-
params[i, 0] = a
106-
params[i, 1] = b
107-
108-
a_mean = np.round(np.mean(params[:, 0]), 2)
109-
b_mean = np.round(np.mean(params[:, 1]), 2)
110-
111-
IAC = scipy.stats.beta.pdf(np.arange(width), a_mean, b_mean, loc=0, scale=width)
112-
slope, _, _, _ = np.linalg.lstsq(np.expand_dims(IAC, axis=1), target_AC, rcond=None)
113115

114-
IAC *= slope
116+
IAC *= slope
115117

116118
return IAC
117119

stumpy/rng.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import os
2+
from contextlib import contextmanager
3+
4+
import numpy as np
5+
6+
# Note that an initial SEED = 0 is disallowed
7+
# in order to account for unit testing
8+
if os.getenv("STUMPY_SEED") is not None: # pragma: no cover
9+
SEED = int(os.getenv("STUMPY_SEED"))
10+
else:
11+
SEED = np.random.randint(1, 4_294_967_296, dtype=np.uint32)
12+
RNG = np.random.RandomState(seed=SEED)
13+
14+
15+
def set_seed(seed):
16+
"""
17+
Permanently set the RNG seed to a different value
18+
19+
Parameters
20+
----------
21+
seed : int
22+
The random seed for (permanently) setting the random number generator to
23+
24+
Returns
25+
-------
26+
None
27+
"""
28+
global SEED
29+
global RNG
30+
SEED = seed
31+
RNG = np.random.RandomState(seed=SEED)
32+
33+
34+
@contextmanager
35+
def fix_seed(seed):
36+
"""
37+
A context manager for setting the RNG seed to a fixed, hardcoded, safe seed
38+
and then returning the RNG back to its previous state prior to the seed change
39+
40+
This is typically used when you want to generate a specific random sequence once.
41+
To repeat the same random sequence, use `fix_state` instead. If you are picking
42+
a random seed directly before calling `fix_seed` then you probably want to use
43+
`fix_state` instead!
44+
45+
Parameters
46+
----------
47+
seed : int
48+
The random seed for (temporarily) setting the random number generator to
49+
50+
Returns
51+
-------
52+
None
53+
"""
54+
curr_state = RNG.get_state()
55+
RNG.seed(seed)
56+
try:
57+
yield
58+
finally:
59+
RNG.set_state(curr_state)
60+
61+
62+
@contextmanager
63+
def fix_state():
64+
"""
65+
A context manager for setting the RNG state to a fixed, hardcoded, safe state
66+
and then returning the RNG back to its previous state prior to the state change
67+
68+
This is typically used when you want to repeat the same random sequence more than
69+
once.
70+
71+
Parameters
72+
----------
73+
None
74+
75+
Returns
76+
-------
77+
None
78+
"""
79+
curr_state = RNG.get_state()
80+
try:
81+
yield
82+
finally:
83+
RNG.set_state(curr_state)

stumpy/scraamp.py

Lines changed: 4 additions & 4 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, rng
1010
from .aamp import _aamp
1111

1212

@@ -78,7 +78,7 @@ def _preprocess_prescraamp(T_A, m, T_B=None, s=None):
7878
else: # AB-join
7979
s = int(np.ceil(m / config.STUMPY_EXCL_ZONE_DENOM))
8080

81-
indices = np.random.permutation(range(0, l, s)).astype(np.int64)
81+
indices = rng.RNG.permutation(range(0, l, s)).astype(np.int64)
8282

8383
return (T_A, T_B, T_A_subseq_isfinite, T_B_subseq_isfinite, indices, s, excl_zone)
8484

@@ -718,7 +718,7 @@ def __init__(
718718
core._merge_topk_PI(self._P, P, self._I, I)
719719

720720
if self._ignore_trivial:
721-
self._diags = np.random.permutation(
721+
self._diags = rng.RNG.permutation(
722722
range(self._excl_zone + 1, self._n_A - self._m + 1)
723723
).astype(np.int64)
724724
if self._diags.shape[0] == 0: # pragma: no cover
@@ -728,7 +728,7 @@ def __init__(
728728
f"Please try a value of `m <= {max_m}`"
729729
)
730730
else:
731-
self._diags = np.random.permutation(
731+
self._diags = rng.RNG.permutation(
732732
range(-(self._n_A - self._m + 1) + 1, self._n_B - self._m + 1)
733733
).astype(np.int64)
734734

stumpy/scrump.py

Lines changed: 4 additions & 4 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, sdp
9+
from . import config, core, rng, sdp
1010
from .scraamp import prescraamp, scraamp
1111
from .stump import _stump
1212

@@ -116,7 +116,7 @@ def _preprocess_prescrump(
116116
else: # AB-join
117117
s = int(np.ceil(m / config.STUMPY_EXCL_ZONE_DENOM))
118118

119-
indices = np.random.permutation(range(0, l, s)).astype(np.int64)
119+
indices = rng.RNG.permutation(range(0, l, s)).astype(np.int64)
120120

121121
return (
122122
T_A,
@@ -997,7 +997,7 @@ def __init__(
997997
core._merge_topk_PI(self._P, P, self._I, I)
998998

999999
if self._ignore_trivial:
1000-
self._diags = np.random.permutation(
1000+
self._diags = rng.RNG.permutation(
10011001
range(self._excl_zone + 1, self._n_A - self._m + 1)
10021002
).astype(np.int64)
10031003
if self._diags.shape[0] == 0: # pragma: no cover
@@ -1007,7 +1007,7 @@ def __init__(
10071007
f"Please try a value of `m <= {max_m}`"
10081008
)
10091009
else:
1010-
self._diags = np.random.permutation(
1010+
self._diags = rng.RNG.permutation(
10111011
range(-(self._n_A - self._m + 1) + 1, self._n_B - self._m + 1)
10121012
).astype(np.int64)
10131013

tests/naive.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from scipy.spatial.distance import cdist
55
from scipy.stats import norm
66

7-
from stumpy import config, core
7+
from stumpy import config, core, rng
88

99

1010
def is_ptp_zero_1d(a, w): # `a` is 1-D
@@ -1818,7 +1818,7 @@ def prescrump(
18181818
P = np.full((l, k), np.inf, dtype=np.float64)
18191819
I = np.full((l, k), -1, dtype=np.int64)
18201820

1821-
for i in np.random.permutation(range(0, l, s)):
1821+
for i in rng.RNG.permutation(range(0, l, s)):
18221822
distance_profile = dist_matrix[i]
18231823
if exclusion_zone is not None:
18241824
apply_exclusion_zone(distance_profile, i, exclusion_zone, np.inf)
@@ -1914,11 +1914,11 @@ def scrump(
19141914
pass
19151915

19161916
if exclusion_zone is not None:
1917-
diags = np.random.permutation(range(exclusion_zone + 1, n_A - m + 1)).astype(
1917+
diags = rng.RNG.permutation(range(exclusion_zone + 1, n_A - m + 1)).astype(
19181918
np.int64
19191919
)
19201920
else:
1921-
diags = np.random.permutation(range(-(n_A - m + 1) + 1, n_B - m + 1)).astype(
1921+
diags = rng.RNG.permutation(range(-(n_A - m + 1) + 1, n_B - m + 1)).astype(
19221922
np.int64
19231923
)
19241924

@@ -1981,7 +1981,7 @@ def prescraamp(T_A, m, T_B, s, exclusion_zone=None, p=2.0, k=1):
19811981
P = np.full((l, k), np.inf, dtype=np.float64)
19821982
I = np.full((l, k), -1, dtype=np.int64)
19831983

1984-
for i in np.random.permutation(range(0, l, s)):
1984+
for i in rng.RNG.permutation(range(0, l, s)):
19851985
distance_profile = distance_matrix[i]
19861986
if exclusion_zone is not None:
19871987
apply_exclusion_zone(distance_profile, i, exclusion_zone, np.inf)
@@ -2054,11 +2054,11 @@ def scraamp(T_A, m, T_B, percentage, exclusion_zone, pre_scraamp, s, p=2.0, k=1)
20542054
l = n_A - m + 1
20552055

20562056
if exclusion_zone is not None:
2057-
diags = np.random.permutation(range(exclusion_zone + 1, n_A - m + 1)).astype(
2057+
diags = rng.RNG.permutation(range(exclusion_zone + 1, n_A - m + 1)).astype(
20582058
np.int64
20592059
)
20602060
else:
2061-
diags = np.random.permutation(range(-(n_A - m + 1) + 1, n_B - m + 1)).astype(
2061+
diags = rng.RNG.permutation(range(-(n_A - m + 1) + 1, n_B - m + 1)).astype(
20622062
np.int64
20632063
)
20642064

tests/test_aamp.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import pandas as pd
55
import pytest
66

7-
from stumpy import config
7+
from stumpy import config, rng
88
from stumpy.aamp import aamp
99

1010
test_data = [
@@ -13,8 +13,8 @@
1313
np.array([584, -11, 23, 79, 1001, 0, -19], dtype=np.float64),
1414
),
1515
(
16-
np.random.uniform(-1000, 1000, [8]).astype(np.float64),
17-
np.random.uniform(-1000, 1000, [64]).astype(np.float64),
16+
rng.RNG.uniform(-1000, 1000, size=8).astype(np.float64),
17+
rng.RNG.uniform(-1000, 1000, size=64).astype(np.float64),
1818
),
1919
]
2020

@@ -72,7 +72,7 @@ def test_aamp_constant_subsequence_self_join():
7272

7373

7474
def test_aamp_one_constant_subsequence_A_B_join():
75-
T_A = np.random.rand(20)
75+
T_A = rng.RNG.random(20)
7676
T_B = np.concatenate((np.zeros(20, dtype=np.float64), np.ones(5, dtype=np.float64)))
7777
m = 3
7878
ref_mp = naive.aamp(T_A, m, T_B=T_B)
@@ -122,8 +122,8 @@ def test_aamp_two_constant_subsequences_A_B_join():
122122

123123

124124
def test_aamp_identical_subsequence_self_join():
125-
identical = np.random.rand(8)
126-
T_A = np.random.rand(20)
125+
identical = rng.RNG.random(8)
126+
T_A = rng.RNG.random(20)
127127
T_A[1 : 1 + identical.shape[0]] = identical
128128
T_A[11 : 11 + identical.shape[0]] = identical
129129
m = 3
@@ -143,9 +143,9 @@ def test_aamp_identical_subsequence_self_join():
143143

144144

145145
def test_aamp_identical_subsequence_A_B_join():
146-
identical = np.random.rand(8)
147-
T_A = np.random.rand(20)
148-
T_B = np.random.rand(20)
146+
identical = rng.RNG.random(8)
147+
T_A = rng.RNG.random(20)
148+
T_B = rng.RNG.random(20)
149149
T_A[1 : 1 + identical.shape[0]] = identical
150150
T_B[11 : 11 + identical.shape[0]] = identical
151151
m = 3

tests/test_aamp_mmotifs.py

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import numpy.testing as npt
44
import pytest
55

6-
from stumpy import config
6+
from stumpy import config, rng
77
from stumpy.aamp_mmotifs import aamp_mmotifs
88

99
test_data = [
@@ -60,23 +60,23 @@ def test_aamp_mmotifs_default_parameters():
6060
np.array([411.60964047, 423.69925001, 449.11032383, 476.95855027, 506.62406252])
6161
]
6262

63-
np.random.seed(0)
64-
T = np.random.rand(500).reshape(5, 100)
65-
66-
m = 5
67-
excl_zone = int(np.ceil(m / config.STUMPY_EXCL_ZONE_DENOM))
68-
P, I = naive.maamp(T, m, excl_zone)
69-
(
70-
motif_distances_cmp,
71-
motif_indices_cmp,
72-
motif_subspaces_cmp,
73-
motif_mdls_cmp,
74-
) = aamp_mmotifs(T, P, I)
75-
76-
npt.assert_array_almost_equal(motif_distances_ref, motif_distances_cmp)
77-
npt.assert_array_almost_equal(motif_indices_ref, motif_indices_cmp)
78-
npt.assert_array_almost_equal(motif_subspaces_ref, motif_subspaces_cmp)
79-
npt.assert_array_almost_equal(motif_mdls_ref, motif_mdls_cmp)
63+
with rng.fix_seed(0):
64+
T = rng.RNG.rand(500).reshape(5, 100)
65+
66+
m = 5
67+
excl_zone = int(np.ceil(m / config.STUMPY_EXCL_ZONE_DENOM))
68+
P, I = naive.maamp(T, m, excl_zone)
69+
(
70+
motif_distances_cmp,
71+
motif_indices_cmp,
72+
motif_subspaces_cmp,
73+
motif_mdls_cmp,
74+
) = aamp_mmotifs(T, P, I)
75+
76+
npt.assert_array_almost_equal(motif_distances_ref, motif_distances_cmp)
77+
npt.assert_array_almost_equal(motif_indices_ref, motif_indices_cmp)
78+
npt.assert_array_almost_equal(motif_subspaces_ref, motif_subspaces_cmp)
79+
npt.assert_array_almost_equal(motif_mdls_ref, motif_mdls_cmp)
8080

8181

8282
@pytest.mark.parametrize("T", test_data)

0 commit comments

Comments
 (0)