Skip to content

Commit 8e96f8d

Browse files
committed
Numpy 2.0 and Python 3.13 compatibility
1 parent dfd8103 commit 8e96f8d

7 files changed

Lines changed: 44 additions & 37 deletions

File tree

.github/workflows/build-wheels.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ echo "[net]" >> "$HOME/.cargo/config.toml"
1111
echo "git-fetch-with-cli = true" >> "$HOME/.cargo/config.toml"
1212

1313
# build wheels
14-
for PYBIN in /opt/python/cp3[89]*/bin /opt/python/cp31[012]*/bin; do
14+
for PYBIN in /opt/python/cp3[89]*/bin /opt/python/cp31[0123]-cp31[0123]/bin; do
1515
"${PYBIN}/pip" install -r requirements.txt -r requirements-dev.txt
1616
"${PYBIN}/maturin" build -i "${PYBIN}/python" --release --strip
1717
done
@@ -22,7 +22,7 @@ for wheel in target/wheels/*.whl; do
2222
done
2323

2424
# test wheels
25-
for PYBIN in /opt/python/cp3[89]*/bin /opt/python/cp31[012]*/bin; do
25+
for PYBIN in /opt/python/cp3[89]*/bin /opt/python/cp31[0123]-cp31[0123]/bin; do
2626
"${PYBIN}/pip" install kmedoids --no-index --find-links wheelhouse
2727
cd tests && "${PYBIN}/python" -m unittest discover && cd ..
2828
done

.github/workflows/wheels.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ jobs:
5151
runs-on: macos-latest
5252
strategy:
5353
matrix:
54-
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
54+
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
5555
steps:
5656
- uses: actions/checkout@v4
5757
- uses: actions-rs/toolchain@v1
@@ -79,7 +79,7 @@ jobs:
7979
runs-on: windows-latest
8080
strategy:
8181
matrix:
82-
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
82+
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
8383
steps:
8484
- uses: actions/checkout@v4
8585
- uses: actions-rs/toolchain@v1

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
For changes to the main Rust package, please see <https://github.com/kno10/rust-kmedoids/blob/main/CHANGELOG.md>
44

5+
## kmedoids 0.5.3 (2024-12-02)
6+
7+
- no functionality changes
8+
- numpy 2.0 and Python 3.13 compatibility
9+
- update to pyo3 0.23, numpy 0.23
10+
511
## kmedoids 0.5.2 (2024-09-10)
612

713
- fix clippy warnings

CITATION.cff

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ authors:
1010
title: "Fast k-medoids Clustering in Rust and Python"
1111
journal: "J. Open Source Softw."
1212
doi: 10.21105/joss.04183
13-
version: 0.5.2
14-
date-released: 2024-09-10
13+
version: 0.5.3
14+
date-released: 2024-12-02
1515
license: GPL-3.0
1616
preferred-citation:
1717
title: "Fast k-medoids Clustering in Rust and Python"

Cargo.toml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
edition = "2021"
33
name = "kmedoids"
4-
version = "0.5.2"
4+
version = "0.5.3"
55
authors = ["Erich Schubert <erich.schubert@tu-dortmund.de>", "Lars Lenssen <lars.lenssen@tu-dortmund.de>"]
66
description = "k-Medoids clustering with the FasterPAM algorithm"
77
homepage = "https://github.com/kno10/python-kmedoids"
@@ -14,13 +14,13 @@ name = "kmedoids"
1414
crate-type = ["cdylib"]
1515

1616
[dependencies]
17-
rustkmedoids = { version = "0.5.2", package = "kmedoids", git = "https://github.com/kno10/rust-kmedoids" }
18-
numpy = "0.21"
19-
ndarray = "0.15"
17+
rustkmedoids = { version = "0.5.3", package = "kmedoids", git = "https://github.com/kno10/rust-kmedoids" }
18+
numpy = "0.23"
19+
ndarray = "0.16"
2020
rand = "0.8"
2121
rayon = "1.10"
2222

2323
[dependencies.pyo3]
24-
version = "^0.21"
24+
version = "^0.23"
2525
features = ["extension-module"]
2626

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "maturin"
44

