Skip to content

Commit b775710

Browse files
authored
Merge branch 'TDAmeritrade:main' into implement_momp
2 parents 357d9b4 + 3cb836b commit b775710

File tree

12 files changed

+308
-91
lines changed

12 files changed

+308
-91
lines changed

.github/workflows/github-actions.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ jobs:
1010
strategy:
1111
matrix:
1212
os: [ubuntu-latest, macos-latest, windows-latest]
13-
python-version: ['3.8']
13+
python-version: ['3.9']
1414
steps:
1515
- uses: actions/checkout@v4
1616
- name: Set Up Python
@@ -63,7 +63,7 @@ jobs:
6363
strategy:
6464
matrix:
6565
os: [ubuntu-latest, macos-latest, windows-latest]
66-
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
66+
python-version: ['3.9', '3.10', '3.11', '3.12']
6767
steps:
6868
- uses: actions/checkout@v4
6969
- name: Set Up Python
@@ -110,7 +110,7 @@ jobs:
110110
strategy:
111111
matrix:
112112
os: [ubuntu-latest, macos-latest, windows-latest]
113-
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
113+
python-version: ['3.9', '3.10', '3.11', '3.12']
114114
steps:
115115
- uses: actions/checkout@v4
116116
- name: Set Up Python

README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,7 @@ Tests are written in the ``tests`` directory and processed using `PyTest <https:
325325
Python Version
326326
--------------
327327

328-
STUMPY supports `Python 3.8+ <https://python3statement.org/>`__ and, due to the use of unicode variable names/identifiers, is not compatible with Python 2.x. Given the small dependencies, STUMPY may work on older versions of Python but this is beyond the scope of our support and we strongly recommend that you upgrade to the most recent version of Python.
328+
STUMPY supports `Python 3.9+ <https://python3statement.org/>`__ and, due to the use of unicode variable names/identifiers, is not compatible with Python 2.x. Given the small dependencies, STUMPY may work on older versions of Python but this is beyond the scope of our support and we strongly recommend that you upgrade to the most recent version of Python.
329329

330330
------------
331331
Getting Help

docs/Contribute.ipynb

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,43 @@
170170
"![Fetch Upstream Fork](images/fetch_upstream_fork.png) "
171171
]
172172
},
173+
{
174+
"cell_type": "markdown",
175+
"metadata": {},
176+
"source": [
177+
":::{admonition} Instructions for Pulling Main into a Development Branch\n",
178+
":class: toggle\n",
179+
"1. Open a terminal\n",
180+
"2. Change the current working directory to your local development repository (e.g., cd Git/stumpy_dev.git)\n",
181+
"3. Check out your local development branch (e.g., git switch some_new_feature)\n",
182+
"4. Commit all changes in your local development branch (e.g., git add some_file.py and git commit)\n",
183+
"5. Fetch the branches and their respective commits from the upstream repository (e.g., git fetch upstream)\n",
184+
"6. Check out your fork's local default branch (e.g., git checkout main)\n",
185+
"7. Merge the changes from the upstream default branch - in this case, upstream/main - into your local default branch (e.g., git merge upstream/main). This brings your fork's default branch into sync with the upstream repository, without losing your local changes.\n",
186+
"8. If your local main branch didn't have any unique commits, Git will perform a fast-forward. Otherwise, if your local main branch had unique commits, you may need to resolve conflicts. Note that this does not affect your development branch!\n",
187+
"9. Next, switch over to your development branch (e.g., git switch some_new_feature)\n",
188+
"10. Finally, merge the main branch into your development branch (e.g., git merge main)\n",
189+
" - You may see something like the following, if you do, you will need to open up the files tagged with CONFLICT and resolve the merge conflicts. Once that's done, you will need to commit the changes and push the commit (e.g., git push) back to Github.\n",
190+
"\n",
191+
"```\n",
192+
"git merge main\n",
193+
"Auto-merging stumpy/aamp_stimp.py\n",
194+
"Auto-merging stumpy/core.py\n",
195+
"Auto-merging stumpy/mpdist.py\n",
196+
"CONFLICT (content): Merge conflict in stumpy/mpdist.py\n",
197+
"Auto-merging stumpy/mstumped.py\n",
198+
"CONFLICT (content): Merge conflict in stumpy/mstumped.py\n",
199+
"Auto-merging stumpy/ostinato.py\n",
200+
"Auto-merging stumpy/stimp.py\n",
201+
"CONFLICT (content): Merge conflict in stumpy/stimp.py\n",
202+
"Auto-merging stumpy/stumped.py\n",
203+
"CONFLICT (content): Merge conflict in stumpy/stumped.py\n",
204+
"Automatic merge failed; fix conflicts and then commit the result.\n",
205+
"You will need to open up the files tagged with CONFLICT and resolve the merge conflicts. Once that's done, you will need to commit the changes and push the commit (e.g., git push) back to Github.\n",
206+
"```\n",
207+
":::"
208+
]
209+
},
173210
{
174211
"cell_type": "markdown",
175212
"metadata": {},

docs/conf.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
"sphinx.ext.intersphinx",
5252
"sphinx.ext.mathjax",
5353
"sphinx.ext.viewcode",
54+
"sphinx_togglebutton",
5455
"numpydoc",
5556
"myst_nb",
5657
]

