Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed #966 Add Named Attributes to Matrix Profile #972

Merged
merged 17 commits into from
Jun 8, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion stumpy/aamp.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from numba import njit, prange

from . import config, core
from .mparray import mparray


@njit(
Expand Down Expand Up @@ -378,6 +379,12 @@ def aamp(T_A, m, T_B=None, ignore_trivial=True, p=2.0, k=1):
equivalently, out[:, -2] and out[:, -1]) correspond to the top-1 left
matrix profile indices and the top-1 right matrix profile indices, respectively.

For convenience, the matrix profile (distances) and matrix profile indices can
also be accessed via their corresponding named array attributes, `.P_` and
`.I_`,respectively. Similarly, the corresponding left matrix profile indices
and right matrix profile indices may also be accessed via the `.left_I_` and
`.right_I_` array attributes.

Notes
-----
`arXiv:1901.05708 \
Expand Down Expand Up @@ -431,4 +438,4 @@ def aamp(T_A, m, T_B=None, ignore_trivial=True, p=2.0, k=1):

core._check_P(out[:, 0])

return out
return mparray(out, m, k, config.STUMPY_EXCL_ZONE_DENOM)
NimaSarajpoor marked this conversation as resolved.
Show resolved Hide resolved
9 changes: 8 additions & 1 deletion stumpy/aamped.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from . import config, core
from .aamp import _aamp
from .mparray import mparray


def _dask_aamped(
Expand Down Expand Up @@ -212,6 +213,12 @@ def aamped(client, T_A, m, T_B=None, ignore_trivial=True, p=2.0, k=1):
equivalently, out[:, -2] and out[:, -1]) correspond to the top-1 left
matrix profile indices and the top-1 right matrix profile indices, respectively.

For convenience, the matrix profile (distances) and matrix profile indices can
also be accessed via their corresponding named array attributes, `.P_` and
`.I_`,respectively. Similarly, the corresponding left matrix profile indices
and right matrix profile indices may also be accessed via the `.left_I_` and
`.right_I_` array attributes.

Notes
-----
`arXiv:1901.05708 \
Expand Down Expand Up @@ -264,4 +271,4 @@ def aamped(client, T_A, m, T_B=None, ignore_trivial=True, p=2.0, k=1):

core._check_P(out[:, 0])

return out
return mparray(out, m, k, config.STUMPY_EXCL_ZONE_DENOM)
10 changes: 10 additions & 0 deletions stumpy/chains.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ def atsc(IL, IR, j):
>>> mp = stumpy.stump(np.array([584., -11., 23., 79., 1001., 0., -19.]), m=3)
>>> stumpy.atsc(mp[:, 2], mp[:, 3], 1)
array([1, 3])

# Alternative example using named attributes
>>> mp = stumpy.stump(np.array([584., -11., 23., 79., 1001., 0., -19.]), m=3)
>>> stumpy.atsc(mp.left_I_, mp.right_I_, 1)
array([1, 3])
"""
C = deque([j])
for i in range(IL.size):
Expand Down Expand Up @@ -118,6 +123,11 @@ def allc(IL, IR):
>>> mp = stumpy.stump(np.array([584., -11., 23., 79., 1001., 0., -19.]), m=3)
>>> stumpy.allc(mp[:, 2], mp[:, 3])
([array([1, 3]), array([2]), array([0, 4])], array([0, 4]))

# Alternative example using named attributes
>>> mp = stumpy.stump(np.array([584., -11., 23., 79., 1001., 0., -19.]), m=3)
>>> stumpy.allc(mp.left_I_, mp.right_I_)
([array([1, 3]), array([2]), array([0, 4])], array([0, 4]))
"""
L = np.ones(IL.size, dtype=np.int64)
S = set() # type: ignore
Expand Down
5 changes: 5 additions & 0 deletions stumpy/floss.py
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,11 @@ def fluss(I, L, n_regimes, excl_factor=5, custom_iac=None):
>>> mp = stumpy.stump(np.array([584., -11., 23., 79., 1001., 0., -19.]), m=3)
>>> stumpy.fluss(mp[:, 0], 3, 2)
(array([1., 1., 1., 1., 1.]), array([0]))

