Skip to content

Commit 2f40bcd

Browse files
committed
merge from main and resolve conflicts
2 parents 44d4ee0 + f112dde commit 2f40bcd

Some content is hidden

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

60 files changed

+667
-259
lines changed

docstring.py

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,17 @@ def get_docstring_args(fd, file_name, func_name, class_name=None):
1717
msg += f"class: {class_name}\n"
1818
msg += f"function/method: {func_name}\n"
1919
raise RuntimeError(msg)
20-
if class_name is None and len(re.findall(r"Returns", docstring)) != 1:
20+
if len(re.findall(r"Returns", docstring)) != 1:
2121
msg = "Missing required 'Returns' section in docstring in \n"
2222
msg += f"file: {file_name}\n"
23+
if class_name is not None:
24+
msg += f"class: {class_name}\n"
2325
msg += f"function/method: {func_name}\n"
2426
raise RuntimeError(msg)
2527

26-
if len(re.findall(r"Returns", docstring)) > 0:
27-
params_section = re.findall(
28-
r"(?<=Parameters)(.*)(?=Returns)", docstring, re.DOTALL
29-
)[0]
30-
else:
31-
params_section = re.findall(r"(?<=Parameters)(.*)", docstring, re.DOTALL)[0]
28+
params_section = re.findall(
29+
r"(?<=Parameters)(.*)(?=Returns)", docstring, re.DOTALL
30+
)[0]
3231

3332
args = re.findall(r"(\w+)\s+\:", params_section)
3433
args = set([a for a in args if a != "i"]) # `i` should never be a parameter
@@ -43,11 +42,11 @@ def get_signature_args(fd):
4342
return set([a.arg for a in fd.args.args if a.arg != "self"])
4443

4544

