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.
@@ -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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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,8 @@
1
+ """Rocket transformers."""
2
+
3
+ from sktime_cython.transformations.rocket._minirocket import (
4
+ rocket_fit,
5
+ rocket_transform,
6
+ )
7
+
8
+ __all__ = ["rocket_fit", "rocket_transform"]
@@ -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,9 @@
1
+ numpy<2.5,>=1.21
2
+
3
+ [dev]
4
+ pre-commit
5
+ pytest
6
+ pytest-timeout
7
+ pytest-xdist
8
+ sktime
9
+ numba
@@ -0,0 +1,2 @@
1
+ dist
2
+ sktime_cython