docs/requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ scikit-learn
77
numpydoc
88
myst-nb
99
jupyterlab-myst
10+
sphinx-togglebutton

environment.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ channels:
22
- numba
33
- conda-forge
44
dependencies:
5-
- python>=3.8
6-
- numpy>=1.21
5+
- python>=3.9
6+
- numpy>=1.22
77
- scipy>=1.10
8-
- numba>=0.57.1
8+
- numba>=0.59.1
99
- pandas>=0.20.0
1010
- flake8>=3.7.7
1111
- flake8-docstrings>=1.5.0

pyproject.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
55
[project]
66
name = "stumpy"
77
version = "1.13.0"
8-
requires-python = ">=3.8"
8+
requires-python = ">=3.9"
99
authors = [
1010
{name = "Sean M. Law", email = "seanmylaw@gmail.com"}
1111
]
@@ -23,7 +23,7 @@ classifiers = [
2323
"Operating System :: POSIX",
2424
"Operating System :: Unix",
2525
"Operating System :: MacOS",
26-
"Programming Language :: Python :: 3.8",
26+
"Programming Language :: Python :: 3.9",
2727
]
2828
keywords = ["time series", "matrix profile", "motif", "discord"]
2929
maintainers = [
@@ -32,9 +32,9 @@ maintainers = [
3232
]
3333
license = {text = "3-clause BSD License"}
3434
dependencies = [
35-
"numpy >= 1.21",
35+
"numpy >= 1.22",
3636
"scipy >= 1.10",
37-
"numba >= 0.57.1"
37+
"numba >= 0.59.1"
3838
]
3939

4040
[tool.setuptools]

requirements.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
numpy>=1.21
1+
numpy>=1.22
22
scipy>=1.10
3-
numba>=0.57.1
3+
numba>=0.59.1

stumpy/aampi.py

Lines changed: 9 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -247,26 +247,9 @@ def _update_egress(self, t):
247247
if np.any(~self._T_isfinite[-self._m :]):
248248
D[:] = np.inf
249249

250-
core.apply_exclusion_zone(D, D.shape[0] - 1, self._excl_zone, np.inf)
251-
252-
update_idx = np.argwhere(D < self._P[:, -1]).flatten()
253-
for i in update_idx:
254-
idx = np.searchsorted(self._P[i], D[i], side="right")
255-
core._shift_insert_at_index(self._P[i], idx, D[i])
256-
core._shift_insert_at_index(
257-
self._I[i], idx, D.shape[0] + self._n_appended - 1
258-
)
259-
# D.shape[0] is base-1
260-
261-
# Calculate the (top-k) matrix profile values/indices for the last subsequence
262-
# by using its corresponding distance profile `D`
263-
self._P[-1] = np.inf
264-
self._I[-1] = -1
265-
for i, d in enumerate(D):
266-
if d < self._P[-1, -1]:
267-
idx = np.searchsorted(self._P[-1], d, side="right")
268-
core._shift_insert_at_index(self._P[-1], idx, d)
269-
core._shift_insert_at_index(self._I[-1], idx, i + self._n_appended)
250+
core._update_incremental_PI(
251+
D, self._P, self._I, self._excl_zone, n_appended=self._n_appended
252+
)
270253

271254
# All neighbors of the last subsequence are on its left. So, its (top-1)
272255
# matrix profile value/index and its left matrix profile value/index must
@@ -322,30 +305,17 @@ def _update(self, t):
322305
if np.any(~self._T_isfinite[-self._m :]):
323306
D[:] = np.inf
324307

325-
core.apply_exclusion_zone(D, D.shape[0] - 1, self._excl_zone, np.inf)
326-
327-
update_idx = np.argwhere(D[:l] < self._P[:l, -1]).flatten()
328-
for i in update_idx:
329-
idx = np.searchsorted(self._P[i], D[i], side="right")
330-
core._shift_insert_at_index(self._P[i], idx, D[i])
331-
core._shift_insert_at_index(self._I[i], idx, l)
332-
333-
# Calculating top-k matrix profile and (top-1) left matrix profile (and their
334-
# corresponding indices) for new subsequence whose distance profile is `D`
335308
P_new = np.full(self._k, np.inf, dtype=np.float64)
336309
I_new = np.full(self._k, -1, dtype=np.int64)
337-
for i, d in enumerate(D):
338-
if d < P_new[-1]: # maximum value in sorted array P_new
339-
idx = np.searchsorted(P_new, d, side="right")
340-
core._shift_insert_at_index(P_new, idx, d)
341-
core._shift_insert_at_index(I_new, idx, i)
310+
self._P = np.append(self._P, P_new.reshape(1, -1), axis=0)
311+
self._I = np.append(self._I, I_new.reshape(1, -1), axis=0)
312+
313+
core._update_incremental_PI(D, self._P, self._I, self._excl_zone, n_appended=0)
342314

343-
left_I_new = I_new[0]
344-
left_P_new = P_new[0]
315+
left_I_new = self._I[-1, 0]
316+
left_P_new = self._P[-1, 0]
345317

346318
self._T = T_new
347-
self._P = np.append(self._P, P_new.reshape(1, -1), axis=0)
348-
self._I = np.append(self._I, I_new.reshape(1, -1), axis=0)
349319
self._left_P = np.append(self._left_P, left_P_new)
350320
self._left_I = np.append(self._left_I, left_I_new)
351321
self._p_norm = p_norm_new

stumpy/core.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4368,3 +4368,70 @@ def get_ray_nworkers(ray_client):
43684368
Total number of Ray workers
43694369
"""
43704370
return int(ray_client.cluster_resources().get("CPU"))
4371+
4372+
4373+
@njit
4374+
def _update_incremental_PI(D, P, I, excl_zone, n_appended=0):
4375+
"""
4376+
Given the 1D array distance profile, `D`, of the last subsequence of T,
4377+
update (in-place) the (top-k) matrix profile, `P`, and the matrix profile
4378+
index, I.
4379+
4380+
Parameters
4381+
----------
4382+
D : numpy.ndarray
4383+
A 1D array (with dtype float) representing the distance profile of
4384+
the last subsequence of T
4385+
4386+
P : numpy.ndarray
4387+
A 2D array representing the matrix profile of T,
4388+
with shape (len(T) - m + 1, k), where `m` is the window size.
4389+
P[-1, :] should be set to np.inf
4390+
4391+
I : numpy.ndarray
4392+
A 2D array representing the matrix profile index of T,
4393+
with shape (len(T) - m + 1, k), where `m` is the window size
4394+
I[-1, :] should be set to -1.
4395+
4396+
excl_zone : int
4397+
Size of the exclusion zone.
4398+
4399+
n_appended : int
4400+
Number of times the timeseries start point is shifted one to the right.
4401+
See note below for more details.
4402+
4403+
Returns
4404+
-------
4405+
None
4406+
4407+
Note
4408+
-----
4409+
The `n_appended` parameter is used to indicate the number of times the timeseries
4410+
start point is shifted one to the right. When `egress=False` (see stumpy.stumpi),
4411+
the matrix profile and matrix profile index are updated in an incremental fashion
4412+
while considering all historical data. `n_appended` must be set to 0 in such
4413+
cases. However, when `egress=True`, the matrix profile and matrix profile index are
4414+
updated in an incremental fashion and they represent the matrix profile and matrix
4415+
profile index for the `l` most recent subsequences (where `l = len(T) - m + 1`).
4416+
In this case, each subsequence is only compared against upto `l-1` left neighbors
4417+
and upto `l-1` right neighbors.
4418+
"""
4419+
_apply_exclusion_zone(D, D.shape[0] - 1, excl_zone, np.inf)
4420+
4421+
update_idx = np.argwhere(D < P[:, -1]).flatten()
4422+
for i in update_idx:
4423+
idx = np.searchsorted(P[i], D[i], side="right")
4424+
_shift_insert_at_index(P[i], idx, D[i])
4425+
_shift_insert_at_index(I[i], idx, D.shape[0] + n_appended - 1)
4426+
4427+
# Calculate the (top-k) matrix profile values/indidces
4428+
# for the last subsequence
4429+
P[-1] = np.inf
4430+
I[-1] = -1
4431+
for i, d in enumerate(D):
4432+
if d < P[-1, -1]:
4433+
idx = np.searchsorted(P[-1], d, side="right")
4434+
_shift_insert_at_index(P[-1], idx, d)
4435+
_shift_insert_at_index(I[-1], idx, i + n_appended)
4436+
4437+
return

stumpy/stumpi.py

Lines changed: 10 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -354,26 +354,9 @@ def _update_egress(self, t):
354354
if np.any(~self._T_isfinite[-self._m :]):
355355
D[:] = np.inf
356356

357-
core.apply_exclusion_zone(D, D.shape[0] - 1, self._excl_zone, np.inf)
358-
359-
update_idx = np.argwhere(D < self._P[:, -1]).flatten()
360-
for i in update_idx:
361-
idx = np.searchsorted(self._P[i], D[i], side="right")
362-
core._shift_insert_at_index(self._P[i], idx, D[i])
363-
core._shift_insert_at_index(
364-
self._I[i], idx, D.shape[0] + self._n_appended - 1
365-
)
366-
# D.shape[0] is base-1
367-
368-
# Calculate the (top-k) matrix profile values/indices for the last subsequence
369-
# by using its corresponding distance profile `D`
370-
self._P[-1] = np.inf
371-
self._I[-1] = -1
372-
for i, d in enumerate(D):
373-
if d < self._P[-1, -1]:
374-
idx = np.searchsorted(self._P[-1], d, side="right")
375-
core._shift_insert_at_index(self._P[-1], idx, d)
376-
core._shift_insert_at_index(self._I[-1], idx, i + self._n_appended)
357+
core._update_incremental_PI(
358+
D, self._P, self._I, self._excl_zone, n_appended=self._n_appended
359+
)
377360

378361
# All neighbors of the last subsequence are on its left. So, its (top-1)
379362
# matrix profile value/index and its left matrix profile value/index must
@@ -440,30 +423,18 @@ def _update(self, t):
440423
if np.any(~self._T_isfinite[-self._m :]):
441424
D[:] = np.inf
442425

443-
core.apply_exclusion_zone(D, D.shape[0] - 1, self._excl_zone, np.inf)
444-
445-
update_idx = np.argwhere(D[:l] < self._P[:l, -1]).flatten()
446-
for i in update_idx:
447-
idx = np.searchsorted(self._P[i], D[i], side="right")
448-
core._shift_insert_at_index(self._P[i], idx, D[i])
449-
core._shift_insert_at_index(self._I[i], idx, l)
450-
451-
# Calculating top-k matrix profile and (top-1) left matrix profile (and their
452-
# corresponding indices) for new subsequence whose distance profile is `D`
453426
P_new = np.full(self._k, np.inf, dtype=np.float64)
454427
I_new = np.full(self._k, -1, dtype=np.int64)
455-
for i, d in enumerate(D):
456-
if d < P_new[-1]: # maximum value in sorted array P_new
457-
idx = np.searchsorted(P_new, d, side="right")
458-
core._shift_insert_at_index(P_new, idx, d)
459-
core._shift_insert_at_index(I_new, idx, i)
428+
self._P = np.append(self._P, P_new.reshape(1, -1), axis=0)
429+
self._I = np.append(self._I, I_new.reshape(1, -1), axis=0)
430+
431+
core._update_incremental_PI(D, self._P, self._I, self._excl_zone, n_appended=0)
460432

461-
left_I_new = I_new[0]
462-
left_P_new = P_new[0]
433+
left_I_new = self._I[-1, 0]
434+
left_P_new = self._P[-1, 0]
463435

464436
self._T = T_new
465-
self._P = np.append(self._P, P_new.reshape(1, -1), axis=0)
466-
self._I = np.append(self._I, I_new.reshape(1, -1), axis=0)
437+
467438
self._left_P = np.append(self._left_P, left_P_new)
468439
self._left_I = np.append(self._left_I, left_I_new)
469440
self._QT = QT_new

0 commit comments

Comments
 (0)