Skip to content

Commit c5b4d0d

Browse files
committed
Fixed #341 Added non-normalized support for FLOSS
1 parent 0fdd090 commit c5b4d0d

2 files changed

Lines changed: 181 additions & 14 deletions

File tree

stumpy/floss.py

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,9 @@ class floss(object):
335335
A custom idealized arc curve (IAC) that will used for correcting the
336336
arc curve
337337
338+
normalize : bool, default True
339+
When set to `True`, this z-normalizes subsequences prior to computing distances
340+
338341
Attributes
339342
----------
340343
cac_1d_ : ndarray
@@ -370,7 +373,16 @@ class floss(object):
370373
"""
371374

372375
def __init__(
373-
self, mp, T, m, L, excl_factor=5, n_iter=1000, n_samples=1000, custom_iac=None
376+
self,
377+
mp,
378+
T,
379+
m,
380+
L,
381+
excl_factor=5,
382+
n_iter=1000,
383+
n_samples=1000,
384+
custom_iac=None,
385+
normalize=True,
374386
):
375387
"""
376388
Initialize the FLOSS object
@@ -416,6 +428,10 @@ def __init__(
416428
custom_iac : ndarray, default None
417429
A custom idealized arc curve (IAC) that will used for correcting the
418430
arc curve
431+
432+
normalize : bool, default True
433+
When set to `True`, this z-normalizes subsequences prior to computing
434+
distances
419435
"""
420436
self._mp = copy.deepcopy(np.asarray(mp))
421437
self._T = copy.deepcopy(np.asarray(T))
@@ -425,6 +441,7 @@ def __init__(
425441
self._n_iter = n_iter
426442
self._n_samples = n_samples
427443
self._custom_iac = custom_iac
444+
self._normalize = normalize
428445
self._k = self._mp.shape[0]
429446
self._n = self._T.shape[0]
430447
self._last_idx = self._n - self._m + 1 # Depends on the changing length of `T`
@@ -453,11 +470,17 @@ def __init__(
453470
# Note that any -1 indices must have a np.inf matrix profile value
454471
right_indices = [np.arange(IR, IR + self._m) for IR in self._mp[:, 3].tolist()]
455472
right_nn[:] = self._T[np.array(right_indices)]
456-
self._mp[:, 0] = np.linalg.norm(
457-
core.z_norm(core.rolling_window(self._T, self._m), 1)
458-
- core.z_norm(right_nn, 1),
459-
axis=1,
460-
)
473+
if self._normalize:
474+
self._mp[:, 0] = np.linalg.norm(
475+
core.z_norm(core.rolling_window(self._T, self._m), 1)
476+
- core.z_norm(right_nn, 1),
477+
axis=1,
478+
)
479+
else:
480+
self._mp[:, 0] = np.linalg.norm(
481+
core.rolling_window(self._T, self._m) - right_nn,
482+
axis=1,
483+
)
461484
inf_indices = np.argwhere(self._mp[:, 3] < 0).flatten()
462485
self._mp[inf_indices, 0] = np.inf
463486
self._mp[inf_indices, 3] = inf_indices
@@ -508,9 +531,12 @@ def update(self, t):
508531
self._mp[-1, 3] = self._last_idx
509532

510533
# Ingress
511-
M_T, Σ_T = core.compute_mean_std(self._T, self._m)
534+
if self._normalize:
535+
M_T, Σ_T = core.compute_mean_std(self._T, self._m)
536+
D = core.mass(self._finite_Q, self._finite_T, M_T, Σ_T)
537+
else:
538+
D = core.mass_absolute(self._T[-self._m :], self._T)
512539

513-
D = core.mass(self._finite_Q, self._finite_T, M_T, Σ_T)
514540
D[zone_start:] = np.inf
515541

516542
T_subseq_isfinite = core.rolling_isfinite(self._T_isfinite, self._m)

tests/test_floss.py

Lines changed: 147 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import numpy as np
22
import numpy.testing as npt
3-
from stumpy import fluss, stump, core, floss
3+
from stumpy import fluss, stump, aamp, core, floss
44
from stumpy.floss import _nnmark, _iac, _cac, _rea
55
import copy
66
import pytest
@@ -44,15 +44,22 @@ def naive_cac(I, L, excl_factor, custom_iac=None):
4444
return CAC
4545

4646

47-
def naive_right_mp(data, m):
48-
mp = stump(data, m)
47+
def naive_right_mp(data, m, normalize=True):
48+
if normalize:
49+
mp = stump(data, m)
50+
else:
51+
mp = aamp(data, m)
4952
k = mp.shape[0]
5053
right_nn = np.zeros((k, m))
5154
right_indices = [np.arange(IR, IR + m) for IR in mp[:, 3].tolist()]
5255
right_nn[:] = data[np.array(right_indices)]
53-
mp[:, 0] = np.linalg.norm(
54-
core.z_norm(core.rolling_window(data, m), 1) - core.z_norm(right_nn, 1), axis=1
55-
)
56+
if normalize:
57+
mp[:, 0] = np.linalg.norm(
58+
core.z_norm(core.rolling_window(data, m), 1) - core.z_norm(right_nn, 1),
59+
axis=1,
60+
)
61+
else:
62+
mp[:, 0] = np.linalg.norm(core.rolling_window(data, m) - right_nn, axis=1)
5663
inf_indices = np.argwhere(mp[:, 3] < 0).flatten()
5764
mp[inf_indices, 0] = np.inf
5865
mp[inf_indices, 3] = inf_indices
@@ -191,6 +198,67 @@ def test_floss():
191198
npt.assert_almost_equal(ref_T, comp_T)
192199

193200

201+
def test_aamp_floss():
202+
data = np.random.uniform(-1000, 1000, [64])
203+
m = 5
204+
n = 30
205+
old_data = data[:n]
206+
207+
mp = naive_right_mp(old_data, m, normalize=False)
208+
comp_mp = aamp(old_data, m)
209+
k = mp.shape[0]
210+
211+
rolling_Ts = core.rolling_window(data[1:], n)
212+
L = 5
213+
excl_factor = 1
214+
custom_iac = _iac(k, bidirectional=False)
215+
stream = floss(
216+
comp_mp, old_data, m, L, excl_factor, custom_iac=custom_iac, normalize=False
217+
)
218+
last_idx = n - m + 1
219+
excl_zone = int(np.ceil(m / 4))
220+
zone_start = max(0, k - excl_zone)
221+
for i, ref_T in enumerate(rolling_Ts):
222+
mp[:, 1] = -1
223+
mp[:, 2] = -1
224+
mp[:] = np.roll(mp, -1, axis=0)
225+
mp[-1, 0] = np.inf
226+
mp[-1, 3] = last_idx + i
227+
228+
D = naive.aamp_distance_profile(ref_T[-m:], ref_T, m)
229+
D[zone_start:] = np.inf
230+
231+
update_idx = np.argwhere(D < mp[:, 0]).flatten()
232+
mp[update_idx, 0] = D[update_idx]
233+
mp[update_idx, 3] = last_idx + i
234+
235+
ref_cac_1d = _cac(
236+
mp[:, 3] - i - 1,
237+
L,
238+
bidirectional=False,
239+
excl_factor=excl_factor,
240+
custom_iac=custom_iac,
241+
)
242+
243+
ref_mp = mp.copy()
244+
ref_P = ref_mp[:, 0]
245+
ref_I = ref_mp[:, 3]
246+
247+
stream.update(ref_T[-1])
248+
comp_cac_1d = stream.cac_1d_
249+
comp_P = stream.P_
250+
comp_I = stream.I_
251+
comp_T = stream.T_
252+
253+
naive.replace_inf(ref_P)
254+
naive.replace_inf(comp_P)
255+
256+
npt.assert_almost_equal(ref_cac_1d, comp_cac_1d)
257+
npt.assert_almost_equal(ref_P, comp_P)
258+
npt.assert_almost_equal(ref_I, comp_I)
259+
npt.assert_almost_equal(ref_T, comp_T)
260+
261+
194262
@pytest.mark.parametrize("substitute", substitution_values)
195263
@pytest.mark.parametrize("substitution_locations", substitution_locations)
196264
def test_floss_inf_nan(substitute, substitution_locations):
@@ -260,3 +328,76 @@ def test_floss_inf_nan(substitute, substitution_locations):
260328
npt.assert_almost_equal(ref_P, comp_P)
261329
npt.assert_almost_equal(ref_I, comp_I)
262330
npt.assert_almost_equal(ref_T, comp_T)
331+
332+
333+
@pytest.mark.parametrize("substitute", substitution_values)
334+
@pytest.mark.parametrize("substitution_locations", substitution_locations)
335+
def test_aamp_floss_inf_nan(substitute, substitution_locations):
336+
T = np.random.uniform(-1000, 1000, [64])
337+
m = 5
338+
n = 30
339+
data = T.copy()
340+
for substitution_location in substitution_locations:
341+
data[:] = T[:]
342+
data[substitution_location] = substitute
343+
old_data = data[:n]
344+
345+
mp = naive_right_mp(old_data, m, normalize=False)
346+
comp_mp = aamp(old_data, m)
347+
k = mp.shape[0]
348+
349+
rolling_Ts = core.rolling_window(data[1:], n)
350+
L = 5
351+
excl_factor = 1
352+
custom_iac = _iac(k, bidirectional=False)
353+
stream = floss(
354+
comp_mp, old_data, m, L, excl_factor, custom_iac=custom_iac, normalize=False
355+
)
356+
last_idx = n - m + 1
357+
excl_zone = int(np.ceil(m / 4))
358+
zone_start = max(0, k - excl_zone)
359+
for i, ref_T in enumerate(rolling_Ts):
360+
mp[:, 1] = -1
361+
mp[:, 2] = -1
362+
mp[:] = np.roll(mp, -1, axis=0)
363+
mp[-1, 0] = np.inf
364+
mp[-1, 3] = last_idx + i
365+
366+
D = naive.aamp_distance_profile(ref_T[-m:], ref_T, m)
367+
D[zone_start:] = np.inf
368+
369+
ref_T_isfinite = np.isfinite(ref_T)
370+
ref_T_subseq_isfinite = np.all(
371+
core.rolling_window(ref_T_isfinite, m), axis=1
372+
)
373+
374+
D[~ref_T_subseq_isfinite] = np.inf
375+
update_idx = np.argwhere(D < mp[:, 0]).flatten()
376+
mp[update_idx, 0] = D[update_idx]
377+
mp[update_idx, 3] = last_idx + i
378+
379+
ref_cac_1d = _cac(
380+
mp[:, 3] - i - 1,
381+
L,
382+
bidirectional=False,
383+
excl_factor=excl_factor,
384+
custom_iac=custom_iac,
385+
)
386+
387+
ref_mp = mp.copy()
388+
ref_P = ref_mp[:, 0]
389+
ref_I = ref_mp[:, 3]
390+
391+
stream.update(ref_T[-1])
392+
comp_cac_1d = stream.cac_1d_
393+
comp_P = stream.P_
394+
comp_I = stream.I_
395+
comp_T = stream.T_
396+
397+
naive.replace_inf(ref_P)
398+
naive.replace_inf(comp_P)
399+
400+
npt.assert_almost_equal(ref_cac_1d, comp_cac_1d)
401+
npt.assert_almost_equal(ref_P, comp_P)
402+
npt.assert_almost_equal(ref_I, comp_I)
403+
npt.assert_almost_equal(ref_T, comp_T)

0 commit comments

Comments
 (0)