# Alternative example using named attributes
>>> mp = stumpy.stump(np.array([584., -11., 23., 79., 1001., 0., -19.]), m=3)
>>> stumpy.fluss(mp.P_, 3, 2)
(array([1., 1., 1., 1., 1.]), array([0]))
"""
cac = _cac(I, L, bidirectional=True, excl_factor=excl_factor, custom_iac=custom_iac)
regime_locs = _rea(cac, n_regimes, L, excl_factor=excl_factor)
Expand Down
9 changes: 8 additions & 1 deletion stumpy/gpu_aamp.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from numba import cuda

from . import config, core
from .mparray import mparray


@cuda.jit(
Expand Down Expand Up @@ -496,6 +497,12 @@ def gpu_aamp(T_A, m, T_B=None, ignore_trivial=True, device_id=0, p=2.0, k=1):
equivalently, out[:, -2] and out[:, -1]) correspond to the top-1 left
matrix profile indices and the top-1 right matrix profile indices, respectively.

For convenience, the matrix profile (distances) and matrix profile indices can
also be accessed via their corresponding named array attributes, `.P_` and
`.I_`,respectively. Similarly, the corresponding left matrix profile indices
and right matrix profile indices may also be accessed via the `.left_I_` and
`.right_I_` array attributes.

Notes
-----
`arXiv:1901.05708 \
Expand Down Expand Up @@ -706,4 +713,4 @@ def gpu_aamp(T_A, m, T_B=None, ignore_trivial=True, device_id=0, p=2.0, k=1):

core._check_P(out[:, 0])

return out
return mparray(out, m, k, config.STUMPY_EXCL_ZONE_DENOM)
26 changes: 19 additions & 7 deletions stumpy/gpu_stump.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

from . import config, core
from .gpu_aamp import gpu_aamp
from .mparray import mparray


@cuda.jit(
Expand Down Expand Up @@ -573,6 +574,12 @@ def gpu_stump(
equivalently, out[:, -2] and out[:, -1]) correspond to the top-1 left
matrix profile indices and the top-1 right matrix profile indices, respectively.

For convenience, the matrix profile (distances) and matrix profile indices can
also be accessed via their corresponding named array attributes, `.P_` and
`.I_`,respectively. Similarly, the corresponding left matrix profile indices
and right matrix profile indices may also be accessed via the `.left_I_` and
`.right_I_` array attributes. See examples below.

See Also
--------
stumpy.stump : Compute the z-normalized matrix profile
Expand Down Expand Up @@ -615,15 +622,20 @@ def gpu_stump(
>>> from numba import cuda
>>> if __name__ == "__main__":
... all_gpu_devices = [device.id for device in cuda.list_devices()]
... stumpy.gpu_stump(
... mp = stumpy.gpu_stump(
... np.array([584., -11., 23., 79., 1001., 0., -19.]),
... m=3,
... device_id=all_gpu_devices)
array([[0.11633857113691416, 4, -1, 4],
[2.694073918063438, 3, -1, 3],
[3.0000926340485923, 0, 0, 4],
[2.694073918063438, 1, 1, -1],
[0.11633857113691416, 0, 0, -1]], dtype=object)
>>> mp
mparray([[0.11633857113691416, 4, -1, 4],
[2.694073918063438, 3, -1, 3],
[3.0000926340485923, 0, 0, 4],
[2.694073918063438, 1, 1, -1],
[0.11633857113691416, 0, 0, -1]], dtype=object)
>>> mp.P_
mparray([0.11633857, 2.69407392, 3.00009263, 2.69407392, 0.11633857])
>>> mp.I_
mparray([4, 3, 0, 1, 0])
"""
if T_B is None: # Self join!
T_B = T_A
Expand Down Expand Up @@ -838,4 +850,4 @@ def gpu_stump(

core._check_P(out[:, 0])

return out
return mparray(out, m, k, config.STUMPY_EXCL_ZONE_DENOM)
8 changes: 8 additions & 0 deletions stumpy/motifs.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,14 @@ def motifs(
... mp[:, 0],
... max_distance=2.0)
(array([[0. , 0.11633857]]), array([[0, 4]]))

# Alternative example using named attributes
>>> mp = stumpy.stump(np.array([584., -11., 23., 79., 1001., 0., -19.]), m=3)
>>> stumpy.motifs(
... np.array([584., -11., 23., 79., 1001., 0., -19.]),
... mp.P_,
... max_distance=2.0)
(array([[0. , 0.11633857]]), array([[0, 4]]))
"""
T = core._preprocess(T)

Expand Down
189 changes: 189 additions & 0 deletions stumpy/mparray.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
import numpy as np


class mparray(np.ndarray):
"""
A matrix profile convenience class that subclasses the numpy ndarray
NimaSarajpoor marked this conversation as resolved.
Show resolved Hide resolved

Parameters
----------
cls : class
The base class

input_array : ndarray
The input `numpy` array to be subclassed

m : int
Window size

k : int, default 1
NimaSarajpoor marked this conversation as resolved.
Show resolved Hide resolved
The number of top `k` smallest distances used to construct the
matrix profile.

NimaSarajpoor marked this conversation as resolved.
Show resolved Hide resolved
Attributes
----------
P_ : numpy.ndarray
The (top-k) matrix profile for `T`. When `k=1` (default), the first
NimaSarajpoor marked this conversation as resolved.
Show resolved Hide resolved
(and only) column in this 2D array, which consists of the matrix profile,
is returned. When `k > 1`, the output has exactly `k` columns consisting of
the top-k matrix profile.

I_ : numpy.ndarray
The(top-k) matrix profile indices for `T`. When `k=1` (default), the first
NimaSarajpoor marked this conversation as resolved.
Show resolved Hide resolved
(and only) column in this 2D array, which consists of the matrix profile,
indices is returned. When `k > 1`, the output has exactly `k` columns
consisting of the top-k matrix profile indices.

left_I_ : numpy.ndarray
The left (top-1) matrix profile indices for `T`

right_I_ : numpy.ndarray
The right (top-1) matrix profile indices for `T`
"""

def __new__(cls, input_array, m, k, excl_zone_denom):
"""
Create the ndarray instance of our type, given the usual
ndarray input arguments. This will call the standard
ndarray constructor, but return an object of our type.
It also triggers a call mparray.__array_finalize__

Parameters
----------
cls : class
The base class

input_array : ndarray
The input `numpy` array to be subclassed

m : int
Window size

k : int
The number of top `k` smallest distances used to construct the
matrix profile.
NimaSarajpoor marked this conversation as resolved.
Show resolved Hide resolved

excl_zone_denom : int
The denominator used in computing the exclusion zone
"""
obj = np.asarray(input_array).view(cls)
obj._m = m
obj._k = k
obj._excl_zone_denom = excl_zone_denom
# All new attributes will also need to be added to the `__array_finalize__`
# function below so that "new-from-template" objects (e.g., an array slice)
# will also contain the same new attributes
return obj

def __array_finalize__(self, obj):
"""
Finalize the array

Parameters
----------
obj : object
This is the class object
"""
if obj is None: # pragma: no cover
return
# The lines below ensure that child objects that are created from a slice
# of an `mparray` will also inherit the attributes from the parent `mparray`
self._m = getattr(obj, "_m", None)
self._k = getattr(obj, "_k", None)
self._excl_zone_denom = getattr(obj, "_excl_zone_denom", None)

def _P(self):
"""
Matrix profile values

Parameters
----------
None
"""
if self._k == 1:
return self[:, : self._k].flatten().astype(np.float64)
else:
return self[:, : self._k].astype(np.float64)

def _I(self):
"""
Nearest neighbor indices

Parameters
----------
None
"""
if self._k == 1:
return self[:, self._k : 2 * self._k].flatten().astype(np.int64)
else:
return self[:, self._k : 2 * self._k].astype(np.int64)

def _left_I(self):
"""
Left nearest neighbor indices

Parameters
----------
None
"""
if self._k == 1:
return self[:, 2 * self._k].flatten().astype(np.int64)
else:
return self[:, 2 * self._k].astype(np.int64)

def _right_I(self):
"""
Right nearest neighbor indices

Parameters
----------
None
"""
if self._k == 1:
return self[:, 2 * self._k + 1].flatten().astype(np.int64)
else:
return self[:, 2 * self._k + 1].astype(np.int64)

@property
def P_(self):
"""
Matrix profile values

Parameters
----------
None
"""
return self._P()

@property
def I_(self):
"""
Nearest neighbor indices

Parameters
----------
None
"""
return self._I()

@property
def left_I_(self):
"""
Left nearest neighbor indices

Parameters
----------
None
"""
return self._left_I()

@property
def right_I_(self):
"""
Right nearest neighbor indices

Parameters
----------
None
"""
return self._right_I()
Loading
Loading