55
[project]
66
name = "kmedoids"
7-
version = "0.5.2"
7+
version = "0.5.3"
88
description = "k-Medoids Clustering in Python with FasterPAM"
99
requires-dist = ["numpy"]
1010
classifier = [

src/lib.rs

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use pyo3::prelude::*;
22
use pyo3::wrap_pyfunction;
3+
use pyo3::types::PyTuple;
34
use numpy::{PyArray1, PyReadonlyArray1, PyReadonlyArray2, PyArrayMethods, PyUntypedArrayMethods};
45
use rand::{rngs::StdRng, SeedableRng};
56

@@ -16,13 +17,13 @@ macro_rules! variant_call {
1617
/// :return: k-medoids clustering result
1718
/// :rtype: KMedoidsResult
1819
#[pyfunction]
19-
fn $name(dist: PyReadonlyArray2<'_, $type>, meds: PyReadonlyArray1<'_, usize>, max_iter: usize) -> PyResult<Py<PyAny>> {
20+
fn $name<'py>(dist: PyReadonlyArray2<'py, $type>, meds: PyReadonlyArray1<'py, usize>, max_iter: usize) -> PyResult<Py<PyTuple>> {
2021
assert_eq!(dist.ndim(), 2);
2122
assert_eq!(dist.shape()[0], dist.shape()[1]);
2223
let mut meds = meds.to_vec()?;
2324
let (loss, assi, n_iter, n_swap): ($ltype, _, _, _) = rustkmedoids::$variant(&dist.as_array(), &mut meds, max_iter);
24-
Python::with_gil(|py| -> PyResult<Py<PyAny>> {
25-
Ok((loss, PyArray1::from_vec_bound(py, assi), PyArray1::from_vec_bound(py, meds), n_iter, n_swap).to_object(py))
25+
Python::with_gil(|py| {
26+
Ok((loss, PyArray1::from_vec(py, assi), PyArray1::from_vec(py, meds), n_iter, n_swap).into_pyobject(py)?.unbind())
2627
})
2728
}
2829
}}
@@ -62,14 +63,14 @@ macro_rules! rand_call {
6263
/// :return: k-medoids clustering result
6364
/// :rtype: KMedoidsResult
6465
#[pyfunction]
65-
fn $name(dist: PyReadonlyArray2<'_, $type>, meds: PyReadonlyArray1<'_, usize>, max_iter: usize, seed: u64) -> PyResult<Py<PyAny>> {
66+
fn $name<'py>(dist: PyReadonlyArray2<'py, $type>, meds: PyReadonlyArray1<'py, usize>, max_iter: usize, seed: u64) -> PyResult<Py<PyTuple>> {
6667
assert_eq!(dist.ndim(), 2);
6768
assert_eq!(dist.shape()[0], dist.shape()[1]);
6869
let mut meds = meds.to_vec()?;
6970
let mut rnd = StdRng::seed_from_u64(seed);
7071
let (loss, assi, n_iter, n_swap): ($ltype, _, _, _) = rustkmedoids::$variant(&dist.as_array(), &mut meds, max_iter, &mut rnd);
71-
Python::with_gil(|py| -> PyResult<Py<PyAny>> {
72-
Ok((loss, PyArray1::from_vec_bound(py, assi), PyArray1::from_vec_bound(py, meds), n_iter, n_swap).to_object(py))
72+
Python::with_gil(|py| {
73+
Ok((loss, PyArray1::from_vec(py, assi), PyArray1::from_vec(py, meds), n_iter, n_swap).into_pyobject(py)?.unbind())
7374
})
7475
}
7576
}}
@@ -95,7 +96,7 @@ macro_rules! par_call {
9596
/// :return: k-medoids clustering result
9697
/// :rtype: KMedoidsResult
9798
#[pyfunction]
98-
fn $name(dist: PyReadonlyArray2<'_, $type>, meds: PyReadonlyArray1<'_, usize>, max_iter: usize, seed: u64, n_cpu: usize) -> PyResult<Py<PyAny>> {
99+
fn $name<'py>(dist: PyReadonlyArray2<'py, $type>, meds: PyReadonlyArray1<'py, usize>, max_iter: usize, seed: u64, n_cpu: usize) -> PyResult<Py<PyTuple>> {
99100
assert_eq!(dist.ndim(), 2);
100101
assert_eq!(dist.shape()[0], dist.shape()[1]);
101102
let pool = rayon::ThreadPoolBuilder::new().num_threads(n_cpu).build().unwrap();
@@ -105,8 +106,8 @@ fn $name(dist: PyReadonlyArray2<'_, $type>, meds: PyReadonlyArray1<'_, usize>, m
105106
let mut rnd = StdRng::seed_from_u64(seed);
106107
rustkmedoids::$variant(&dist, &mut meds, max_iter, &mut rnd)
107108
});
108-
Python::with_gil(|py| -> PyResult<Py<PyAny>> {
109-
Ok((loss, PyArray1::from_vec_bound(py, assi), PyArray1::from_vec_bound(py, meds), n_iter, n_swap).to_object(py))
109+
Python::with_gil(|py| {
110+
Ok((loss, PyArray1::from_vec(py, assi), PyArray1::from_vec(py, meds), n_iter, n_swap).into_pyobject(py)?.unbind())
110111
})
111112
}
112113
}}
@@ -126,12 +127,12 @@ macro_rules! pam_build_call {
126127
/// :return: k-medoids clustering result
127128
/// :rtype: KMedoidsResult
128129
#[pyfunction]
129-
fn $name(dist: PyReadonlyArray2<'_, $type>, k: usize) -> PyResult<Py<PyAny>> {
130+
fn $name<'py>(dist: PyReadonlyArray2<'py, $type>, k: usize) -> PyResult<Py<PyTuple>> {
130131
assert_eq!(dist.ndim(), 2);
131132
assert_eq!(dist.shape()[0], dist.shape()[1]);
132133
let (loss, assi, meds): ($ltype, _, _) = rustkmedoids::pam_build(&dist.as_array(), k);
133-
Python::with_gil(|py| -> PyResult<Py<PyAny>> {
134-
Ok((loss, PyArray1::from_vec_bound(py, assi), PyArray1::from_vec_bound(py, meds), 1).to_object(py))
134+
Python::with_gil(|py| {
135+
Ok((loss, PyArray1::from_vec(py, assi), PyArray1::from_vec(py, meds), 1).into_pyobject(py)?.unbind())
135136
})
136137
}
137138
}}
@@ -153,13 +154,13 @@ macro_rules! alternating_call {
153154
/// :return: k-medoids clustering result
154155
/// :rtype: KMedoidsResult
155156
#[pyfunction]
156-
fn $name(dist: PyReadonlyArray2<'_, $type>, meds: PyReadonlyArray1<'_, usize>, max_iter: usize) -> PyResult<Py<PyAny>> {
157+
fn $name<'py>(dist: PyReadonlyArray2<'py, $type>, meds: PyReadonlyArray1<'py, usize>, max_iter: usize) -> PyResult<Py<PyTuple>> {
157158
assert_eq!(dist.ndim(), 2);
158159
assert_eq!(dist.shape()[0], dist.shape()[1]);
159160
let mut meds = meds.to_vec()?;
160161
let (loss, assi, n_iter): ($ltype, _, _) = rustkmedoids::alternating(&dist.as_array(), &mut meds, max_iter);
161-
Python::with_gil(|py| -> PyResult<Py<PyAny>> {
162-
Ok((loss, PyArray1::from_vec_bound(py, assi), PyArray1::from_vec_bound(py, meds), n_iter).to_object(py))
162+
Python::with_gil(|py| {
163+
Ok((loss, PyArray1::from_vec(py, assi), PyArray1::from_vec(py, meds), n_iter).into_pyobject(py)?.unbind())
163164
})
164165
}
165166
}}
@@ -181,15 +182,15 @@ macro_rules! dynmsc_call {
181182
/// :return: k-medoids clustering result
182183
/// :rtype: DynkResult
183184
#[pyfunction]
184-
fn $name(dist: PyReadonlyArray2<'_, $type>, meds: PyReadonlyArray1<'_, usize>, minimum_k: usize, max_iter: usize) -> PyResult<Py<PyAny>> {
185+
fn $name<'py>(dist: PyReadonlyArray2<'py, $type>, meds: PyReadonlyArray1<'py, usize>, minimum_k: usize, max_iter: usize) -> PyResult<Py<PyTuple>> {
185186
assert_eq!(dist.ndim(), 2);
186187
assert_eq!(dist.shape()[0], dist.shape()[1]);
187188
let mut meds = meds.to_vec()?;
188189
let maxk = meds.len() + 1;
189190
let (loss, assi, n_iter, n_swap, best_meds, losses): ($ltype, _, _, _, _, _) = rustkmedoids::dynmsc(&dist.as_array(), &mut meds, minimum_k, max_iter);
190191
let bestk = best_meds.len();
191-
Python::with_gil(|py| -> PyResult<Py<PyAny>> {
192-
Ok((loss, PyArray1::from_vec_bound(py, assi), PyArray1::from_vec_bound(py, best_meds), bestk, PyArray1::from_vec_bound(py, losses), (minimum_k..maxk).collect::<Vec<usize>>(), n_iter, n_swap).to_object(py))
192+
Python::with_gil(|py| {
193+
Ok((loss, PyArray1::from_vec(py, assi), PyArray1::from_vec(py, best_meds), bestk, PyArray1::from_vec(py, losses), PyArray1::from_vec(py, (minimum_k..maxk).collect::<Vec<usize>>()), n_iter, n_swap).into_pyobject(py)?.unbind())
193194
})
194195
}
195196
}}
@@ -209,12 +210,12 @@ macro_rules! silhouette_call {
209210
/// :return: Silhouette evaluation result
210211
/// :rtype: pair of Silhouette score and Silhouette coefficients per point
211212
#[pyfunction]
212-
fn $name(dist: PyReadonlyArray2<'_, $type>, assi: PyReadonlyArray1<'_, usize>, samples: bool) -> PyResult<Py<PyAny>> {
213+
fn $name<'py>(dist: PyReadonlyArray2<'py, $type>, assi: PyReadonlyArray1<'py, usize>, samples: bool) -> PyResult<Py<PyTuple>> {
213214
assert_eq!(dist.ndim(), 2);
214215
assert_eq!(dist.shape()[0], dist.shape()[1]);
215216
let (sil, sils): (f64, _) = rustkmedoids::silhouette(&dist.as_array(), &assi.to_vec()?, samples);
216-
Python::with_gil(|py| -> PyResult<Py<PyAny>> {
217-
Ok((sil, PyArray1::from_vec_bound(py, sils)).to_object(py))
217+
Python::with_gil(|py| {
218+
Ok((sil, PyArray1::from_vec(py, sils)).into_pyobject(py)?.unbind())
218219
})
219220
}
220221
}}
@@ -236,7 +237,7 @@ macro_rules! par_silhouette_call {
236237
/// :return: Silhouette evaluation result
237238
/// :rtype: Silhouette score
238239
#[pyfunction]
239-
fn $name(dist: PyReadonlyArray2<'_, $type>, assi: PyReadonlyArray1<'_, usize>, n_cpu: usize) -> PyResult<f64> {
240+
fn $name<'py>(dist: PyReadonlyArray2<'py, $type>, assi: PyReadonlyArray1<'py, usize>, n_cpu: usize) -> PyResult<f64> {
240241
assert_eq!(dist.ndim(), 2);
241242
assert_eq!(dist.shape()[0], dist.shape()[1]);
242243
let pool = rayon::ThreadPoolBuilder::new().num_threads(n_cpu).build().unwrap();
@@ -264,12 +265,12 @@ macro_rules! medoid_silhouette_call {
264265
/// :return: Medoid Silhouette evaluation result
265266
/// :rtype: pair of Medoid Silhouette score and Medoid Silhouette coefficients per point
266267
#[pyfunction]
267-
fn $name(dist: PyReadonlyArray2<'_, $type>, meds: PyReadonlyArray1<'_, usize>, samples: bool) -> PyResult<Py<PyAny>> {
268+
fn $name<'py>(dist: PyReadonlyArray2<'py, $type>, meds: PyReadonlyArray1<'py, usize>, samples: bool) -> PyResult<Py<PyTuple>> {
268269
assert_eq!(dist.ndim(), 2);
269270
assert_eq!(dist.shape()[0], dist.shape()[1]);
270271
let (sil, sils): (f64, _) = rustkmedoids::medoid_silhouette(&dist.as_array(), &meds.to_vec()?, samples);
271-
Python::with_gil(|py| -> PyResult<Py<PyAny>> {
272-
Ok((sil, PyArray1::from_vec_bound(py, sils)).to_object(py))
272+
Python::with_gil(|py| {
273+
Ok((sil, PyArray1::from_vec(py, sils)).into_pyobject(py)?.unbind())
273274
})
274275
}
275276
}}

0 commit comments

Comments
 (0)