sktime-cython 0.1.0__tar.gz
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.
- sktime_cython-0.1.0/LICENSE +30 -0
- sktime_cython-0.1.0/MANIFEST.in +7 -0
- sktime_cython-0.1.0/PKG-INFO +185 -0
- sktime_cython-0.1.0/README.md +114 -0
- sktime_cython-0.1.0/pyproject.toml +120 -0
- sktime_cython-0.1.0/setup.cfg +4 -0
- sktime_cython-0.1.0/setup.py +32 -0
- sktime_cython-0.1.0/sktime_cython/__init__.py +6 -0
- sktime_cython-0.1.0/sktime_cython/transformations/__init__.py +1 -0
- sktime_cython-0.1.0/sktime_cython/transformations/rocket/__init__.py +8 -0
- sktime_cython-0.1.0/sktime_cython/transformations/rocket/_minirocket.py +201 -0
- sktime_cython-0.1.0/sktime_cython/transformations/rocket/_minirocket_multivariate_cython.pyi +21 -0
- sktime_cython-0.1.0/sktime_cython/transformations/rocket/_minirocket_multivariate_cython.pyx +289 -0
- sktime_cython-0.1.0/sktime_cython/transformations/rocket/tests/__init__.py +1 -0
- sktime_cython-0.1.0/sktime_cython/transformations/rocket/tests/test_minirocket_cython.py +83 -0
- sktime_cython-0.1.0/sktime_cython.egg-info/PKG-INFO +185 -0
- sktime_cython-0.1.0/sktime_cython.egg-info/SOURCES.txt +18 -0
- sktime_cython-0.1.0/sktime_cython.egg-info/dependency_links.txt +1 -0
- sktime_cython-0.1.0/sktime_cython.egg-info/requires.txt +9 -0
- sktime_cython-0.1.0/sktime_cython.egg-info/top_level.txt +2 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2019 - present, The sktime developers.
|
|
4
|
+
|
|
5
|
+
All rights reserved.
|
|
6
|
+
|
|
7
|
+
Redistribution and use in source and binary forms, with or without
|
|
8
|
+
modification, are permitted provided that the following conditions are met:
|
|
9
|
+
|
|
10
|
+
* Redistributions of source code must retain the above copyright notice, this
|
|
11
|
+
list of conditions and the following disclaimer.
|
|
12
|
+
|
|
13
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
|
14
|
+
this list of conditions and the following disclaimer in the documentation
|
|
15
|
+
and/or other materials provided with the distribution.
|
|
16
|
+
|
|
17
|
+
* Neither the name of the copyright holder nor the names of its
|
|
18
|
+
contributors may be used to endorse or promote products derived from
|
|
19
|
+
this software without specific prior written permission.
|
|
20
|
+
|
|
21
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
22
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
23
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
24
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
25
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
26
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
27
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
28
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
29
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
30
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Cython sources must ship in the sdist so it can build on the user's machine.
|
|
2
|
+
recursive-include sktime_cython *.pyx *.pxd *.pyi
|
|
3
|
+
include LICENSE README.md pyproject.toml setup.py
|
|
4
|
+
|
|
5
|
+
# Never ship build artifacts: the .c is regenerated from .pyx at build time.
|
|
6
|
+
recursive-exclude sktime_cython *.c *.so *.pyd
|
|
7
|
+
global-exclude __pycache__ *.py[cod]
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sktime-cython
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Cython extensions for sktime
|
|
5
|
+
Author-email: sktime developers <sktime.toolbox@gmail.com>
|
|
6
|
+
Maintainer-email: sktime developers <sktime.toolbox@gmail.com>
|
|
7
|
+
License: BSD 3-Clause License
|
|
8
|
+
|
|
9
|
+
Copyright (c) 2019 - present, The sktime developers.
|
|
10
|
+
|
|
11
|
+
All rights reserved.
|
|
12
|
+
|
|
13
|
+
Redistribution and use in source and binary forms, with or without
|
|
14
|
+
modification, are permitted provided that the following conditions are met:
|
|
15
|
+
|
|
16
|
+
* Redistributions of source code must retain the above copyright notice, this
|
|
17
|
+
list of conditions and the following disclaimer.
|
|
18
|
+
|
|
19
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
|
20
|
+
this list of conditions and the following disclaimer in the documentation
|
|
21
|
+
and/or other materials provided with the distribution.
|
|
22
|
+
|
|
23
|
+
* Neither the name of the copyright holder nor the names of its
|
|
24
|
+
contributors may be used to endorse or promote products derived from
|
|
25
|
+
this software without specific prior written permission.
|
|
26
|
+
|
|
27
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
28
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
29
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
30
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
31
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
32
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
33
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
34
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
35
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
36
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
37
|
+
|
|
38
|
+
Project-URL: Homepage, https://github.com/sktime/sktime-cython
|
|
39
|
+
Project-URL: Repository, https://github.com/sktime/sktime-cython
|
|
40
|
+
Keywords: cython,machine-learning,scikit-learn,time-series,time-series-analysis
|
|
41
|
+
Classifier: Intended Audience :: Developers
|
|
42
|
+
Classifier: Intended Audience :: Science/Research
|
|
43
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
44
|
+
Classifier: Operating System :: MacOS
|
|
45
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
46
|
+
Classifier: Operating System :: POSIX
|
|
47
|
+
Classifier: Operating System :: Unix
|
|
48
|
+
Classifier: Programming Language :: Cython
|
|
49
|
+
Classifier: Programming Language :: Python
|
|
50
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
51
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
52
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
53
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
54
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
55
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
56
|
+
Classifier: Topic :: Scientific/Engineering
|
|
57
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
58
|
+
Classifier: Topic :: Software Development
|
|
59
|
+
Requires-Python: <3.15,>=3.10
|
|
60
|
+
Description-Content-Type: text/markdown
|
|
61
|
+
License-File: LICENSE
|
|
62
|
+
Requires-Dist: numpy<2.5,>=1.21
|
|
63
|
+
Provides-Extra: dev
|
|
64
|
+
Requires-Dist: pre-commit; extra == "dev"
|
|
65
|
+
Requires-Dist: pytest; extra == "dev"
|
|
66
|
+
Requires-Dist: pytest-timeout; extra == "dev"
|
|
67
|
+
Requires-Dist: pytest-xdist; extra == "dev"
|
|
68
|
+
Requires-Dist: sktime; extra == "dev"
|
|
69
|
+
Requires-Dist: numba; extra == "dev"
|
|
70
|
+
Dynamic: license-file
|
|
71
|
+
|
|
72
|
+
# sktime-cython
|
|
73
|
+
|
|
74
|
+
Cython-compiled estimators for [sktime](https://github.com/sktime/sktime).
|
|
75
|
+
|
|
76
|
+
This package hosts ahead-of-time compiled implementations of sktime algorithms,
|
|
77
|
+
isolating the C-compilation and binary-wheel complexity from the main `sktime`
|
|
78
|
+
package. Estimators here expose a plain numpy-in/numpy-out compute layer with
|
|
79
|
+
**no sktime runtime dependency**; `sktime` keeps thin `BaseTransformer` /
|
|
80
|
+
`BaseEstimator` wrappers that delegate to this package.
|
|
81
|
+
|
|
82
|
+
Why a separate package: compiled extensions need a C toolchain, per-platform
|
|
83
|
+
wheels, and `cibuildwheel` release machinery. Keeping that here lets `sktime`
|
|
84
|
+
stay pure-Python while still offering compiled, numba-free fast paths.
|
|
85
|
+
|
|
86
|
+
## Estimators
|
|
87
|
+
|
|
88
|
+
| Estimator | Compute API | Notes |
|
|
89
|
+
|-----------|-------------|-------|
|
|
90
|
+
| Multivariate MiniRocket | `sktime_cython.fit` / `transform` | numba-free; equivalent to `MiniRocketMultivariate`, no JIT warmup |
|
|
91
|
+
|
|
92
|
+
More estimators are added as `.pyx` kernels under `sktime_cython/_cython/` with
|
|
93
|
+
a numpy compute layer alongside.
|
|
94
|
+
|
|
95
|
+
Each estimator lives in its own submodule (nothing is re-exported at the top
|
|
96
|
+
level, so estimators never collide on common names like `fit`/`transform`):
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
import numpy as np
|
|
100
|
+
from sktime_cython.minirocket import fit, transform
|
|
101
|
+
|
|
102
|
+
X = np.random.rand(100, 6, 500).astype("float32")
|
|
103
|
+
params = fit(X, num_kernels=10_000, random_state=42)
|
|
104
|
+
features = transform(X, params, n_jobs=-1) # GIL-released kernel, real threads
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Runtime dependency: numpy only.
|
|
108
|
+
|
|
109
|
+
## Development
|
|
110
|
+
|
|
111
|
+
Requires a C compiler and Python 3.10+. Run from the project root.
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
# editable install with the dev extra (pulls sktime + numba for equivalence
|
|
115
|
+
# tests, plus pre-commit); compiles the Cython extensions via build isolation
|
|
116
|
+
uv pip install -e ".[dev]" # or: pip install -e ".[dev]"
|
|
117
|
+
|
|
118
|
+
# install the git pre-commit hooks ONCE — this is what stops CI lint failures
|
|
119
|
+
pre-commit install
|
|
120
|
+
|
|
121
|
+
# run the tests
|
|
122
|
+
python -m pytest sktime_cython -v
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
After editing a `.pyx`, recompile with `pip install -e .` again (or `make build`).
|
|
126
|
+
|
|
127
|
+
`make` shortcuts (auto-detect `uv`, falling back to `pip`/`python`):
|
|
128
|
+
`make install`, `make build`, `make test`, `make clean`.
|
|
129
|
+
|
|
130
|
+
## Before you push (avoid CI failures)
|
|
131
|
+
|
|
132
|
+
CI runs the pre-commit hooks and **fails if they reformat anything**. Running
|
|
133
|
+
`pre-commit install` (above) auto-runs them on every `git commit`. To check the
|
|
134
|
+
whole tree on demand:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
pre-commit run --all-files
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
This runs `ruff check` (lint) and `ruff format`. If `ruff format` reports "files
|
|
141
|
+
were modified", it already fixed them — `git add` the changes and commit again.
|
|
142
|
+
|
|
143
|
+
## CI workflows
|
|
144
|
+
|
|
145
|
+
- **`test.yml`** (push / PR to `main`): a `code-quality` job running the
|
|
146
|
+
pre-commit hooks, plus a matrix that builds the Cython extensions and runs
|
|
147
|
+
pytest across Python 3.10–3.14 on Linux, macOS, and Windows.
|
|
148
|
+
- **`release.yml`** (on GitHub release): builds binary wheels with
|
|
149
|
+
[`cibuildwheel`](https://cibuildwheel.pypa.io/) for all platforms, builds an
|
|
150
|
+
sdist (with `.pyx` sources), and publishes to PyPI via trusted publishing.
|
|
151
|
+
|
|
152
|
+
## Adding an estimator
|
|
153
|
+
|
|
154
|
+
1. Drop the kernel `.pyx` (and optional `.pyi` stub) in a subfolder of the package.
|
|
155
|
+
The package structure mirrors that of `sktime`, files should be added paralleling
|
|
156
|
+
its primary import location in `sktime`
|
|
157
|
+
2. Register the kernel as an `Extension` in `setup.py`.
|
|
158
|
+
3. Add a numpy compute-layer module `sktime_cython/<name>.py` exposing the
|
|
159
|
+
estimator's public functions. Keep them in the submodule — do not re-export
|
|
160
|
+
at the top level (avoids name collisions across estimators).
|
|
161
|
+
4. Add tests in a subfolder `tests`, in the same folder as the kernel.
|
|
162
|
+
|
|
163
|
+
## Package structure
|
|
164
|
+
|
|
165
|
+
Cython kernels and python layers should be added in a parallel location as in the
|
|
166
|
+
`sktime` package.
|
|
167
|
+
|
|
168
|
+
Example for minirocket:
|
|
169
|
+
|
|
170
|
+
```
|
|
171
|
+
sktime_cython/
|
|
172
|
+
transformations/
|
|
173
|
+
rocket/
|
|
174
|
+
__init__.py # public compute-layer exports
|
|
175
|
+
_minirocket.py # numpy fit/transform layer
|
|
176
|
+
_minirocket_multivariate_cython.pyx # compiled kernels
|
|
177
|
+
_minirocket_multivariate_cython.pyi # type stubs
|
|
178
|
+
tests/
|
|
179
|
+
__init__.py
|
|
180
|
+
test_minirocket.py # tests
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## License
|
|
184
|
+
|
|
185
|
+
BSD 3-Clause License — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# sktime-cython
|
|
2
|
+
|
|
3
|
+
Cython-compiled estimators for [sktime](https://github.com/sktime/sktime).
|
|
4
|
+
|
|
5
|
+
This package hosts ahead-of-time compiled implementations of sktime algorithms,
|
|
6
|
+
isolating the C-compilation and binary-wheel complexity from the main `sktime`
|
|
7
|
+
package. Estimators here expose a plain numpy-in/numpy-out compute layer with
|
|
8
|
+
**no sktime runtime dependency**; `sktime` keeps thin `BaseTransformer` /
|
|
9
|
+
`BaseEstimator` wrappers that delegate to this package.
|
|
10
|
+
|
|
11
|
+
Why a separate package: compiled extensions need a C toolchain, per-platform
|
|
12
|
+
wheels, and `cibuildwheel` release machinery. Keeping that here lets `sktime`
|
|
13
|
+
stay pure-Python while still offering compiled, numba-free fast paths.
|
|
14
|
+
|
|
15
|
+
## Estimators
|
|
16
|
+
|
|
17
|
+
| Estimator | Compute API | Notes |
|
|
18
|
+
|-----------|-------------|-------|
|
|
19
|
+
| Multivariate MiniRocket | `sktime_cython.fit` / `transform` | numba-free; equivalent to `MiniRocketMultivariate`, no JIT warmup |
|
|
20
|
+
|
|
21
|
+
More estimators are added as `.pyx` kernels under `sktime_cython/_cython/` with
|
|
22
|
+
a numpy compute layer alongside.
|
|
23
|
+
|
|
24
|
+
Each estimator lives in its own submodule (nothing is re-exported at the top
|
|
25
|
+
level, so estimators never collide on common names like `fit`/`transform`):
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
import numpy as np
|
|
29
|
+
from sktime_cython.minirocket import fit, transform
|
|
30
|
+
|
|
31
|
+
X = np.random.rand(100, 6, 500).astype("float32")
|
|
32
|
+
params = fit(X, num_kernels=10_000, random_state=42)
|
|
33
|
+
features = transform(X, params, n_jobs=-1) # GIL-released kernel, real threads
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Runtime dependency: numpy only.
|
|
37
|
+
|
|
38
|
+
## Development
|
|
39
|
+
|
|
40
|
+
Requires a C compiler and Python 3.10+. Run from the project root.
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# editable install with the dev extra (pulls sktime + numba for equivalence
|
|
44
|
+
# tests, plus pre-commit); compiles the Cython extensions via build isolation
|
|
45
|
+
uv pip install -e ".[dev]" # or: pip install -e ".[dev]"
|
|
46
|
+
|
|
47
|
+
# install the git pre-commit hooks ONCE — this is what stops CI lint failures
|
|
48
|
+
pre-commit install
|
|
49
|
+
|
|
50
|
+
# run the tests
|
|
51
|
+
python -m pytest sktime_cython -v
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
After editing a `.pyx`, recompile with `pip install -e .` again (or `make build`).
|
|
55
|
+
|
|
56
|
+
`make` shortcuts (auto-detect `uv`, falling back to `pip`/`python`):
|
|
57
|
+
`make install`, `make build`, `make test`, `make clean`.
|
|
58
|
+
|
|
59
|
+
## Before you push (avoid CI failures)
|
|
60
|
+
|
|
61
|
+
CI runs the pre-commit hooks and **fails if they reformat anything**. Running
|
|
62
|
+
`pre-commit install` (above) auto-runs them on every `git commit`. To check the
|
|
63
|
+
whole tree on demand:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
pre-commit run --all-files
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
This runs `ruff check` (lint) and `ruff format`. If `ruff format` reports "files
|
|
70
|
+
were modified", it already fixed them — `git add` the changes and commit again.
|
|
71
|
+
|
|
72
|
+
## CI workflows
|
|
73
|
+
|
|
74
|
+
- **`test.yml`** (push / PR to `main`): a `code-quality` job running the
|
|
75
|
+
pre-commit hooks, plus a matrix that builds the Cython extensions and runs
|
|
76
|
+
pytest across Python 3.10–3.14 on Linux, macOS, and Windows.
|
|
77
|
+
- **`release.yml`** (on GitHub release): builds binary wheels with
|
|
78
|
+
[`cibuildwheel`](https://cibuildwheel.pypa.io/) for all platforms, builds an
|
|
79
|
+
sdist (with `.pyx` sources), and publishes to PyPI via trusted publishing.
|
|
80
|
+
|
|
81
|
+
## Adding an estimator
|
|
82
|
+
|
|
83
|
+
1. Drop the kernel `.pyx` (and optional `.pyi` stub) in a subfolder of the package.
|
|
84
|
+
The package structure mirrors that of `sktime`, files should be added paralleling
|
|
85
|
+
its primary import location in `sktime`
|
|
86
|
+
2. Register the kernel as an `Extension` in `setup.py`.
|
|
87
|
+
3. Add a numpy compute-layer module `sktime_cython/<name>.py` exposing the
|
|
88
|
+
estimator's public functions. Keep them in the submodule — do not re-export
|
|
89
|
+
at the top level (avoids name collisions across estimators).
|
|
90
|
+
4. Add tests in a subfolder `tests`, in the same folder as the kernel.
|
|
91
|
+
|
|
92
|
+
## Package structure
|
|
93
|
+
|
|
94
|
+
Cython kernels and python layers should be added in a parallel location as in the
|
|
95
|
+
`sktime` package.
|
|
96
|
+
|
|
97
|
+
Example for minirocket:
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
sktime_cython/
|
|
101
|
+
transformations/
|
|
102
|
+
rocket/
|
|
103
|
+
__init__.py # public compute-layer exports
|
|
104
|
+
_minirocket.py # numpy fit/transform layer
|
|
105
|
+
_minirocket_multivariate_cython.pyx # compiled kernels
|
|
106
|
+
_minirocket_multivariate_cython.pyi # type stubs
|
|
107
|
+
tests/
|
|
108
|
+
__init__.py
|
|
109
|
+
test_minirocket.py # tests
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## License
|
|
113
|
+
|
|
114
|
+
BSD 3-Clause License — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "sktime-cython"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Cython extensions for sktime"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
keywords = [
|
|
7
|
+
"cython",
|
|
8
|
+
"machine-learning",
|
|
9
|
+
"scikit-learn",
|
|
10
|
+
"time-series",
|
|
11
|
+
"time-series-analysis",
|
|
12
|
+
]
|
|
13
|
+
license = { file = "LICENSE" }
|
|
14
|
+
maintainers = [
|
|
15
|
+
{ name = "sktime developers", email = "sktime.toolbox@gmail.com" },
|
|
16
|
+
]
|
|
17
|
+
authors = [
|
|
18
|
+
{ name = "sktime developers", email = "sktime.toolbox@gmail.com" },
|
|
19
|
+
]
|
|
20
|
+
requires-python = ">=3.10,<3.15"
|
|
21
|
+
classifiers = [
|
|
22
|
+
"Intended Audience :: Developers",
|
|
23
|
+
"Intended Audience :: Science/Research",
|
|
24
|
+
"License :: OSI Approved :: BSD License",
|
|
25
|
+
"Operating System :: MacOS",
|
|
26
|
+
"Operating System :: Microsoft :: Windows",
|
|
27
|
+
"Operating System :: POSIX",
|
|
28
|
+
"Operating System :: Unix",
|
|
29
|
+
"Programming Language :: Cython",
|
|
30
|
+
"Programming Language :: Python",
|
|
31
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
32
|
+
"Programming Language :: Python :: 3.10",
|
|
33
|
+
"Programming Language :: Python :: 3.11",
|
|
34
|
+
"Programming Language :: Python :: 3.12",
|
|
35
|
+
"Programming Language :: Python :: 3.13",
|
|
36
|
+
"Programming Language :: Python :: 3.14",
|
|
37
|
+
"Topic :: Scientific/Engineering",
|
|
38
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
39
|
+
"Topic :: Software Development",
|
|
40
|
+
]
|
|
41
|
+
dependencies = [
|
|
42
|
+
"numpy>=1.21,<2.5",
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
[project.optional-dependencies]
|
|
46
|
+
dev = [
|
|
47
|
+
"pre-commit",
|
|
48
|
+
"pytest",
|
|
49
|
+
"pytest-timeout",
|
|
50
|
+
"pytest-xdist",
|
|
51
|
+
# sktime + numba power the equivalence test (Cython vs numba MiniRocket).
|
|
52
|
+
# Dev-only: runtime deps stay numpy-only.
|
|
53
|
+
"sktime",
|
|
54
|
+
"numba",
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
[project.urls]
|
|
58
|
+
Homepage = "https://github.com/sktime/sktime-cython"
|
|
59
|
+
Repository = "https://github.com/sktime/sktime-cython"
|
|
60
|
+
|
|
61
|
+
[build-system]
|
|
62
|
+
requires = [
|
|
63
|
+
"Cython>=3.0",
|
|
64
|
+
"numpy>=1.21",
|
|
65
|
+
"setuptools>=70.1",
|
|
66
|
+
]
|
|
67
|
+
build-backend = "setuptools.build_meta"
|
|
68
|
+
|
|
69
|
+
[tool.setuptools.packages.find]
|
|
70
|
+
exclude = ["tests", "tests.*"]
|
|
71
|
+
|
|
72
|
+
[tool.cibuildwheel]
|
|
73
|
+
# CPython only; PyPy has no upstream numpy wheels for this matrix, so skip it.
|
|
74
|
+
# requires-python in [project] already bounds the CPython versions built.
|
|
75
|
+
skip = ["pp*"]
|
|
76
|
+
# Run tests after each wheel build to verify the Cython extensions actually
|
|
77
|
+
# work. --pyargs imports the installed wheel by name instead of collecting from
|
|
78
|
+
# the source tree, so this exercises the built artifact.
|
|
79
|
+
test-requires = ["pytest"]
|
|
80
|
+
test-command = "python -m pytest --pyargs sktime_cython -v"
|
|
81
|
+
|
|
82
|
+
[tool.ruff]
|
|
83
|
+
line-length = 88
|
|
84
|
+
target-version = "py310"
|
|
85
|
+
|
|
86
|
+
[tool.ruff.lint]
|
|
87
|
+
select = [
|
|
88
|
+
"D",
|
|
89
|
+
"E",
|
|
90
|
+
"W",
|
|
91
|
+
"F",
|
|
92
|
+
"S",
|
|
93
|
+
"UP",
|
|
94
|
+
"I",
|
|
95
|
+
"C4",
|
|
96
|
+
]
|
|
97
|
+
ignore = [
|
|
98
|
+
"E203",
|
|
99
|
+
"E402",
|
|
100
|
+
"E731",
|
|
101
|
+
"S101",
|
|
102
|
+
"RUF100",
|
|
103
|
+
"E741",
|
|
104
|
+
]
|
|
105
|
+
|
|
106
|
+
[tool.ruff.lint.pydocstyle]
|
|
107
|
+
convention = "numpy"
|
|
108
|
+
|
|
109
|
+
[tool.ruff.lint.per-file-ignores]
|
|
110
|
+
"**/__init__.py" = [
|
|
111
|
+
"F401",
|
|
112
|
+
]
|
|
113
|
+
"**/tests/**" = [
|
|
114
|
+
"D",
|
|
115
|
+
"S605",
|
|
116
|
+
"S607",
|
|
117
|
+
"PT004",
|
|
118
|
+
"PT011",
|
|
119
|
+
"PT012",
|
|
120
|
+
]
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Setup script for sktime-cython Cython extensions."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
from Cython.Build import cythonize
|
|
7
|
+
from setuptools import Extension, setup
|
|
8
|
+
|
|
9
|
+
# fast-math is the bulk of the speedup vs numba; flags are platform-specific.
|
|
10
|
+
if sys.platform == "win32":
|
|
11
|
+
_fast = ["/O2", "/fp:fast"]
|
|
12
|
+
else:
|
|
13
|
+
_fast = ["-O3", "-ffast-math"]
|
|
14
|
+
|
|
15
|
+
extensions = [
|
|
16
|
+
Extension(
|
|
17
|
+
"sktime_cython.transformations.rocket._minirocket_multivariate_cython",
|
|
18
|
+
sources=[
|
|
19
|
+
"sktime_cython/transformations/rocket/_minirocket_multivariate_cython.pyx"
|
|
20
|
+
],
|
|
21
|
+
include_dirs=[np.get_include()],
|
|
22
|
+
define_macros=[("NPY_NO_DEPRECATED_API", "NPY_1_7_API_VERSION")],
|
|
23
|
+
extra_compile_args=_fast,
|
|
24
|
+
),
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
setup(
|
|
28
|
+
ext_modules=cythonize(
|
|
29
|
+
extensions,
|
|
30
|
+
compiler_directives={"language_level": "3"},
|
|
31
|
+
),
|
|
32
|
+
)
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"""sktime-cython - Cython extensions for sktime.
|
|
2
|
+
|
|
3
|
+
Each estimator lives in its own submodule and exposes its own compute API,
|
|
4
|
+
e.g. ``from sktime_cython.minirocket import fit, transform``. Nothing is
|
|
5
|
+
re-exported at the top level, so estimators never collide on common names.
|
|
6
|
+
"""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Transformations."""
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"""Numba-free MiniRocket multivariate transform (pure-numpy + Cython core).
|
|
2
|
+
|
|
3
|
+
Compute layer with no sktime dependency: numpy arrays in, numpy arrays out.
|
|
4
|
+
``fit`` returns a parameter tuple; ``transform`` applies it. An sktime
|
|
5
|
+
``BaseTransformer`` wrapper delegates to these two functions.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import multiprocessing
|
|
9
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
10
|
+
|
|
11
|
+
import numpy as np
|
|
12
|
+
|
|
13
|
+
from sktime_cython.transformations.rocket import _minirocket_multivariate_cython as _cy
|
|
14
|
+
|
|
15
|
+
__all__ = ["rocket_fit", "rocket_transform"]
|
|
16
|
+
|
|
17
|
+
_NUM_KERNELS = 84
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _fit_dilations(n_timepoints, num_features, max_dilations_per_kernel):
|
|
21
|
+
"""Dilation schedule (pure numpy, copied from sktime numba module)."""
|
|
22
|
+
num_kernels = _NUM_KERNELS
|
|
23
|
+
if num_features < num_kernels:
|
|
24
|
+
num_features = num_kernels
|
|
25
|
+
|
|
26
|
+
num_features_per_kernel = num_features // num_kernels
|
|
27
|
+
true_max_dilations_per_kernel = min(
|
|
28
|
+
num_features_per_kernel, max_dilations_per_kernel
|
|
29
|
+
)
|
|
30
|
+
multiplier = num_features_per_kernel / true_max_dilations_per_kernel
|
|
31
|
+
|
|
32
|
+
max_exponent = np.log2((n_timepoints - 1) / (9 - 1))
|
|
33
|
+
dilations, num_features_per_dilation = np.unique(
|
|
34
|
+
np.logspace(0, max_exponent, true_max_dilations_per_kernel, base=2).astype(
|
|
35
|
+
np.int32
|
|
36
|
+
),
|
|
37
|
+
return_counts=True,
|
|
38
|
+
)
|
|
39
|
+
num_features_per_dilation = (num_features_per_dilation * multiplier).astype(
|
|
40
|
+
np.int32
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
remainder = num_features_per_kernel - np.sum(num_features_per_dilation)
|
|
44
|
+
i = 0
|
|
45
|
+
while remainder > 0:
|
|
46
|
+
num_features_per_dilation[i] += 1
|
|
47
|
+
remainder -= 1
|
|
48
|
+
i = (i + 1) % len(num_features_per_dilation)
|
|
49
|
+
|
|
50
|
+
return dilations, num_features_per_dilation
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _quantiles(n):
|
|
54
|
+
"""Evenly-spaced low-discrepancy quantile points (copied from sktime)."""
|
|
55
|
+
return np.array(
|
|
56
|
+
[(_ * ((np.sqrt(5) + 1) / 2)) % 1 for _ in range(1, n + 1)], dtype=np.float32
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def rocket_fit(X, num_kernels=10_000, max_dilations_per_kernel=32, random_state=None):
|
|
61
|
+
"""Fit dilations, channel selections, and biases.
|
|
62
|
+
|
|
63
|
+
Parameters
|
|
64
|
+
----------
|
|
65
|
+
X : 3D np.ndarray, shape (n_instances, n_columns, n_timepoints)
|
|
66
|
+
panel of time series; cast to float32 internally.
|
|
67
|
+
num_kernels : int, default=10000
|
|
68
|
+
number of kernels; rounded down to a multiple of 84 (min 84).
|
|
69
|
+
max_dilations_per_kernel : int, default=32
|
|
70
|
+
maximum number of dilations per kernel.
|
|
71
|
+
random_state : int or None, default=None
|
|
72
|
+
seed for reproducibility.
|
|
73
|
+
|
|
74
|
+
Returns
|
|
75
|
+
-------
|
|
76
|
+
parameters : tuple of np.ndarray
|
|
77
|
+
(num_channels_per_combination, channel_indices, dilations,
|
|
78
|
+
num_features_per_dilation, biases), ready to pass to ``transform``.
|
|
79
|
+
"""
|
|
80
|
+
if random_state is not None and not isinstance(random_state, (int, np.integer)):
|
|
81
|
+
raise ValueError(
|
|
82
|
+
f"random_state must be int or None, but found {type(random_state)}"
|
|
83
|
+
)
|
|
84
|
+
seed = np.int32(random_state) if random_state is not None else None
|
|
85
|
+
|
|
86
|
+
X = np.ascontiguousarray(X, dtype=np.float32)
|
|
87
|
+
n_instances, n_columns, n_timepoints = X.shape
|
|
88
|
+
if n_timepoints < 9:
|
|
89
|
+
raise ValueError(
|
|
90
|
+
f"n_timepoints must be >= 9, but found {n_timepoints};"
|
|
91
|
+
" zero pad shorter series so that n_timepoints == 9"
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
if seed is not None:
|
|
95
|
+
np.random.seed(seed)
|
|
96
|
+
|
|
97
|
+
num_kernels_ = _NUM_KERNELS
|
|
98
|
+
dilations, num_features_per_dilation = _fit_dilations(
|
|
99
|
+
n_timepoints, num_kernels, max_dilations_per_kernel
|
|
100
|
+
)
|
|
101
|
+
num_features_per_kernel = np.sum(num_features_per_dilation)
|
|
102
|
+
quantiles = _quantiles(num_kernels_ * num_features_per_kernel)
|
|
103
|
+
|
|
104
|
+
num_dilations = len(dilations)
|
|
105
|
+
num_combinations = num_kernels_ * num_dilations
|
|
106
|
+
|
|
107
|
+
max_num_channels = min(n_columns, 9)
|
|
108
|
+
max_exponent = np.log2(max_num_channels + 1)
|
|
109
|
+
|
|
110
|
+
num_channels_per_combination = (
|
|
111
|
+
2 ** np.random.uniform(0, max_exponent, num_combinations)
|
|
112
|
+
).astype(np.int32)
|
|
113
|
+
|
|
114
|
+
channel_indices = np.zeros(num_channels_per_combination.sum(), dtype=np.int32)
|
|
115
|
+
num_channels_start = 0
|
|
116
|
+
for combination_index in range(num_combinations):
|
|
117
|
+
n_this = num_channels_per_combination[combination_index]
|
|
118
|
+
num_channels_end = num_channels_start + n_this
|
|
119
|
+
channel_indices[num_channels_start:num_channels_end] = np.random.choice(
|
|
120
|
+
n_columns, n_this, replace=False
|
|
121
|
+
)
|
|
122
|
+
num_channels_start = num_channels_end
|
|
123
|
+
|
|
124
|
+
# biases: re-seed (matching numba _fit_biases_multi), draw one instance
|
|
125
|
+
# index per combination, build C in Cython, then quantile per combination.
|
|
126
|
+
if seed is not None:
|
|
127
|
+
np.random.seed(seed)
|
|
128
|
+
instance_indices = np.array(
|
|
129
|
+
[np.random.randint(n_instances) for _ in range(num_combinations)],
|
|
130
|
+
dtype=np.int32,
|
|
131
|
+
)
|
|
132
|
+
C = _cy.fit_biases(
|
|
133
|
+
X,
|
|
134
|
+
num_channels_per_combination,
|
|
135
|
+
channel_indices,
|
|
136
|
+
dilations.astype(np.int32),
|
|
137
|
+
num_features_per_dilation.astype(np.int32),
|
|
138
|
+
instance_indices,
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
biases = np.zeros(num_kernels_ * int(num_features_per_kernel), dtype=np.float32)
|
|
142
|
+
feature_index_start = 0
|
|
143
|
+
combination_index = 0
|
|
144
|
+
for dilation_index in range(num_dilations):
|
|
145
|
+
nfd = num_features_per_dilation[dilation_index]
|
|
146
|
+
for _kernel_index in range(num_kernels_):
|
|
147
|
+
feature_index_end = feature_index_start + nfd
|
|
148
|
+
biases[feature_index_start:feature_index_end] = np.quantile(
|
|
149
|
+
C[combination_index],
|
|
150
|
+
quantiles[feature_index_start:feature_index_end],
|
|
151
|
+
).astype(np.float32)
|
|
152
|
+
feature_index_start = feature_index_end
|
|
153
|
+
combination_index += 1
|
|
154
|
+
|
|
155
|
+
return (
|
|
156
|
+
num_channels_per_combination,
|
|
157
|
+
channel_indices,
|
|
158
|
+
dilations.astype(np.int32),
|
|
159
|
+
num_features_per_dilation.astype(np.int32),
|
|
160
|
+
biases,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def rocket_transform(X, parameters, n_jobs=1):
|
|
165
|
+
"""Apply a fitted MiniRocket transform.
|
|
166
|
+
|
|
167
|
+
Parameters
|
|
168
|
+
----------
|
|
169
|
+
X : 3D np.ndarray, shape (n_instances, n_columns, n_timepoints)
|
|
170
|
+
panel of time series; cast to contiguous float32 internally.
|
|
171
|
+
parameters : tuple
|
|
172
|
+
the tuple returned by ``fit``.
|
|
173
|
+
n_jobs : int, default=1
|
|
174
|
+
threads for the GIL-releasing Cython kernel over disjoint instance
|
|
175
|
+
chunks. ``-1`` (or out of range) uses all processors.
|
|
176
|
+
|
|
177
|
+
Returns
|
|
178
|
+
-------
|
|
179
|
+
np.ndarray, shape (n_instances, n_features), float32
|
|
180
|
+
"""
|
|
181
|
+
X = np.ascontiguousarray(X, dtype=np.float32)
|
|
182
|
+
n_instances = X.shape[0]
|
|
183
|
+
|
|
184
|
+
if n_jobs < 1 or n_jobs > multiprocessing.cpu_count():
|
|
185
|
+
n_jobs = multiprocessing.cpu_count()
|
|
186
|
+
n_jobs = min(n_jobs, n_instances)
|
|
187
|
+
|
|
188
|
+
if n_jobs <= 1:
|
|
189
|
+
return _cy.transform(X, *parameters)
|
|
190
|
+
|
|
191
|
+
# the Cython kernel releases the GIL, so plain threads run truly in
|
|
192
|
+
# parallel across disjoint instance chunks.
|
|
193
|
+
bounds = np.linspace(0, n_instances, n_jobs + 1).astype(int)
|
|
194
|
+
chunks = [
|
|
195
|
+
np.ascontiguousarray(X[bounds[i] : bounds[i + 1]])
|
|
196
|
+
for i in range(n_jobs)
|
|
197
|
+
if bounds[i + 1] > bounds[i]
|
|
198
|
+
]
|
|
199
|
+
with ThreadPoolExecutor(max_workers=n_jobs) as ex:
|
|
200
|
+
parts = list(ex.map(lambda c: _cy.transform(c, *parameters), chunks))
|
|
201
|
+
return np.vstack(parts)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Type stubs for the compiled MiniRocketMultivariate Cython kernels."""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from numpy.typing import NDArray
|
|
5
|
+
|
|
6
|
+
def transform(
|
|
7
|
+
X: NDArray[np.float32],
|
|
8
|
+
num_channels_per_combination: NDArray[np.int32],
|
|
9
|
+
channel_indices: NDArray[np.int32],
|
|
10
|
+
dilations: NDArray[np.int32],
|
|
11
|
+
num_features_per_dilation: NDArray[np.int32],
|
|
12
|
+
biases: NDArray[np.float32],
|
|
13
|
+
) -> NDArray[np.float32]: ...
|
|
14
|
+
def fit_biases(
|
|
15
|
+
X: NDArray[np.float32],
|
|
16
|
+
num_channels_per_combination: NDArray[np.int32],
|
|
17
|
+
channel_indices: NDArray[np.int32],
|
|
18
|
+
dilations: NDArray[np.int32],
|
|
19
|
+
num_features_per_dilation: NDArray[np.int32],
|
|
20
|
+
instance_indices: NDArray[np.int32],
|
|
21
|
+
) -> NDArray[np.float32]: ...
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
# cython: language_level=3, boundscheck=False, wraparound=False, cdivision=True
|
|
2
|
+
"""Cython MiniRocketMultivariate kernels.
|
|
3
|
+
|
|
4
|
+
Ahead-of-time compiled ports of sktime's ``_minirocket_multi_numba`` kernels
|
|
5
|
+
(``_fit_biases_multi`` and ``_transform_multi``). Same math, no numba JIT
|
|
6
|
+
warmup. Both consume float32/int32 arrays produced by the pure-numpy fit
|
|
7
|
+
scaffolding in ``MiniRocketMultivariateCython`` and are numerically equivalent
|
|
8
|
+
to the numba implementation (verified against it as groundtruth in tests).
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import numpy as np
|
|
12
|
+
|
|
13
|
+
cimport numpy as cnp
|
|
14
|
+
from libc.stdlib cimport free, malloc
|
|
15
|
+
|
|
16
|
+
cnp.import_array()
|
|
17
|
+
|
|
18
|
+
# combinations(range(9), 3) -> 84 kernels, flattened to 252 int32.
|
|
19
|
+
cdef int[252] _IDX
|
|
20
|
+
cdef int _fill_idx():
|
|
21
|
+
cdef int n = 0, i, j, k
|
|
22
|
+
for i in range(9):
|
|
23
|
+
for j in range(i + 1, 9):
|
|
24
|
+
for k in range(j + 1, 9):
|
|
25
|
+
_IDX[3 * n] = i
|
|
26
|
+
_IDX[3 * n + 1] = j
|
|
27
|
+
_IDX[3 * n + 2] = k
|
|
28
|
+
n += 1
|
|
29
|
+
return n
|
|
30
|
+
cdef int _NUM_KERNELS = _fill_idx()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def transform(
|
|
34
|
+
cnp.ndarray[cnp.float32_t, ndim=3, mode="c"] X,
|
|
35
|
+
int[::1] num_channels_per_combination,
|
|
36
|
+
int[::1] channel_indices,
|
|
37
|
+
int[::1] dilations,
|
|
38
|
+
int[::1] num_features_per_dilation,
|
|
39
|
+
float[::1] biases,
|
|
40
|
+
):
|
|
41
|
+
"""Port of ``_transform_multi``. X is (n_instances, n_columns, n_timepoints)."""
|
|
42
|
+
cdef int n_instances = X.shape[0]
|
|
43
|
+
cdef int n_columns = X.shape[1]
|
|
44
|
+
cdef int n_timepoints = X.shape[2]
|
|
45
|
+
cdef float[:, :, ::1] Xv = X
|
|
46
|
+
|
|
47
|
+
cdef int num_kernels = _NUM_KERNELS
|
|
48
|
+
cdef int num_dilations = dilations.shape[0]
|
|
49
|
+
|
|
50
|
+
cdef int total_fpd = 0
|
|
51
|
+
cdef int d
|
|
52
|
+
for d in range(num_dilations):
|
|
53
|
+
total_fpd += num_features_per_dilation[d]
|
|
54
|
+
cdef int num_features = num_kernels * total_fpd
|
|
55
|
+
|
|
56
|
+
cdef cnp.ndarray[cnp.float32_t, ndim=2, mode="c"] features = np.zeros(
|
|
57
|
+
(n_instances, num_features), dtype=np.float32
|
|
58
|
+
)
|
|
59
|
+
cdef float[:, ::1] feat = features
|
|
60
|
+
|
|
61
|
+
cdef int csize = n_columns * n_timepoints
|
|
62
|
+
# Per-instance work buffers (single-threaded; reused across dilations).
|
|
63
|
+
cdef float* C_alpha = <float*>malloc(csize * sizeof(float))
|
|
64
|
+
cdef float* C_gamma = <float*>malloc(9 * csize * sizeof(float))
|
|
65
|
+
cdef float* C = <float*>malloc(n_timepoints * sizeof(float))
|
|
66
|
+
if C_alpha == NULL or C_gamma == NULL or C == NULL:
|
|
67
|
+
free(C_alpha); free(C_gamma); free(C)
|
|
68
|
+
raise MemoryError()
|
|
69
|
+
|
|
70
|
+
cdef int ex, di, ki, g, c, t, ch
|
|
71
|
+
cdef int padding, padding0, padding1, nfd, dilation
|
|
72
|
+
cdef int start, end, base, combination_index
|
|
73
|
+
cdef int fis, fie, ncc, ncs, nce
|
|
74
|
+
cdef int i0, i1, i2, fc, n_valid, lo, hi
|
|
75
|
+
cdef float bias, x, count
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
with nogil:
|
|
79
|
+
for ex in range(n_instances):
|
|
80
|
+
fis = 0
|
|
81
|
+
ncs = 0
|
|
82
|
+
combination_index = 0
|
|
83
|
+
for di in range(num_dilations):
|
|
84
|
+
padding0 = di % 2
|
|
85
|
+
dilation = dilations[di]
|
|
86
|
+
padding = ((9 - 1) * dilation) // 2
|
|
87
|
+
nfd = num_features_per_dilation[di]
|
|
88
|
+
|
|
89
|
+
# C_alpha = A = -X ; C_gamma = 0 ; C_gamma[4] = G = 3X
|
|
90
|
+
for c in range(n_columns):
|
|
91
|
+
base = c * n_timepoints
|
|
92
|
+
for t in range(n_timepoints):
|
|
93
|
+
x = Xv[ex, c, t]
|
|
94
|
+
C_alpha[base + t] = -x
|
|
95
|
+
C_gamma[4 * csize + base + t] = x + x + x
|
|
96
|
+
for g in range(9):
|
|
97
|
+
if g == 4:
|
|
98
|
+
continue
|
|
99
|
+
for c in range(n_columns):
|
|
100
|
+
base = g * csize + c * n_timepoints
|
|
101
|
+
for t in range(n_timepoints):
|
|
102
|
+
C_gamma[base + t] = 0.0
|
|
103
|
+
|
|
104
|
+
# gamma_index 0..3
|
|
105
|
+
end = n_timepoints - padding
|
|
106
|
+
for g in range(4):
|
|
107
|
+
if end > 0:
|
|
108
|
+
for c in range(n_columns):
|
|
109
|
+
base = c * n_timepoints
|
|
110
|
+
for t in range(end):
|
|
111
|
+
C_alpha[base + n_timepoints - end + t] += -Xv[ex, c, t]
|
|
112
|
+
C_gamma[g * csize + base + n_timepoints - end + t] = (
|
|
113
|
+
3.0 * Xv[ex, c, t]
|
|
114
|
+
)
|
|
115
|
+
end += dilation
|
|
116
|
+
|
|
117
|
+
# gamma_index 5..8
|
|
118
|
+
start = dilation
|
|
119
|
+
for g in range(5, 9):
|
|
120
|
+
if start < n_timepoints:
|
|
121
|
+
for c in range(n_columns):
|
|
122
|
+
base = c * n_timepoints
|
|
123
|
+
for t in range(n_timepoints - start):
|
|
124
|
+
C_alpha[base + t] += -Xv[ex, c, start + t]
|
|
125
|
+
C_gamma[g * csize + base + t] = (
|
|
126
|
+
3.0 * Xv[ex, c, start + t]
|
|
127
|
+
)
|
|
128
|
+
start += dilation
|
|
129
|
+
|
|
130
|
+
for ki in range(num_kernels):
|
|
131
|
+
fie = fis + nfd
|
|
132
|
+
ncc = num_channels_per_combination[combination_index]
|
|
133
|
+
nce = ncs + ncc
|
|
134
|
+
padding1 = (padding0 + ki) % 2
|
|
135
|
+
i0 = _IDX[3 * ki]
|
|
136
|
+
i1 = _IDX[3 * ki + 1]
|
|
137
|
+
i2 = _IDX[3 * ki + 2]
|
|
138
|
+
|
|
139
|
+
# C[t] = sum over the combination's channels
|
|
140
|
+
for t in range(n_timepoints):
|
|
141
|
+
C[t] = 0.0
|
|
142
|
+
for ch in range(ncs, nce):
|
|
143
|
+
c = channel_indices[ch]
|
|
144
|
+
base = c * n_timepoints
|
|
145
|
+
for t in range(n_timepoints):
|
|
146
|
+
C[t] += (
|
|
147
|
+
C_alpha[base + t]
|
|
148
|
+
+ C_gamma[i0 * csize + base + t]
|
|
149
|
+
+ C_gamma[i1 * csize + base + t]
|
|
150
|
+
+ C_gamma[i2 * csize + base + t]
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
if padding1 == 0:
|
|
154
|
+
lo = 0
|
|
155
|
+
hi = n_timepoints
|
|
156
|
+
else:
|
|
157
|
+
lo = padding
|
|
158
|
+
hi = n_timepoints - padding
|
|
159
|
+
n_valid = hi - lo
|
|
160
|
+
|
|
161
|
+
if n_valid > 0:
|
|
162
|
+
for fc in range(nfd):
|
|
163
|
+
bias = biases[fis + fc]
|
|
164
|
+
count = 0.0
|
|
165
|
+
for t in range(lo, hi):
|
|
166
|
+
if C[t] > bias:
|
|
167
|
+
count += 1.0
|
|
168
|
+
feat[ex, fis + fc] = count / n_valid
|
|
169
|
+
|
|
170
|
+
fis = fie
|
|
171
|
+
combination_index += 1
|
|
172
|
+
ncs = nce
|
|
173
|
+
finally:
|
|
174
|
+
free(C_alpha)
|
|
175
|
+
free(C_gamma)
|
|
176
|
+
free(C)
|
|
177
|
+
|
|
178
|
+
return features
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def fit_biases(
|
|
182
|
+
cnp.ndarray[cnp.float32_t, ndim=3, mode="c"] X,
|
|
183
|
+
int[::1] num_channels_per_combination,
|
|
184
|
+
int[::1] channel_indices,
|
|
185
|
+
int[::1] dilations,
|
|
186
|
+
int[::1] num_features_per_dilation,
|
|
187
|
+
int[::1] instance_indices,
|
|
188
|
+
):
|
|
189
|
+
"""Build per-combination convolution output C, summed over channels.
|
|
190
|
+
|
|
191
|
+
Port of the convolution-building half of ``_fit_biases_multi``. Returns a
|
|
192
|
+
``(num_combinations, n_timepoints)`` float32 array; the caller applies
|
|
193
|
+
``np.quantile`` per combination to obtain biases. ``instance_indices`` holds
|
|
194
|
+
the ``np.random.randint(n_instances)`` draw for each combination, computed by
|
|
195
|
+
the caller to reproduce the numba random sequence exactly.
|
|
196
|
+
"""
|
|
197
|
+
cdef int n_timepoints = X.shape[2]
|
|
198
|
+
cdef float[:, :, ::1] Xv = X
|
|
199
|
+
|
|
200
|
+
cdef int num_kernels = _NUM_KERNELS
|
|
201
|
+
cdef int num_dilations = dilations.shape[0]
|
|
202
|
+
cdef int num_combinations = num_kernels * num_dilations
|
|
203
|
+
|
|
204
|
+
cdef cnp.ndarray[cnp.float32_t, ndim=2, mode="c"] out = np.zeros(
|
|
205
|
+
(num_combinations, n_timepoints), dtype=np.float32
|
|
206
|
+
)
|
|
207
|
+
cdef float[:, ::1] outv = out
|
|
208
|
+
|
|
209
|
+
# 9 gamma planes x n_timepoints for a single (instance, channel) row.
|
|
210
|
+
cdef float* C_alpha = <float*>malloc(n_timepoints * sizeof(float))
|
|
211
|
+
cdef float* C_gamma = <float*>malloc(9 * n_timepoints * sizeof(float))
|
|
212
|
+
if C_alpha == NULL or C_gamma == NULL:
|
|
213
|
+
free(C_alpha); free(C_gamma)
|
|
214
|
+
raise MemoryError()
|
|
215
|
+
|
|
216
|
+
cdef int di, ki, g, t, ch, ex, c
|
|
217
|
+
cdef int padding, dilation, start, end
|
|
218
|
+
cdef int comb, ncs, nce, ncc
|
|
219
|
+
cdef int i0, i1, i2
|
|
220
|
+
cdef float x
|
|
221
|
+
|
|
222
|
+
try:
|
|
223
|
+
comb = 0
|
|
224
|
+
ncs = 0
|
|
225
|
+
for di in range(num_dilations):
|
|
226
|
+
dilation = dilations[di]
|
|
227
|
+
padding = ((9 - 1) * dilation) // 2
|
|
228
|
+
|
|
229
|
+
for ki in range(num_kernels):
|
|
230
|
+
ncc = num_channels_per_combination[comb]
|
|
231
|
+
nce = ncs + ncc
|
|
232
|
+
ex = instance_indices[comb]
|
|
233
|
+
i0 = _IDX[3 * ki]
|
|
234
|
+
i1 = _IDX[3 * ki + 1]
|
|
235
|
+
i2 = _IDX[3 * ki + 2]
|
|
236
|
+
|
|
237
|
+
for t in range(n_timepoints):
|
|
238
|
+
outv[comb, t] = 0.0
|
|
239
|
+
|
|
240
|
+
# Accumulate C = C_alpha + C_gamma[i0,i1,i2], summed over channels.
|
|
241
|
+
for ch in range(ncs, nce):
|
|
242
|
+
c = channel_indices[ch]
|
|
243
|
+
|
|
244
|
+
# C_alpha = A = -X ; C_gamma = 0 ; C_gamma[4] = G = 3X
|
|
245
|
+
for t in range(n_timepoints):
|
|
246
|
+
x = Xv[ex, c, t]
|
|
247
|
+
C_alpha[t] = -x
|
|
248
|
+
C_gamma[4 * n_timepoints + t] = x + x + x
|
|
249
|
+
for g in range(9):
|
|
250
|
+
if g == 4:
|
|
251
|
+
continue
|
|
252
|
+
for t in range(n_timepoints):
|
|
253
|
+
C_gamma[g * n_timepoints + t] = 0.0
|
|
254
|
+
|
|
255
|
+
end = n_timepoints - padding
|
|
256
|
+
for g in range(4):
|
|
257
|
+
if end > 0:
|
|
258
|
+
for t in range(end):
|
|
259
|
+
C_alpha[n_timepoints - end + t] += -Xv[ex, c, t]
|
|
260
|
+
C_gamma[g * n_timepoints + n_timepoints - end + t] = (
|
|
261
|
+
3.0 * Xv[ex, c, t]
|
|
262
|
+
)
|
|
263
|
+
end += dilation
|
|
264
|
+
|
|
265
|
+
start = dilation
|
|
266
|
+
for g in range(5, 9):
|
|
267
|
+
if start < n_timepoints:
|
|
268
|
+
for t in range(n_timepoints - start):
|
|
269
|
+
C_alpha[t] += -Xv[ex, c, start + t]
|
|
270
|
+
C_gamma[g * n_timepoints + t] = (
|
|
271
|
+
3.0 * Xv[ex, c, start + t]
|
|
272
|
+
)
|
|
273
|
+
start += dilation
|
|
274
|
+
|
|
275
|
+
for t in range(n_timepoints):
|
|
276
|
+
outv[comb, t] += (
|
|
277
|
+
C_alpha[t]
|
|
278
|
+
+ C_gamma[i0 * n_timepoints + t]
|
|
279
|
+
+ C_gamma[i1 * n_timepoints + t]
|
|
280
|
+
+ C_gamma[i2 * n_timepoints + t]
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
comb += 1
|
|
284
|
+
ncs = nce
|
|
285
|
+
finally:
|
|
286
|
+
free(C_alpha)
|
|
287
|
+
free(C_gamma)
|
|
288
|
+
|
|
289
|
+
return out
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Rocket unit tests."""
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""Tests for the Cython MiniRocket transform.
|
|
2
|
+
|
|
3
|
+
The shape / threading / guard tests are self-contained (no sktime) so they run
|
|
4
|
+
in cibuildwheel's isolated wheel-test env, which installs only pytest. The
|
|
5
|
+
equivalence-vs-numba test imports sktime lazily and is skipped where sktime is
|
|
6
|
+
absent (install the ``dev`` extra to run it).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import importlib.util
|
|
10
|
+
|
|
11
|
+
import numpy as np
|
|
12
|
+
import pytest
|
|
13
|
+
|
|
14
|
+
from sktime_cython.transformations.rocket._minirocket import (
|
|
15
|
+
rocket_fit,
|
|
16
|
+
rocket_transform,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
# sktime is only present with the `dev` extra; the cibuildwheel wheel-test env
|
|
20
|
+
# installs pytest only. find_spec detects absence without importing.
|
|
21
|
+
_HAS_SKTIME = importlib.util.find_spec("sktime") is not None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _panel(seed, n_columns=3, n_timepoints=60):
|
|
25
|
+
rng = np.random.RandomState(seed)
|
|
26
|
+
return rng.normal(size=(6, n_columns, n_timepoints)).astype(np.float32)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@pytest.mark.skipif(not _HAS_SKTIME, reason="sktime not installed (dev extra)")
|
|
30
|
+
@pytest.mark.parametrize("n_columns", [1, 4])
|
|
31
|
+
@pytest.mark.parametrize(
|
|
32
|
+
"num_kernels,max_dilations_per_kernel,random_state",
|
|
33
|
+
[(84, 32, 42), (168, 16, 7), (84, 32, 0)],
|
|
34
|
+
)
|
|
35
|
+
def test_cython_matches_numba(
|
|
36
|
+
n_columns, num_kernels, max_dilations_per_kernel, random_state
|
|
37
|
+
):
|
|
38
|
+
"""Cython transform must match the numba implementation (groundtruth)."""
|
|
39
|
+
from sktime.transformations.rocket import MiniRocketMultivariate
|
|
40
|
+
|
|
41
|
+
X = _panel(random_state, n_columns=n_columns)
|
|
42
|
+
|
|
43
|
+
numba_out = MiniRocketMultivariate(
|
|
44
|
+
num_kernels=num_kernels,
|
|
45
|
+
max_dilations_per_kernel=max_dilations_per_kernel,
|
|
46
|
+
random_state=random_state,
|
|
47
|
+
).fit_transform(X)
|
|
48
|
+
|
|
49
|
+
params = rocket_fit(
|
|
50
|
+
X,
|
|
51
|
+
num_kernels=num_kernels,
|
|
52
|
+
max_dilations_per_kernel=max_dilations_per_kernel,
|
|
53
|
+
random_state=random_state,
|
|
54
|
+
)
|
|
55
|
+
cython_out = rocket_transform(X, params)
|
|
56
|
+
|
|
57
|
+
assert cython_out.shape == numba_out.shape
|
|
58
|
+
np.testing.assert_allclose(cython_out, numba_out.to_numpy(), rtol=1e-4, atol=1e-5)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@pytest.mark.parametrize("num_kernels", [84, 168])
|
|
62
|
+
def test_output_shape(num_kernels):
|
|
63
|
+
"""transform yields (n_instances, num_kernels) float32 features."""
|
|
64
|
+
X = _panel(0)
|
|
65
|
+
out = rocket_transform(X, rocket_fit(X, num_kernels=num_kernels, random_state=0))
|
|
66
|
+
assert out.shape == (X.shape[0], num_kernels)
|
|
67
|
+
assert out.dtype == np.float32
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_threaded_matches_serial():
|
|
71
|
+
"""n_jobs>1 must match the single-threaded result."""
|
|
72
|
+
X = _panel(3)
|
|
73
|
+
params = rocket_fit(X, num_kernels=168, random_state=1)
|
|
74
|
+
np.testing.assert_array_equal(
|
|
75
|
+
rocket_transform(X, params, n_jobs=1), rocket_transform(X, params, n_jobs=4)
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def test_too_short_series_raises():
|
|
80
|
+
"""n_timepoints < 9 is rejected."""
|
|
81
|
+
X = _panel(0, n_timepoints=8)
|
|
82
|
+
with pytest.raises(ValueError, match="n_timepoints must be >= 9"):
|
|
83
|
+
rocket_fit(X)
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sktime-cython
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Cython extensions for sktime
|
|
5
|
+
Author-email: sktime developers <sktime.toolbox@gmail.com>
|
|
6
|
+
Maintainer-email: sktime developers <sktime.toolbox@gmail.com>
|
|
7
|
+
License: BSD 3-Clause License
|
|
8
|
+
|
|
9
|
+
Copyright (c) 2019 - present, The sktime developers.
|
|
10
|
+
|
|
11
|
+
All rights reserved.
|
|
12
|
+
|
|
13
|
+
Redistribution and use in source and binary forms, with or without
|
|
14
|
+
modification, are permitted provided that the following conditions are met:
|
|
15
|
+
|
|
16
|
+
* Redistributions of source code must retain the above copyright notice, this
|
|
17
|
+
list of conditions and the following disclaimer.
|
|
18
|
+
|
|
19
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
|
20
|
+
this list of conditions and the following disclaimer in the documentation
|
|
21
|
+
and/or other materials provided with the distribution.
|
|
22
|
+
|
|
23
|
+
* Neither the name of the copyright holder nor the names of its
|
|
24
|
+
contributors may be used to endorse or promote products derived from
|
|
25
|
+
this software without specific prior written permission.
|
|
26
|
+
|
|
27
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
28
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
29
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
30
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
31
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
32
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
33
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
34
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
35
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
36
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
37
|
+
|
|
38
|
+
Project-URL: Homepage, https://github.com/sktime/sktime-cython
|
|
39
|
+
Project-URL: Repository, https://github.com/sktime/sktime-cython
|
|
40
|
+
Keywords: cython,machine-learning,scikit-learn,time-series,time-series-analysis
|
|
41
|
+
Classifier: Intended Audience :: Developers
|
|
42
|
+
Classifier: Intended Audience :: Science/Research
|
|
43
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
44
|
+
Classifier: Operating System :: MacOS
|
|
45
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
46
|
+
Classifier: Operating System :: POSIX
|
|
47
|
+
Classifier: Operating System :: Unix
|
|
48
|
+
Classifier: Programming Language :: Cython
|
|
49
|
+
Classifier: Programming Language :: Python
|
|
50
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
51
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
52
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
53
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
54
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
55
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
56
|
+
Classifier: Topic :: Scientific/Engineering
|
|
57
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
58
|
+
Classifier: Topic :: Software Development
|
|
59
|
+
Requires-Python: <3.15,>=3.10
|
|
60
|
+
Description-Content-Type: text/markdown
|
|
61
|
+
License-File: LICENSE
|
|
62
|
+
Requires-Dist: numpy<2.5,>=1.21
|
|
63
|
+
Provides-Extra: dev
|
|
64
|
+
Requires-Dist: pre-commit; extra == "dev"
|
|
65
|
+
Requires-Dist: pytest; extra == "dev"
|
|
66
|
+
Requires-Dist: pytest-timeout; extra == "dev"
|
|
67
|
+
Requires-Dist: pytest-xdist; extra == "dev"
|
|
68
|
+
Requires-Dist: sktime; extra == "dev"
|
|
69
|
+
Requires-Dist: numba; extra == "dev"
|
|
70
|
+
Dynamic: license-file
|
|
71
|
+
|
|
72
|
+
# sktime-cython
|
|
73
|
+
|
|
74
|
+
Cython-compiled estimators for [sktime](https://github.com/sktime/sktime).
|
|
75
|
+
|
|
76
|
+
This package hosts ahead-of-time compiled implementations of sktime algorithms,
|
|
77
|
+
isolating the C-compilation and binary-wheel complexity from the main `sktime`
|
|
78
|
+
package. Estimators here expose a plain numpy-in/numpy-out compute layer with
|
|
79
|
+
**no sktime runtime dependency**; `sktime` keeps thin `BaseTransformer` /
|
|
80
|
+
`BaseEstimator` wrappers that delegate to this package.
|
|
81
|
+
|
|
82
|
+
Why a separate package: compiled extensions need a C toolchain, per-platform
|
|
83
|
+
wheels, and `cibuildwheel` release machinery. Keeping that here lets `sktime`
|
|
84
|
+
stay pure-Python while still offering compiled, numba-free fast paths.
|
|
85
|
+
|
|
86
|
+
## Estimators
|
|
87
|
+
|
|
88
|
+
| Estimator | Compute API | Notes |
|
|
89
|
+
|-----------|-------------|-------|
|
|
90
|
+
| Multivariate MiniRocket | `sktime_cython.fit` / `transform` | numba-free; equivalent to `MiniRocketMultivariate`, no JIT warmup |
|
|
91
|
+
|
|
92
|
+
More estimators are added as `.pyx` kernels under `sktime_cython/_cython/` with
|
|
93
|
+
a numpy compute layer alongside.
|
|
94
|
+
|
|
95
|
+
Each estimator lives in its own submodule (nothing is re-exported at the top
|
|
96
|
+
level, so estimators never collide on common names like `fit`/`transform`):
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
import numpy as np
|
|
100
|
+
from sktime_cython.minirocket import fit, transform
|
|
101
|
+
|
|
102
|
+
X = np.random.rand(100, 6, 500).astype("float32")
|
|
103
|
+
params = fit(X, num_kernels=10_000, random_state=42)
|
|
104
|
+
features = transform(X, params, n_jobs=-1) # GIL-released kernel, real threads
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Runtime dependency: numpy only.
|
|
108
|
+
|
|
109
|
+
## Development
|
|
110
|
+
|
|
111
|
+
Requires a C compiler and Python 3.10+. Run from the project root.
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
# editable install with the dev extra (pulls sktime + numba for equivalence
|
|
115
|
+
# tests, plus pre-commit); compiles the Cython extensions via build isolation
|
|
116
|
+
uv pip install -e ".[dev]" # or: pip install -e ".[dev]"
|
|
117
|
+
|
|
118
|
+
# install the git pre-commit hooks ONCE — this is what stops CI lint failures
|
|
119
|
+
pre-commit install
|
|
120
|
+
|
|
121
|
+
# run the tests
|
|
122
|
+
python -m pytest sktime_cython -v
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
After editing a `.pyx`, recompile with `pip install -e .` again (or `make build`).
|
|
126
|
+
|
|
127
|
+
`make` shortcuts (auto-detect `uv`, falling back to `pip`/`python`):
|
|
128
|
+
`make install`, `make build`, `make test`, `make clean`.
|
|
129
|
+
|
|
130
|
+
## Before you push (avoid CI failures)
|
|
131
|
+
|
|
132
|
+
CI runs the pre-commit hooks and **fails if they reformat anything**. Running
|
|
133
|
+
`pre-commit install` (above) auto-runs them on every `git commit`. To check the
|
|
134
|
+
whole tree on demand:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
pre-commit run --all-files
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
This runs `ruff check` (lint) and `ruff format`. If `ruff format` reports "files
|
|
141
|
+
were modified", it already fixed them — `git add` the changes and commit again.
|
|
142
|
+
|
|
143
|
+
## CI workflows
|
|
144
|
+
|
|
145
|
+
- **`test.yml`** (push / PR to `main`): a `code-quality` job running the
|
|
146
|
+
pre-commit hooks, plus a matrix that builds the Cython extensions and runs
|
|
147
|
+
pytest across Python 3.10–3.14 on Linux, macOS, and Windows.
|
|
148
|
+
- **`release.yml`** (on GitHub release): builds binary wheels with
|
|
149
|
+
[`cibuildwheel`](https://cibuildwheel.pypa.io/) for all platforms, builds an
|
|
150
|
+
sdist (with `.pyx` sources), and publishes to PyPI via trusted publishing.
|
|
151
|
+
|
|
152
|
+
## Adding an estimator
|
|
153
|
+
|
|
154
|
+
1. Drop the kernel `.pyx` (and optional `.pyi` stub) in a subfolder of the package.
|
|
155
|
+
The package structure mirrors that of `sktime`, files should be added paralleling
|
|
156
|
+
its primary import location in `sktime`
|
|
157
|
+
2. Register the kernel as an `Extension` in `setup.py`.
|
|
158
|
+
3. Add a numpy compute-layer module `sktime_cython/<name>.py` exposing the
|
|
159
|
+
estimator's public functions. Keep them in the submodule — do not re-export
|
|
160
|
+
at the top level (avoids name collisions across estimators).
|
|
161
|
+
4. Add tests in a subfolder `tests`, in the same folder as the kernel.
|
|
162
|
+
|
|
163
|
+
## Package structure
|
|
164
|
+
|
|
165
|
+
Cython kernels and python layers should be added in a parallel location as in the
|
|
166
|
+
`sktime` package.
|
|
167
|
+
|
|
168
|
+
Example for minirocket:
|
|
169
|
+
|
|
170
|
+
```
|
|
171
|
+
sktime_cython/
|
|
172
|
+
transformations/
|
|
173
|
+
rocket/
|
|
174
|
+
__init__.py # public compute-layer exports
|
|
175
|
+
_minirocket.py # numpy fit/transform layer
|
|
176
|
+
_minirocket_multivariate_cython.pyx # compiled kernels
|
|
177
|
+
_minirocket_multivariate_cython.pyi # type stubs
|
|
178
|
+
tests/
|
|
179
|
+
__init__.py
|
|
180
|
+
test_minirocket.py # tests
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## License
|
|
184
|
+
|
|
185
|
+
BSD 3-Clause License — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
MANIFEST.in
|
|
3
|
+
README.md
|
|
4
|
+
pyproject.toml
|
|
5
|
+
setup.py
|
|
6
|
+
sktime_cython/__init__.py
|
|
7
|
+
sktime_cython.egg-info/PKG-INFO
|
|
8
|
+
sktime_cython.egg-info/SOURCES.txt
|
|
9
|
+
sktime_cython.egg-info/dependency_links.txt
|
|
10
|
+
sktime_cython.egg-info/requires.txt
|
|
11
|
+
sktime_cython.egg-info/top_level.txt
|
|
12
|
+
sktime_cython/transformations/__init__.py
|
|
13
|
+
sktime_cython/transformations/rocket/__init__.py
|
|
14
|
+
sktime_cython/transformations/rocket/_minirocket.py
|
|
15
|
+
sktime_cython/transformations/rocket/_minirocket_multivariate_cython.pyi
|
|
16
|
+
sktime_cython/transformations/rocket/_minirocket_multivariate_cython.pyx
|
|
17
|
+
sktime_cython/transformations/rocket/tests/__init__.py
|
|
18
|
+
sktime_cython/transformations/rocket/tests/test_minirocket_cython.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|