46-
def check_args(docstring_args, signature_args, file_name, func_name, class_name=None):
45+
def check_args(doc_args, sig_args, file_name, func_name, class_name=None):
4746
"""
4847
Compare docstring arguments and signature argments
4948
"""
50-
diff_args = signature_args.difference(docstring_args)
49+
diff_args = sig_args.difference(doc_args)
5150
if len(diff_args) > 0:
5251
msg = "Found one or more arguments/parameters with missing docstring in \n"
5352
msg += f"file: {file_name}\n"
@@ -57,7 +56,7 @@ def check_args(docstring_args, signature_args, file_name, func_name, class_name=
5756
msg += f"parameter(s): {diff_args}\n"
5857
raise RuntimeError(msg)
5958

60-
diff_args = docstring_args.difference(signature_args)
59+
diff_args = doc_args.difference(sig_args)
6160
if len(diff_args) > 0:
6261
msg = "Found one or more unsupported arguments/parameters with docstring in \n"
6362
msg += f"file: {file_name}\n"

environment.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,4 @@ dependencies:
2929
- polars>=1.14.0
3030
- fftw>=3.3
3131
- pyfftw>=0.15.0
32+
- numba-cuda>=0.24.0

pyproject.toml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,9 @@ isort = ">=5.11.0"
8787
polars = ">=1.14.0"
8888
pip = "*"
8989
lxml = "*"
90-
fftw = ">=3.3"
91-
pyfftw = ">=0.15.0"
90+
fftw = "*"
91+
pyfftw = "*"
92+
numba-cuda = "*"
9293
# readthedocs
9394
sphinx = ">=3.5.3"
9495
pydata-sphinx-theme = "*"
@@ -113,5 +114,5 @@ tbb = ">=2019.5"
113114
tests = "./test.sh"
114115
coverage = "./test.sh coverage"
115116
docs = "cd docs && ./setup.sh"
116-
black = 'black --exclude=".*\.ipynb" --extend-exclude=".venv|.pixi" --diff ./'
117+
black = 'black --exclude=".*\.ipynb" --extend-exclude=".venv|.pixi" ./'
117118
isort = 'isort --profile black --skip .venv --skip .pixi ./'

stumpy/__init__.py

Lines changed: 129 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,63 @@
11
import ast
2+
import importlib
23
import os.path
34
import pathlib
5+
import types
46
from importlib.metadata import distribution
57
from site import getsitepackages
68

79
from numba import cuda
810

911
from . import cache, config
10-
from .aamp import aamp # noqa: F401
11-
from .aamp_mmotifs import aamp_mmotifs # noqa: F401
12-
from .aamp_motifs import aamp_match, aamp_motifs # noqa: F401
13-
from .aamp_ostinato import aamp_ostinato, aamp_ostinatoed # noqa: F401
14-
from .aamp_stimp import aamp_stimp, aamp_stimped # noqa: F401
15-
from .aampdist import aampdist, aampdisted # noqa: F401
16-
from .aampdist_snippets import aampdist_snippets # noqa: F401
17-
from .aamped import aamped # noqa: F401
18-
from .aampi import aampi # noqa: F401
19-
from .chains import allc, atsc # noqa: F401
20-
from .core import mass # noqa: F401
21-
from .floss import floss, fluss # noqa: F401
22-
from .maamp import maamp, maamp_mdl, maamp_subspace # noqa: F401
23-
from .maamped import maamped # noqa: F401
24-
from .mmotifs import mmotifs # noqa: F401
25-
from .motifs import match, motifs # noqa: F401
26-
from .mpdist import mpdist, mpdisted # noqa: F401
27-
from .mstump import mdl, mstump, subspace # noqa: F401
28-
from .mstumped import mstumped # noqa: F401
29-
from .ostinato import ostinato, ostinatoed # noqa: F401
30-
from .scraamp import prescraamp, scraamp # noqa: F401
31-
from .scrump import prescrump, scrump # noqa: F401
32-
from .snippets import snippets # noqa: F401
33-
from .stimp import stimp, stimped # noqa: F401
34-
from .stump import stump # noqa: F401
35-
from .stumped import stumped # noqa: F401
36-
from .stumpi import stumpi # noqa: F401
12+
13+
# Define which functions belong to which module
14+
# Key: function name to expose at top level
15+
# Value: name of the module
16+
_lazy_imports = {
17+
"aamp": "aamp",
18+
"aamp_mmotifs": "aamp_mmotifs",
19+
"aamp_match": "aamp_motifs",
20+
"aamp_motifs": "aamp_motifs",
21+
"aamp_ostinato": "aamp_ostinato",
22+
"aamp_ostinatoed": "aamp_ostinato",
23+
"aamp_stimp": "aamp_stimp",
24+
"aamp_stimped": "aamp_stimp",
25+
"aampdist": "aampdist",
26+
"aampdisted": "aampdist",
27+
"aampdist_snippets": "aampdist_snippets",
28+
"aamped": "aamped",
29+
"aampi": "aampi",
30+
"allc": "chains",
31+
"atsc": "chains",
32+
"mass": "core",
33+
"floss": "floss",
34+
"fluss": "floss",
35+
"maamp": "maamp",
36+
"maamp_mdl": "maamp",
37+
"maamp_subspace": "maamp",
38+
"maamped": "maamped",
39+
"mmotifs": "mmotifs",
40+
"match": "motifs",
41+
"motifs": "motifs",
42+
"mpdist": "mpdist",
43+
"mpdisted": "mpdist",
44+
"mdl": "mstump",
45+
"mstump": "mstump",
46+
"subspace": "mstump",
47+
"mstumped": "mstumped",
48+
"ostinato": "ostinato",
49+
"ostinatoed": "ostinato",
50+
"prescraamp": "scraamp",
51+
"scraamp": "scraamp",
52+
"prescrump": "scrump",
53+
"scrump": "scrump",
54+
"snippets": "snippets",
55+
"stimp": "stimp",
56+
"stimped": "stimp",
57+
"stump": "stump",
58+
"stumped": "stumped",
59+
"stumpi": "stumpi",
60+
}
3761

3862
# Get the default fastmath flags for all njit functions
3963
# and update the _STUMPY_DEFAULTS dictionary
@@ -61,14 +85,18 @@ def _get_fastmath_value(module_name, func_name): # pragma: no cover
6185
config._STUMPY_DEFAULTS[key] = _get_fastmath_value(module_name, func_name)
6286

6387
if cuda.is_available():
64-
from .gpu_aamp import gpu_aamp # noqa: F401
65-
from .gpu_aamp_ostinato import gpu_aamp_ostinato # noqa: F401
66-
from .gpu_aamp_stimp import gpu_aamp_stimp # noqa: F401
67-
from .gpu_aampdist import gpu_aampdist # noqa: F401
68-
from .gpu_mpdist import gpu_mpdist # noqa: F401
69-
from .gpu_ostinato import gpu_ostinato # noqa: F401
70-
from .gpu_stimp import gpu_stimp # noqa: F401
71-
from .gpu_stump import gpu_stump # noqa: F401
88+
_lazy_imports.update(
89+
{
90+
"gpu_aamp": "gpu_aamp",
91+
"gpu_aamp_ostinato": "gpu_aamp_ostinato",
92+
"gpu_aamp_stimp": "gpu_aamp_stimp",
93+
"gpu_aampdist": "gpu_aampdist",
94+
"gpu_mpdist": "gpu_mpdist",
95+
"gpu_ostinato": "gpu_ostinato",
96+
"gpu_stimp": "gpu_stimp",
97+
"gpu_stump": "gpu_stump",
98+
}
99+
)
72100
else: # pragma: no cover
73101
from . import core
74102
from .core import _gpu_aamp_driver_not_found as gpu_aamp # noqa: F401
@@ -220,3 +248,69 @@ def _get_fastmath_value(module_name, func_name): # pragma: no cover
220248
__version__ = "Please install this project with setup.py"
221249
else: # pragma: no cover
222250
__version__ = _dist.version
251+
252+
253+
# PEP 562: module-level __getattr__ for lazy imports
254+
def __getattr__(name): # pragma: no cover
255+
if name in _lazy_imports:
256+
mod_name = _lazy_imports[name]
257+
module = importlib.import_module(f"{__package__}.{mod_name}")
258+
# Retrieve the attribute from the loaded module and cache it
259+
attr = getattr(module, name)
260+
globals()[name] = attr
261+
return attr
262+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
263+
264+
265+
# Ensure that if a module was imported during package import
266+
# (causing the package attribute to point to the module object), we
267+
# replace that entry with the actual attribute (e.g., function) so that
268+
# users get the expected callable at `stumpy.aamp` rather than the module.
269+
for _name, _sub in _lazy_imports.items(): # pragma: no cover
270+
val = globals().get(_name)
271+
if isinstance(val, types.ModuleType):
272+
try:
273+
replacement = getattr(val, _name)
274+
except AttributeError:
275+
# Nothing to do if the module doesn't define the attribute
276+
continue
277+
globals()[_name] = replacement
278+
279+
280+
# Eagerly import exports that would otherwise collide with
281+
# same-named modules. This keeps lazy imports for most names but
282+
# ensures that when a top-level exported name exactly matches its
283+
# module (e.g., `stump` -> `stump.py`), the exported attribute is
284+
# available immediately so REPL completers prefer the callable/class
285+
# instead of the module.
286+
for _name, _sub in _lazy_imports.items(): # pragma: no cover
287+
try:
288+
if _name == _sub:
289+
filepath = pathlib.Path(__file__).parent / f"{_sub}.py"
290+
if filepath.exists():
291+
module = importlib.import_module(f"{__package__}.{_sub}")
292+
try:
293+
globals()[_name] = getattr(module, _name)
294+
except AttributeError:
295+
# If the module doesn't define the attribute, keep it lazy
296+
pass
297+
except Exception:
298+
# Be conservative: don't let eager-import attempts raise during package import
299+
pass
300+
301+
302+
def __dir__(): # pragma: no cover
303+
# Expose lazy names in dir() for discoverability
304+
# Also include __all__ so tools that consult it will see the intended
305+
# top-level exports (this helps some REPL completers prefer the
306+
# callable/class exports over same-named modules).
307+
all_names = list(globals().keys()) + list(_lazy_imports.keys())
308+
all_names += list(globals().get("__all__", []))
309+
return sorted(all_names)
310+
311+
312+
# Make the lazy-exported names explicit for tools that respect __all__.
313+
# This helps REPL tab-completion prefer functions/classes over modules
314+
# when names collide (e.g., `stumpy.stump` should point to the function
315+
# rather than the module during completion).
316+
__all__ = sorted(list(_lazy_imports.keys()))

stumpy/aamp_stimp.py

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,10 @@ def __init__(
189189
The p-norm to apply for computing the Minkowski distance. Minkowski distance
190190
is typically used with `p` being 1 or 2, which correspond to the Manhattan
191191
distance and the Euclidean distance, respectively.
192+
193+
Returns
194+
-------
195+
None
192196
"""
193197
self._T = T.copy()
194198
self._T_min = np.min(self._T[np.isfinite(self._T)])
@@ -228,6 +232,10 @@ def update(self):
228232
----------
229233
None
230234
235+
Returns
236+
-------
237+
None
238+
231239
Notes
232240
-----
233241
`DOI: 10.1109/ICBK.2019.00031 \
@@ -290,7 +298,9 @@ def pan(self, threshold=0.2, normalize=True, contrast=True, binary=True, clip=Tr
290298
291299
Returns
292300
-------
293-
None
301+
PAN : numpy.ndarray
302+
The transformed (i.e., normalized, contrasted, binarized, and repeated)
303+
pan matrix profile
294304
"""
295305
PAN = self._PAN.copy()
296306
# Retrieve the row indices where the matrix profile was actually computed
@@ -334,6 +344,12 @@ def PAN_(self):
334344
Parameters
335345
----------
336346
None
347+
348+
Returns
349+
-------
350+
out : numpy.ndarray
351+
The transformed (i.e., normalized, contrasted, binarized, and repeated) pan
352+
matrix profile
337353
"""
338354
return self.pan().astype(np.float64)
339355

@@ -345,6 +361,12 @@ def M_(self):
345361
Parameters
346362
----------
347363
None
364+
365+
Returns
366+
-------
367+
out : numpy.ndarray
368+
The full list of (breadth first search (level) ordered) subsequence window
369+
sizes
348370
"""
349371
return self._M.astype(np.int64)
350372

@@ -360,7 +382,9 @@ def P_(self):
360382
361383
Returns
362384
-------
363-
None
385+
P : list of numpy.ndarray
386+
A list of all of the raw (i.e., non-transformed) matrix profiles matrix
387+
profile in (breadth first searched (level) ordered)
364388
"""
365389
P = []
366390
for i, idx in enumerate(self._bfs_indices):
@@ -491,6 +515,10 @@ def __init__(
491515
The p-norm to apply for computing the Minkowski distance. Minkowski distance
492516
is typically used with `p` being 1 or 2, which correspond to the Manhattan
493517
distance and the Euclidean distance, respectively.
518+
519+
Returns
520+
-------
521+
None
494522
"""
495523
super().__init__(
496524
T,
@@ -597,6 +625,10 @@ def __init__(
597625
The p-norm to apply for computing the Minkowski distance. Minkowski distance
598626
is typically used with `p` being 1 or 2, which correspond to the Manhattan
599627
distance and the Euclidean distance, respectively.
628+
629+
Returns
630+
-------
631+
None
600632
"""
601633
super().__init__(
602634
T,

0 commit comments

Comments
 (0)