modusa 0.2.22__py3-none-any.whl → 0.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- modusa/.DS_Store +0 -0
- modusa/__init__.py +8 -1
- modusa/decorators.py +4 -4
- modusa/devtools/generate_docs_source.py +96 -0
- modusa/devtools/generate_template.py +13 -13
- modusa/devtools/main.py +4 -3
- modusa/devtools/templates/generator.py +1 -1
- modusa/devtools/templates/io.py +1 -1
- modusa/devtools/templates/{signal.py → model.py} +18 -11
- modusa/devtools/templates/plugin.py +1 -1
- modusa/devtools/templates/test.py +2 -3
- modusa/devtools/templates/{engine.py → tool.py} +3 -8
- modusa/generators/__init__.py +9 -1
- modusa/generators/audio.py +188 -0
- modusa/generators/audio_waveforms.py +22 -13
- modusa/generators/base.py +1 -1
- modusa/generators/ftds.py +298 -0
- modusa/generators/s1d.py +270 -0
- modusa/generators/s2d.py +300 -0
- modusa/generators/s_ax.py +102 -0
- modusa/generators/t_ax.py +64 -0
- modusa/generators/tds.py +267 -0
- modusa/main.py +0 -30
- modusa/models/__init__.py +14 -0
- modusa/models/__pycache__/signal1D.cpython-312.pyc.4443461152 +0 -0
- modusa/models/audio.py +90 -0
- modusa/models/base.py +70 -0
- modusa/models/data.py +457 -0
- modusa/models/ftds.py +584 -0
- modusa/models/s1d.py +578 -0
- modusa/models/s2d.py +619 -0
- modusa/models/s_ax.py +448 -0
- modusa/models/t_ax.py +335 -0
- modusa/models/tds.py +465 -0
- modusa/plugins/__init__.py +3 -1
- modusa/tmp.py +98 -0
- modusa/tools/__init__.py +7 -0
- modusa/tools/audio_converter.py +73 -0
- modusa/tools/audio_loader.py +90 -0
- modusa/tools/audio_player.py +89 -0
- modusa/tools/base.py +43 -0
- modusa/tools/math_ops.py +335 -0
- modusa/tools/plotter.py +351 -0
- modusa/tools/youtube_downloader.py +72 -0
- modusa/utils/excp.py +15 -42
- modusa/utils/np_func_cat.py +44 -0
- modusa/utils/plot.py +142 -0
- {modusa-0.2.22.dist-info → modusa-0.3.dist-info}/METADATA +5 -16
- modusa-0.3.dist-info/RECORD +60 -0
- modusa/engines/.DS_Store +0 -0
- modusa/engines/__init__.py +0 -3
- modusa/engines/base.py +0 -14
- modusa/io/__init__.py +0 -9
- modusa/io/audio_converter.py +0 -76
- modusa/io/audio_loader.py +0 -214
- modusa/io/audio_player.py +0 -72
- modusa/io/base.py +0 -43
- modusa/io/plotter.py +0 -430
- modusa/io/youtube_downloader.py +0 -139
- modusa/signals/__init__.py +0 -7
- modusa/signals/audio_signal.py +0 -483
- modusa/signals/base.py +0 -34
- modusa/signals/frequency_domain_signal.py +0 -329
- modusa/signals/signal_ops.py +0 -158
- modusa/signals/spectrogram.py +0 -465
- modusa/signals/time_domain_signal.py +0 -309
- modusa-0.2.22.dist-info/RECORD +0 -47
- {modusa-0.2.22.dist-info → modusa-0.3.dist-info}/WHEEL +0 -0
- {modusa-0.2.22.dist-info → modusa-0.3.dist-info}/entry_points.txt +0 -0
- {modusa-0.2.22.dist-info → modusa-0.3.dist-info}/licenses/LICENSE.md +0 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
import soundfile as sf
|
|
5
|
+
from scipy.signal import resample
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
import tempfile
|
|
8
|
+
from scipy.signal import resample
|
|
9
|
+
from .youtube_downloader import download
|
|
10
|
+
from .audio_converter import convert
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def load(path, sr=None):
|
|
14
|
+
"""
|
|
15
|
+
Loads audio file from various sources.
|
|
16
|
+
|
|
17
|
+
.. code-block:: python
|
|
18
|
+
|
|
19
|
+
import modusa as ms
|
|
20
|
+
audio_fp = ms.load(
|
|
21
|
+
"https://www.youtube.com/watch?v=lIpw9-Y_N0g",
|
|
22
|
+
sr = None)
|
|
23
|
+
|
|
24
|
+
Parameters
|
|
25
|
+
----------
|
|
26
|
+
path: str
|
|
27
|
+
- Path to the audio
|
|
28
|
+
- Youtube URL
|
|
29
|
+
sr: int
|
|
30
|
+
- Sampling rate to load the audio in.
|
|
31
|
+
|
|
32
|
+
Return
|
|
33
|
+
------
|
|
34
|
+
np.ndarray
|
|
35
|
+
- Audio signal.
|
|
36
|
+
int
|
|
37
|
+
- Sampling rate of the loaded audio signal.
|
|
38
|
+
title
|
|
39
|
+
- Title of the loaded audio.
|
|
40
|
+
- Filename without extension or YouTube title.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
# Check if the path is YouTube
|
|
44
|
+
if ".youtube." in str(path):
|
|
45
|
+
# Download the audio in temp directory using tempfile module
|
|
46
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
47
|
+
# Download
|
|
48
|
+
audio_fp: Path = download(url=path, content_type="audio", output_dir=Path(tmpdir))
|
|
49
|
+
|
|
50
|
+
# Convert the audio to ".wav" form for loading
|
|
51
|
+
wav_audio_fp: Path = convert(inp_audio_fp=audio_fp, output_audio_fp=audio_fp.with_suffix(".wav"))
|
|
52
|
+
|
|
53
|
+
# Load the audio in memory
|
|
54
|
+
audio_data, audio_sr = sf.read(wav_audio_fp)
|
|
55
|
+
title = audio_fp.stem
|
|
56
|
+
|
|
57
|
+
# Convert to mono if it's multi-channel
|
|
58
|
+
if audio_data.ndim > 1:
|
|
59
|
+
audio_data = audio_data.mean(axis=1)
|
|
60
|
+
|
|
61
|
+
# Resample if needed
|
|
62
|
+
if sr is not None:
|
|
63
|
+
if audio_sr != sr:
|
|
64
|
+
n_samples = int(len(audio_data) * sr / audio_sr)
|
|
65
|
+
audio_data = resample(audio_data, n_samples)
|
|
66
|
+
audio_sr = sr
|
|
67
|
+
|
|
68
|
+
else:
|
|
69
|
+
# Check if the file exists
|
|
70
|
+
fp = Path(path)
|
|
71
|
+
|
|
72
|
+
if not fp.exists():
|
|
73
|
+
raise FileNotFoundError(f"{path} does not exist.")
|
|
74
|
+
|
|
75
|
+
# Load the audio in memory
|
|
76
|
+
audio_data, audio_sr = sf.read(fp)
|
|
77
|
+
title = fp.stem
|
|
78
|
+
|
|
79
|
+
# Convert to mono if it's multi-channel
|
|
80
|
+
if audio_data.ndim > 1:
|
|
81
|
+
audio_data = audio_data.mean(axis=1)
|
|
82
|
+
|
|
83
|
+
# Resample if needed
|
|
84
|
+
if sr is not None:
|
|
85
|
+
if audio_sr != sr:
|
|
86
|
+
n_samples = int(len(audio_data) * sr / audio_sr)
|
|
87
|
+
audio_data = resample(audio_data, n_samples)
|
|
88
|
+
audio_sr = sr
|
|
89
|
+
|
|
90
|
+
return audio_data, audio_sr, title
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from IPython.display import display, HTML, Audio
|
|
5
|
+
|
|
6
|
+
def play(y: np.ndarray, sr: float, t0: float = 0.0, regions = None, title = None) -> None:
|
|
7
|
+
"""
|
|
8
|
+
Plays audio clips for given regions in Jupyter Notebooks.
|
|
9
|
+
|
|
10
|
+
Parameters
|
|
11
|
+
----------
|
|
12
|
+
y : np.ndarray
|
|
13
|
+
- Audio data.
|
|
14
|
+
- Mono (1D) numpy array.
|
|
15
|
+
sr: float
|
|
16
|
+
- Sampling rate of the audio.
|
|
17
|
+
t0: float
|
|
18
|
+
- Starting timestamp, incase the audio is cropped
|
|
19
|
+
- Default: 0.0 → Starts from 0.0 sec
|
|
20
|
+
regions : list[tuple[float, float, str]] | tuple[float, float, str] | None
|
|
21
|
+
- Regions to extract and play (in sec), e.g. [(0, 10.2, "tag")]
|
|
22
|
+
- If there is only one region, a tuple should also work. e.g. (0, 10.2, "tag")
|
|
23
|
+
- Default: None → The entire song is selected.
|
|
24
|
+
title : str | None
|
|
25
|
+
- Title to display above audio players.
|
|
26
|
+
|
|
27
|
+
Returns
|
|
28
|
+
-------
|
|
29
|
+
None
|
|
30
|
+
"""
|
|
31
|
+
if title:
|
|
32
|
+
display(HTML(f"<h4>{title}</h4>"))
|
|
33
|
+
|
|
34
|
+
clip_tags = []
|
|
35
|
+
timings = []
|
|
36
|
+
players = []
|
|
37
|
+
|
|
38
|
+
if isinstance(regions, tuple): regions = [regions] # (10, 20, "Region 1") -> [(10, 20, "Region 1")]
|
|
39
|
+
|
|
40
|
+
if regions is not None:
|
|
41
|
+
for region in regions:
|
|
42
|
+
assert len(region) == 3
|
|
43
|
+
|
|
44
|
+
start_sec = region[0] - t0
|
|
45
|
+
end_sec = region[1] - t0
|
|
46
|
+
tag = region[2]
|
|
47
|
+
|
|
48
|
+
start_sample, end_sample = int(start_sec * sr), int(end_sec * sr)
|
|
49
|
+
clip = y[start_sample: end_sample]
|
|
50
|
+
audio_player = Audio(data=clip, rate=sr)._repr_html_()
|
|
51
|
+
|
|
52
|
+
clip_tags.append(f"<td style='text-align:center; border-right:1px solid #ccc; padding:6px;'>{tag}</td>")
|
|
53
|
+
timings.append(f"<td style='text-align:center; border-right:1px solid #ccc; padding:6px;'>{start_sec:.2f}s → {end_sec:.2f}s</td>")
|
|
54
|
+
players.append(f"<td style='padding:6px;'>{audio_player}</td>")
|
|
55
|
+
else:
|
|
56
|
+
audio_player = Audio(data=y, rate=sr)._repr_html_()
|
|
57
|
+
|
|
58
|
+
start_sec = t0
|
|
59
|
+
end_sec = t0 + y.shape[0] / sr
|
|
60
|
+
|
|
61
|
+
clip_tags.append(f"<td style='text-align:center; border-right:1px solid #ccc; padding:6px;'>1</td>")
|
|
62
|
+
timings.append(f"<td style='text-align:center; border-right:1px solid #ccc; padding:6px;'>{start_sec:.2f}s → {end_sec:.2f}s</td>")
|
|
63
|
+
players.append(f"<td style='padding:6px;'>{audio_player}</td>")
|
|
64
|
+
|
|
65
|
+
# Wrap rows in a table with border
|
|
66
|
+
table_html = f"""
|
|
67
|
+
<div style="display:inline-block; border:1px solid #ccc; border-radius:6px; overflow:hidden;">
|
|
68
|
+
<table style="border-collapse:collapse;">
|
|
69
|
+
<tr style="background-color:#f2f2f2;">
|
|
70
|
+
<th style="text-align:left; padding:6px 12px;">Clip</th>
|
|
71
|
+
{''.join(clip_tags)}
|
|
72
|
+
</tr>
|
|
73
|
+
<tr style="background-color:#fcfcfc;">
|
|
74
|
+
<th style="text-align:left; padding:6px 12px;">Timing</th>
|
|
75
|
+
{''.join(timings)}
|
|
76
|
+
</tr>
|
|
77
|
+
<tr>
|
|
78
|
+
<th style="text-align:left; padding:6px 12px;">Player</th>
|
|
79
|
+
{''.join(players)}
|
|
80
|
+
</tr>
|
|
81
|
+
</table>
|
|
82
|
+
</div>
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
return HTML(table_html)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
|
modusa/tools/base.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
|
|
5
|
+
class ModusaTool(ABC):
|
|
6
|
+
"""
|
|
7
|
+
Base class for all tool: youtube downloader, audio converter, filter.
|
|
8
|
+
|
|
9
|
+
>>> modusa-dev create io
|
|
10
|
+
|
|
11
|
+
.. code-block:: python
|
|
12
|
+
|
|
13
|
+
# General template of a subclass of ModusaTool
|
|
14
|
+
from modusa.tools.base import ModusaTool
|
|
15
|
+
|
|
16
|
+
class MyCustomIOClass(ModusaIO):
|
|
17
|
+
#--------Meta Information----------
|
|
18
|
+
_name = "My Custom Tool"
|
|
19
|
+
_description = "My custom class for Tool."
|
|
20
|
+
_author_name = "Ankit Anand"
|
|
21
|
+
_author_email = "ankit0.anand0@gmail.com"
|
|
22
|
+
_created_at = "2025-07-06"
|
|
23
|
+
#----------------------------------
|
|
24
|
+
|
|
25
|
+
@staticmethod
|
|
26
|
+
def do_something():
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
Note
|
|
31
|
+
----
|
|
32
|
+
- This class is intended to be subclassed by any tool built for the modusa framework.
|
|
33
|
+
- In order to create a tool, you can use modusa-dev CLI to generate a template.
|
|
34
|
+
- It is recommended to treat subclasses of ModusaTool as namespaces and define @staticmethods with control parameters, rather than using instance-level __init__ methods.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
#--------Meta Information----------
|
|
38
|
+
_name: str = "Modusa Tool"
|
|
39
|
+
_description: str = "Base class for any tool in the Modusa framework."
|
|
40
|
+
_author_name = "Ankit Anand"
|
|
41
|
+
_author_email = "ankit0.anand0@gmail.com"
|
|
42
|
+
_created_at = "2025-07-11"
|
|
43
|
+
#----------------------------------
|
modusa/tools/math_ops.py
ADDED
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
from modusa import excp
|
|
4
|
+
from modusa.tools.base import ModusaTool
|
|
5
|
+
from typing import Any
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
class MathOps(ModusaTool):
|
|
9
|
+
"""
|
|
10
|
+
Performs arithmetic and NumPy-style ops.
|
|
11
|
+
|
|
12
|
+
Note
|
|
13
|
+
----
|
|
14
|
+
- Shape-changing operations like reshape, transpose, etc. are not yet supported. Use only element-wise or aggregation ops for now.
|
|
15
|
+
- Index alignment must be handled carefully in future extensions.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def _axes_match(a1: tuple[np.ndarray, ...], a2: tuple[np.ndarray, ...]) -> bool:
|
|
19
|
+
"""
|
|
20
|
+
To check if two axes are same.
|
|
21
|
+
|
|
22
|
+
It checks the length of the axes and the corresponding values.
|
|
23
|
+
"""
|
|
24
|
+
if len(a1) != len(a2):
|
|
25
|
+
return False
|
|
26
|
+
return all(np.allclose(x, y, atol=1e-8) for x, y in zip(a1, a2))
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
#----------------------------------
|
|
30
|
+
# To handle basic element wise
|
|
31
|
+
# math operations like
|
|
32
|
+
# +, -, *, **, / ...
|
|
33
|
+
#----------------------------------
|
|
34
|
+
|
|
35
|
+
@staticmethod
|
|
36
|
+
def add(a: Any, b: Any) -> np.generic | np.ndarray:
|
|
37
|
+
try:
|
|
38
|
+
result = np.add(a, b)
|
|
39
|
+
except Exception as e:
|
|
40
|
+
raise excp.InputError(f"`a` and `b` can't be added") from e
|
|
41
|
+
|
|
42
|
+
if isinstance(a, str) and isinstance(b, str): # numpy actually concatenates, we do not want that
|
|
43
|
+
raise excp.InputError(f"`a` and `b` can't be added")
|
|
44
|
+
return result
|
|
45
|
+
|
|
46
|
+
@staticmethod
|
|
47
|
+
def subtract(a: Any, b: Any) -> np.generic | np.ndarray:
|
|
48
|
+
try:
|
|
49
|
+
result = np.subtract(a, b)
|
|
50
|
+
except Exception as e:
|
|
51
|
+
raise excp.InputError(f"`a` and `b` can't be subtracted") from e
|
|
52
|
+
return result
|
|
53
|
+
|
|
54
|
+
@staticmethod
|
|
55
|
+
def multiply(a: Any, b: Any) -> np.generic | np.ndarray:
|
|
56
|
+
try:
|
|
57
|
+
result = np.multiply(a, b)
|
|
58
|
+
except Exception as e:
|
|
59
|
+
raise excp.InputError(f"`a` and `b` can't be multiplied") from e
|
|
60
|
+
|
|
61
|
+
if isinstance(a, str) and isinstance(b, str): # numpy actually concatenates, we do not want that
|
|
62
|
+
raise excp.InputError(f"`a` and `b` can't be multiplied")
|
|
63
|
+
return result
|
|
64
|
+
|
|
65
|
+
@staticmethod
|
|
66
|
+
def divide(a: Any, b: Any) -> np.generic | np.ndarray:
|
|
67
|
+
try:
|
|
68
|
+
result = np.divide(a, b)
|
|
69
|
+
except Exception as e:
|
|
70
|
+
raise excp.InputError(f"`a` and `b` can't be divided") from e
|
|
71
|
+
return result
|
|
72
|
+
|
|
73
|
+
@staticmethod
|
|
74
|
+
def power(a: Any, b: Any) -> np.generic | np.ndarray:
|
|
75
|
+
try:
|
|
76
|
+
result = np.power(a, b)
|
|
77
|
+
except Exception as e:
|
|
78
|
+
raise excp.InputError(f"`a` can't be exponentiated with `b`") from e
|
|
79
|
+
return result
|
|
80
|
+
|
|
81
|
+
@staticmethod
|
|
82
|
+
def floor_divide(a: Any, b: Any) -> np.generic | np.ndarray:
|
|
83
|
+
try:
|
|
84
|
+
result = np.floor_divide(a, b)
|
|
85
|
+
except Exception as e:
|
|
86
|
+
raise excp.InputError(f"`a` can't be floor divided by `b`") from e
|
|
87
|
+
return result
|
|
88
|
+
|
|
89
|
+
#----------------------------------
|
|
90
|
+
# To handle numpy aggregator ops
|
|
91
|
+
#----------------------------------
|
|
92
|
+
@staticmethod
|
|
93
|
+
def mean(a: Any, axis: int | None = None) -> np.generic | np.ndarray:
|
|
94
|
+
try:
|
|
95
|
+
result = np.mean(a, axis=axis)
|
|
96
|
+
except Exception as e:
|
|
97
|
+
raise excp.InputError(f"can't find mean for `a`") from e
|
|
98
|
+
return result
|
|
99
|
+
|
|
100
|
+
@staticmethod
|
|
101
|
+
def std(a: Any, axis: int | None = None) -> np.generic | np.ndarray:
|
|
102
|
+
""""""
|
|
103
|
+
try:
|
|
104
|
+
result = np.std(a, axis=axis)
|
|
105
|
+
except Exception as e:
|
|
106
|
+
raise excp.InputError(f"can't find std for `a`") from e
|
|
107
|
+
return result
|
|
108
|
+
|
|
109
|
+
@staticmethod
|
|
110
|
+
def min(a: Any, axis: int | None = None) -> np.generic | np.ndarray:
|
|
111
|
+
try:
|
|
112
|
+
result = np.min(a, axis=axis)
|
|
113
|
+
except Exception as e:
|
|
114
|
+
raise excp.InputError(f"can't find min for `a`") from e
|
|
115
|
+
return result
|
|
116
|
+
|
|
117
|
+
@staticmethod
|
|
118
|
+
def max(a: Any, axis: int | None = None) -> np.generic | np.ndarray:
|
|
119
|
+
try:
|
|
120
|
+
result = np.max(a, axis=axis)
|
|
121
|
+
except Exception as e:
|
|
122
|
+
raise excp.InputError(f"can't find max for `a`") from e
|
|
123
|
+
return result
|
|
124
|
+
|
|
125
|
+
@staticmethod
|
|
126
|
+
def sum(a: Any, axis: int | None = None) -> np.generic | np.ndarray:
|
|
127
|
+
try:
|
|
128
|
+
result = np.sum(a, axis=axis)
|
|
129
|
+
except Exception as e:
|
|
130
|
+
raise excp.InputError(f"can't find sum for `a`") from e
|
|
131
|
+
return result
|
|
132
|
+
|
|
133
|
+
#----------------------------------
|
|
134
|
+
# To handle numpy ops where the
|
|
135
|
+
# shapes are unaltered
|
|
136
|
+
# sin, cos, exp, log, ...
|
|
137
|
+
#----------------------------------
|
|
138
|
+
|
|
139
|
+
@staticmethod
|
|
140
|
+
def sin(a: Any) -> np.generic | np.ndarray:
|
|
141
|
+
try:
|
|
142
|
+
result = np.sin(a)
|
|
143
|
+
except Exception as e:
|
|
144
|
+
raise excp.InputError(f"can't find sin for `a`") from e
|
|
145
|
+
return result
|
|
146
|
+
|
|
147
|
+
@staticmethod
|
|
148
|
+
def cos(a: Any) -> np.generic | np.ndarray:
|
|
149
|
+
try:
|
|
150
|
+
result = np.cos(a)
|
|
151
|
+
except Exception as e:
|
|
152
|
+
raise excp.InputError(f"can't find cos for `a`") from e
|
|
153
|
+
return result
|
|
154
|
+
|
|
155
|
+
@staticmethod
|
|
156
|
+
def tanh(a: Any) -> np.generic | np.ndarray:
|
|
157
|
+
try:
|
|
158
|
+
result = np.tanh(a)
|
|
159
|
+
except Exception as e:
|
|
160
|
+
raise excp.InputError(f"can't find tanh for `a`") from e
|
|
161
|
+
return result
|
|
162
|
+
|
|
163
|
+
@staticmethod
|
|
164
|
+
def exp(a: Any) -> np.generic | np.ndarray:
|
|
165
|
+
try:
|
|
166
|
+
result = np.exp(a)
|
|
167
|
+
except Exception as e:
|
|
168
|
+
raise excp.InputError(f"can't find exp for `a`") from e
|
|
169
|
+
return result
|
|
170
|
+
|
|
171
|
+
@staticmethod
|
|
172
|
+
def log(a: Any) -> np.generic | np.ndarray:
|
|
173
|
+
try:
|
|
174
|
+
result = np.log(a)
|
|
175
|
+
except Exception as e:
|
|
176
|
+
raise excp.InputError(f"can't find log for `a`") from e
|
|
177
|
+
return result
|
|
178
|
+
|
|
179
|
+
@staticmethod
|
|
180
|
+
def log10(a: Any) -> np.generic | np.ndarray:
|
|
181
|
+
try:
|
|
182
|
+
result = np.log10(a)
|
|
183
|
+
except Exception as e:
|
|
184
|
+
raise excp.InputError(f"can't find log10 for `a`") from e
|
|
185
|
+
return result
|
|
186
|
+
|
|
187
|
+
@staticmethod
|
|
188
|
+
def log2(a: Any) -> np.generic | np.ndarray:
|
|
189
|
+
try:
|
|
190
|
+
result = np.log2(a)
|
|
191
|
+
except Exception as e:
|
|
192
|
+
raise excp.InputError(f"can't find log2 for `a`") from e
|
|
193
|
+
return result
|
|
194
|
+
|
|
195
|
+
@staticmethod
|
|
196
|
+
def log1p(a: Any) -> np.generic | np.ndarray:
|
|
197
|
+
try:
|
|
198
|
+
result = np.log1p(a)
|
|
199
|
+
except Exception as e:
|
|
200
|
+
raise excp.InputError(f"can't find log1p for `a`") from e
|
|
201
|
+
return result
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
@staticmethod
|
|
205
|
+
def sqrt(a: Any) -> np.generic | np.ndarray:
|
|
206
|
+
try:
|
|
207
|
+
result = np.sqrt(a)
|
|
208
|
+
except Exception as e:
|
|
209
|
+
raise excp.InputError(f"can't find sqrt for `a`") from e
|
|
210
|
+
return result
|
|
211
|
+
|
|
212
|
+
@staticmethod
|
|
213
|
+
def abs(a: Any) -> np.generic | np.ndarray:
|
|
214
|
+
try:
|
|
215
|
+
result = np.abs(a)
|
|
216
|
+
except Exception as e:
|
|
217
|
+
raise excp.InputError(f"can't find abs for `a`") from e
|
|
218
|
+
return result
|
|
219
|
+
|
|
220
|
+
@staticmethod
|
|
221
|
+
def floor(a: Any) -> np.generic | np.ndarray:
|
|
222
|
+
try:
|
|
223
|
+
result = np.floor(a)
|
|
224
|
+
except Exception as e:
|
|
225
|
+
raise excp.InputError(f"can't find floor for `a`") from e
|
|
226
|
+
return result
|
|
227
|
+
|
|
228
|
+
@staticmethod
|
|
229
|
+
def ceil(a: Any) -> np.generic | np.ndarray:
|
|
230
|
+
try:
|
|
231
|
+
result = np.ceil(a)
|
|
232
|
+
except Exception as e:
|
|
233
|
+
raise excp.InputError(f"can't find ceil for `a`") from e
|
|
234
|
+
return result
|
|
235
|
+
|
|
236
|
+
@staticmethod
|
|
237
|
+
def round(a: Any) -> np.generic | np.ndarray:
|
|
238
|
+
try:
|
|
239
|
+
result = np.round(a)
|
|
240
|
+
except Exception as e:
|
|
241
|
+
raise excp.InputError(f"can't find round for `a`") from e
|
|
242
|
+
return result
|
|
243
|
+
|
|
244
|
+
#------------------------------------
|
|
245
|
+
# TODO: Add shape-changing ops like
|
|
246
|
+
# reshape, transpose, squeeze later
|
|
247
|
+
#------------------------------------
|
|
248
|
+
|
|
249
|
+
@staticmethod
|
|
250
|
+
def reshape(a: Any, shape: int | tuple[int, ...]) -> np.ndarray:
|
|
251
|
+
try:
|
|
252
|
+
result = np.reshape(a, shape=shape)
|
|
253
|
+
except Exception as e:
|
|
254
|
+
raise excp.InputError(f"can't reshape `a`") from e
|
|
255
|
+
return result
|
|
256
|
+
|
|
257
|
+
#------------------------------------
|
|
258
|
+
# Complex numbers operations
|
|
259
|
+
#------------------------------------
|
|
260
|
+
|
|
261
|
+
@staticmethod
|
|
262
|
+
def real(a: Any) -> np.ndarray:
|
|
263
|
+
try:
|
|
264
|
+
result = np.real(a)
|
|
265
|
+
except Exception as e:
|
|
266
|
+
raise excp.InputError(f"can't find real for `a`") from e
|
|
267
|
+
return result
|
|
268
|
+
|
|
269
|
+
@staticmethod
|
|
270
|
+
def imag(a: Any) -> np.ndarray:
|
|
271
|
+
try:
|
|
272
|
+
result = np.imag(a)
|
|
273
|
+
except Exception as e:
|
|
274
|
+
raise excp.InputError(f"can't find imag for `a`") from e
|
|
275
|
+
return result
|
|
276
|
+
|
|
277
|
+
@staticmethod
|
|
278
|
+
def angle(a: Any) -> np.ndarray:
|
|
279
|
+
try:
|
|
280
|
+
result = np.angle(a)
|
|
281
|
+
except Exception as e:
|
|
282
|
+
raise excp.InputError(f"can't find angle for `a`") from e
|
|
283
|
+
return result
|
|
284
|
+
|
|
285
|
+
#------------------------------------
|
|
286
|
+
# Comparison
|
|
287
|
+
#------------------------------------
|
|
288
|
+
|
|
289
|
+
@staticmethod
|
|
290
|
+
def lt(a: Any, b: Any) -> np.ndarray:
|
|
291
|
+
try:
|
|
292
|
+
mask = a < b
|
|
293
|
+
except Exception as e:
|
|
294
|
+
raise excp.InputError(f"`a` and `b` can't be compared") from e
|
|
295
|
+
return mask
|
|
296
|
+
|
|
297
|
+
@staticmethod
|
|
298
|
+
def le(a: Any, b: Any) -> np.ndarray:
|
|
299
|
+
try:
|
|
300
|
+
mask = a <= b
|
|
301
|
+
except Exception as e:
|
|
302
|
+
raise excp.InputError(f"`a` and `b` can't be compared") from e
|
|
303
|
+
return mask
|
|
304
|
+
|
|
305
|
+
@staticmethod
|
|
306
|
+
def gt(a: Any, b: Any) -> np.ndarray:
|
|
307
|
+
try:
|
|
308
|
+
mask = a > b
|
|
309
|
+
except Exception as e:
|
|
310
|
+
raise excp.InputError(f"`a` and `b` can't be compared") from e
|
|
311
|
+
return mask
|
|
312
|
+
|
|
313
|
+
@staticmethod
|
|
314
|
+
def ge(a: Any, b: Any) -> np.ndarray:
|
|
315
|
+
try:
|
|
316
|
+
mask = a >= b
|
|
317
|
+
except Exception as e:
|
|
318
|
+
raise excp.InputError(f"`a` and `b` can't be compared") from e
|
|
319
|
+
return mask
|
|
320
|
+
|
|
321
|
+
@staticmethod
|
|
322
|
+
def eq(a: Any, b: Any) -> np.ndarray:
|
|
323
|
+
try:
|
|
324
|
+
mask = a == b
|
|
325
|
+
except Exception as e:
|
|
326
|
+
raise excp.InputError(f"`a` and `b` can't be compared") from e
|
|
327
|
+
return mask
|
|
328
|
+
|
|
329
|
+
@staticmethod
|
|
330
|
+
def ne(a: Any, b: Any) -> np.ndarray:
|
|
331
|
+
try:
|
|
332
|
+
mask = a != b
|
|
333
|
+
except Exception as e:
|
|
334
|
+
raise excp.InputError(f"`a` and `b` can't be compared") from e
|
|
335
|
+
return mask
|