diff --git a/.github/workflows/python-compatibility.yml b/.github/workflows/python-compatibility.yml new file mode 100644 index 0000000..efbe8d5 --- /dev/null +++ b/.github/workflows/python-compatibility.yml @@ -0,0 +1,31 @@ +name: Python compatibility + +on: + push: + branches: [master] + pull_request: + +permissions: + contents: read + +jobs: + cpu: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.13", "3.14"] + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-python@v7 + with: + python-version: ${{ matrix.python-version }} + - name: Build distributions + run: | + python -m pip install --upgrade pip build + python -m build + - name: Install wheel and test dependencies + run: python -m pip install dist/*.whl pytest + - name: Run test suite + working-directory: ${{ runner.temp }} + run: python -m pytest -q -rs "$GITHUB_WORKSPACE/tests" diff --git a/README.md b/README.md index 3e6e101..f78e51d 100644 --- a/README.md +++ b/README.md @@ -46,11 +46,16 @@ see [Tutorial_2_Advanced_multioutput](https://github.com/AILab-MLTools/Py-Boost/ ## Installation -Before installing py-boost via pip, you should have cupy installed. You can use: +Py-Boost supports Python 3.8–3.14. Install the CuPy distribution compatible +with your Python and CUDA versions before installing Py-Boost. -`pip install -U cupy-cuda110 py-boost` +On Python 3.14, use Numba 0.63 or later and CuPy 14 or later. For example, +with CUDA 12.x: -**Note**: replace with your cuda version! For details, see [this guide](https://docs.cupy.dev/en/stable/install.html). +`pip install -U "cupy-cuda12x>=14" py-boost` + +For other Python or CUDA versions, choose the appropriate CuPy package from the +[CuPy installation guide](https://docs.cupy.dev/en/stable/install.html). ## Quick tour diff --git a/build_package.sh b/build_package.sh index 782b52b..fbb4ee7 100644 --- a/build_package.sh +++ b/build_package.sh @@ -6,7 +6,7 @@ source ./py_boost_venv/bin/activate pip install -U pip pip install -U poetry -pip install -U cupy-cuda110 +pip install -U "${CUPY_PACKAGE:-cupy-cuda12x}" poetry lock poetry install diff --git a/pyproject.toml b/pyproject.toml index b9e045f..f5116b0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,6 +19,7 @@ classifiers = [ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Operating System :: OS Independent", "Intended Audience :: Science/Research", "Development Status :: 3 - Alpha", @@ -29,7 +30,7 @@ classifiers = [ [tool.poetry.dependencies] -python = ">=3.8, <3.14" +python = ">=3.8, <3.15" scikit-learn = ">=1" numpy = "*" diff --git a/tests/test_quantization.py b/tests/test_quantization.py new file mode 100644 index 0000000..e5dd286 --- /dev/null +++ b/tests/test_quantization.py @@ -0,0 +1,38 @@ +"""CPU coverage for the Numba kernels used by all training paths.""" + +import numpy as np +import pytest + +from py_boost.quantization.base import ( + QuantileQuantizer, + UniformQuantizer, + UniquantQuantizer, +) + + +@pytest.mark.parametrize("dtype", [np.float32, np.float64]) +@pytest.mark.parametrize("quantizer_cls", [ + QuantileQuantizer, UniformQuantizer, UniquantQuantizer, +]) +def test_quantization_matches_borders(quantizer_cls, dtype): + # More distinct values than bins exercises each quantizer's binning kernel. + X = np.linspace(-10, 10, 128, dtype=dtype).reshape(32, 4) + X[0] = np.nan + quantizer = quantizer_cls(max_bin=8) + + encoded = quantizer.fit_transform(X) + borders = quantizer.get_borders() + + assert encoded.dtype == np.uint8 + assert encoded.shape == X.shape + assert quantizer.get_max_bin() <= 8 + for column, column_borders in enumerate(borders): + assert np.isneginf(column_borders[0]) + assert np.isposinf(column_borders[-1]) + assert np.all(np.diff(column_borders) > 0) + valid = ~np.isnan(X[:, column]) + expected = np.zeros(X.shape[0], dtype=np.uint8) + expected[valid] = np.searchsorted(column_borders, X[valid, column]) + np.testing.assert_array_equal(encoded[:, column], expected) + + np.testing.assert_array_equal(quantizer.transform(X), encoded)