From a69fb0522be87342c8c654fe4da1506b13cea6d1 Mon Sep 17 00:00:00 2001 From: Malte Londschien Date: Sun, 9 Nov 2025 08:57:51 -0400 Subject: [PATCH 1/8] Update deps --- Cargo.toml | 6 +++--- changeforest-py/Cargo.toml | 6 +++--- changeforest-r/src/rust/Cargo.toml | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 00c33bd..7b36fe7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,14 +13,14 @@ exclude = ["/.github", "/changeforest-py", "/changeforest-r", "/testdata", ".pre name = "changeforest" [dependencies] -ndarray = "0.16" +ndarray = "0.17" rand = "0.8" biosphere = "0.4.0" [dev-dependencies] rstest = "0.26" assert_approx_eq = "1.1" -ndarray-rand = "0.15" -ndarray = { version = "0.16", features = ["approx"] } +ndarray-rand = "0.16" +ndarray = { version = "0.17", features = ["approx"] } ndarray-csv = "^0.5" csv = "^1" diff --git a/changeforest-py/Cargo.toml b/changeforest-py/Cargo.toml index 08f11ff..9399aec 100644 --- a/changeforest-py/Cargo.toml +++ b/changeforest-py/Cargo.toml @@ -12,8 +12,8 @@ crate-type = ["cdylib"] name = "changeforest" [dependencies] -numpy = "0.26" +numpy = "0.27" changeforest = { path = "../" } -ndarray = "0.16" -pyo3 = {version = "0.26", features = ["extension-module"]} +ndarray = "0.17" +pyo3 = {version = "0.27", features = ["extension-module"]} biosphere = "0.4.0" diff --git a/changeforest-r/src/rust/Cargo.toml b/changeforest-r/src/rust/Cargo.toml index bc9d341..b4ce2c1 100644 --- a/changeforest-r/src/rust/Cargo.toml +++ b/changeforest-r/src/rust/Cargo.toml @@ -11,5 +11,5 @@ name = 'changeforestr' [dependencies] extendr-api = { version="0.8", features = ["ndarray"] } changeforest = { path = "../../../" } -ndarray = "0.16" +ndarray = "0.17" biosphere = "0.4.0" From 5e39e58230e4af7a3ae0e343fdb6eb8e22e776a1 Mon Sep 17 00:00:00 2001 From: Malte Londschien Date: Thu, 11 Dec 2025 14:45:53 -0400 Subject: [PATCH 2/8] Update biosphere. --- CHANGELOG.md | 6 ++++++ Cargo.toml | 10 +++++----- changeforest-py/Cargo.toml | 8 ++++---- changeforest-py/pyproject.toml | 2 +- changeforest-r/DESCRIPTION | 2 +- changeforest-r/src/rust/Cargo.toml | 6 +++--- 6 files changed, 20 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 670e4d8..9767ef4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # Changelog +## 1.2.2 - (2025-12-11) + +**Other changes:** + +- Update `ndarray` to 0.17.1, `biosphere` to 0.4.2, `numpy` and `pyo3` to 0.27.1, `r-extendr` to 0.8.1. + ## 1.2.1 - (2025-09-22) **Bug fixes:** diff --git a/Cargo.toml b/Cargo.toml index 7b36fe7..324e848 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ name = "changeforest" description = "Random Forests for Change Point Detection" authors = ["Malte Londschien "] repository = "https://github.com/mlondschien/changeforest/" -version = "1.2.1" +version = "1.2.2" edition = "2021" readme = "README.md" license = "BSD-3-Clause" @@ -13,14 +13,14 @@ exclude = ["/.github", "/changeforest-py", "/changeforest-r", "/testdata", ".pre name = "changeforest" [dependencies] -ndarray = "0.17" -rand = "0.8" -biosphere = "0.4.0" +ndarray = "0.17.1" +rand = "0.9" +biosphere = "0.4.2" [dev-dependencies] rstest = "0.26" assert_approx_eq = "1.1" ndarray-rand = "0.16" -ndarray = { version = "0.17", features = ["approx"] } +ndarray = { version = "0.17.1", features = ["approx"] } ndarray-csv = "^0.5" csv = "^1" diff --git a/changeforest-py/Cargo.toml b/changeforest-py/Cargo.toml index 9399aec..a17591c 100644 --- a/changeforest-py/Cargo.toml +++ b/changeforest-py/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "changeforest_py" -version = "1.2.1" +version = "1.2.2" edition = "2021" readme = "README.md" @@ -12,8 +12,8 @@ crate-type = ["cdylib"] name = "changeforest" [dependencies] -numpy = "0.27" +numpy = "0.27.1" changeforest = { path = "../" } -ndarray = "0.17" +ndarray = "0.17.1" pyo3 = {version = "0.27", features = ["extension-module"]} -biosphere = "0.4.0" +biosphere = "0.4.2" diff --git a/changeforest-py/pyproject.toml b/changeforest-py/pyproject.toml index 2d4501b..b660f3a 100644 --- a/changeforest-py/pyproject.toml +++ b/changeforest-py/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "changeforest" description = "Random Forests for Change Point Detection" -version = "1.2.1" +version = "1.2.2" readme = "README.md" requires-python = ">=3.8" author = "Malte Londschien " diff --git a/changeforest-r/DESCRIPTION b/changeforest-r/DESCRIPTION index 9605b92..4d745f0 100644 --- a/changeforest-r/DESCRIPTION +++ b/changeforest-r/DESCRIPTION @@ -1,7 +1,7 @@ Package: changeforest Type: Package Title: Random Forests for Change Point Detection -Version: 1.2.1 +Version: 1.2.2 Author: Malte Londschien Maintainer: Malte Londschien Description: diff --git a/changeforest-r/src/rust/Cargo.toml b/changeforest-r/src/rust/Cargo.toml index b4ce2c1..80832cc 100644 --- a/changeforest-r/src/rust/Cargo.toml +++ b/changeforest-r/src/rust/Cargo.toml @@ -1,7 +1,7 @@ [package] name = 'changeforestr' publish = false -version = '1.2.1' +version = '1.2.2' edition = '2021' [lib] @@ -11,5 +11,5 @@ name = 'changeforestr' [dependencies] extendr-api = { version="0.8", features = ["ndarray"] } changeforest = { path = "../../../" } -ndarray = "0.17" -biosphere = "0.4.0" +ndarray = "0.17.1" +biosphere = "0.4.2" From c4c0d6eecad0694570dbcee4400c1fa3422aac43 Mon Sep 17 00:00:00 2001 From: Malte Londschien Date: Thu, 11 Dec 2025 14:50:17 -0400 Subject: [PATCH 3/8] Adapt to breaking changes of rand. --- src/segmentation.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/segmentation.rs b/src/segmentation.rs index d62c680..a235a15 100644 --- a/src/segmentation.rs +++ b/src/segmentation.rs @@ -2,7 +2,7 @@ use crate::optimizer::OptimizerResult; use crate::ModelSelectionResult; use crate::Optimizer; use rand::{ - distributions::{Distribution, Uniform}, + distr::{Distribution, Uniform}, rngs::StdRng, SeedableRng, }; @@ -68,7 +68,7 @@ impl<'a> Segmentation<'a> { } SegmentationType::WBS => { let mut rng = StdRng::seed_from_u64(optimizer.control().seed); - let dist = Uniform::from(0..(optimizer.n() + 1)); + let dist = Uniform::new(0, optimizer.n() + 1).unwrap(); let mut start: usize; let mut stop: usize; From 83c347f33e5c25b547dd3ff2eb5e0d21f7bbc8b7 Mon Sep 17 00:00:00 2001 From: Malte Londschien Date: Thu, 11 Dec 2025 15:05:25 -0400 Subject: [PATCH 4/8] Update tests, changelog. --- CHANGELOG.md | 6 +++--- Cargo.toml | 4 ++-- changeforest-py/Cargo.toml | 2 +- changeforest-py/pyproject.toml | 2 +- changeforest-r/DESCRIPTION | 2 +- changeforest-r/src/rust/Cargo.toml | 2 +- src/segmentation.rs | 4 ++-- src/testing.rs | 2 +- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9767ef4..3e69a47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,11 @@ # Changelog -## 1.2.2 - (2025-12-11) +## 2.0.0 - (2025-12-11) -**Other changes:** +**Breaking changes:** -- Update `ndarray` to 0.17.1, `biosphere` to 0.4.2, `numpy` and `pyo3` to 0.27.1, `r-extendr` to 0.8.1. +- Update `ndarray` to 0.17.1, `biosphere` to 0.4.2, `numpy` and `pyo3` to 0.27.1, `r-extendr` to 0.8.1, and `rand` from 0.8 to 0.9. As the rust crate `rand` changed the algorithm for the `Uniform` distribution, this breaks backwards reproducibility. ## 1.2.1 - (2025-09-22) diff --git a/Cargo.toml b/Cargo.toml index 324e848..100991c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ name = "changeforest" description = "Random Forests for Change Point Detection" authors = ["Malte Londschien "] repository = "https://github.com/mlondschien/changeforest/" -version = "1.2.2" +version = "2.0.0" edition = "2021" readme = "README.md" license = "BSD-3-Clause" @@ -22,5 +22,5 @@ rstest = "0.26" assert_approx_eq = "1.1" ndarray-rand = "0.16" ndarray = { version = "0.17.1", features = ["approx"] } -ndarray-csv = "^0.5" +ndarray-csv = "0.5.4" csv = "^1" diff --git a/changeforest-py/Cargo.toml b/changeforest-py/Cargo.toml index a17591c..111ccbd 100644 --- a/changeforest-py/Cargo.toml +++ b/changeforest-py/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "changeforest_py" -version = "1.2.2" +version = "2.0.0" edition = "2021" readme = "README.md" diff --git a/changeforest-py/pyproject.toml b/changeforest-py/pyproject.toml index b660f3a..d284351 100644 --- a/changeforest-py/pyproject.toml +++ b/changeforest-py/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "changeforest" description = "Random Forests for Change Point Detection" -version = "1.2.2" +version = "2.0.0" readme = "README.md" requires-python = ">=3.8" author = "Malte Londschien " diff --git a/changeforest-r/DESCRIPTION b/changeforest-r/DESCRIPTION index 4d745f0..116853f 100644 --- a/changeforest-r/DESCRIPTION +++ b/changeforest-r/DESCRIPTION @@ -1,7 +1,7 @@ Package: changeforest Type: Package Title: Random Forests for Change Point Detection -Version: 1.2.2 +Version: 2.0.0 Author: Malte Londschien Maintainer: Malte Londschien Description: diff --git a/changeforest-r/src/rust/Cargo.toml b/changeforest-r/src/rust/Cargo.toml index 80832cc..0b78fc9 100644 --- a/changeforest-r/src/rust/Cargo.toml +++ b/changeforest-r/src/rust/Cargo.toml @@ -1,7 +1,7 @@ [package] name = 'changeforestr' publish = false -version = '1.2.2' +version = '2.0.0' edition = '2021' [lib] diff --git a/src/segmentation.rs b/src/segmentation.rs index a235a15..6c438e3 100644 --- a/src/segmentation.rs +++ b/src/segmentation.rs @@ -181,7 +181,7 @@ mod tests { (50, 100, 62, 3000.0) ])] #[case(SegmentationType::WBS, vec![ - (73, 78, 74, 415.0), + (56, 78, 74, 415.0), (2, 59, 16, 684.0), (26, 77, 38, 1836.0), (22, 80, 36, 1856.0), @@ -212,7 +212,7 @@ mod tests { #[rstest] #[case(SegmentationType::BS, (25, 1000.))] #[case(SegmentationType::SBS, (62, 3000.))] - #[case(SegmentationType::WBS, (60, 2900.))] + #[case(SegmentationType::WBS, (60, 2958.))] fn test_optimizer(#[case] segmentation_type: SegmentationType, #[case] expected: (usize, f64)) { let control = Control::default(); let optimizer = testing::TrivialOptimizer { control: &control }; diff --git a/src/testing.rs b/src/testing.rs index 7179b5f..07257e2 100644 --- a/src/testing.rs +++ b/src/testing.rs @@ -209,7 +209,7 @@ pub fn array() -> Array2 { X.slice_mut(s![25..40, 2]).fill(3.); X.slice_mut(s![25..80, 1]).fill(-2.); - X + Array::random_using((100, 5), Uniform::new(0., 1.), &mut rng) + X + Array::random_using((100, 5), Uniform::new(0., 1.).unwrap(), &mut rng) } #[cfg(test)] From c8f90be4cd7d866d081227f4e5b5eadead50fb3a Mon Sep 17 00:00:00 2001 From: Malte Londschien Date: Thu, 11 Dec 2025 15:12:16 -0400 Subject: [PATCH 5/8] Update random tests --- src/segmentation.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/segmentation.rs b/src/segmentation.rs index 6c438e3..76d421f 100644 --- a/src/segmentation.rs +++ b/src/segmentation.rs @@ -181,11 +181,11 @@ mod tests { (50, 100, 62, 3000.0) ])] #[case(SegmentationType::WBS, vec![ - (56, 78, 74, 415.0), - (2, 59, 16, 684.0), - (26, 77, 38, 1836.0), - (22, 80, 36, 1856.0), - (75, 97, 80, 1870.0) + (56, 78, 61, 1452.0), + (12, 77, 28, 1430.0), + (7, 22, 10, 255.0), + (79, 80, 79, 89.0), + (62, 75, 65, 936.0) ])] fn test_generate_segments( #[case] segmentation_type: SegmentationType, From 63d68ed38c58a10b4135c25ff029aaf7a1c2e49b Mon Sep 17 00:00:00 2001 From: Malte Londschien Date: Thu, 11 Dec 2025 15:16:18 -0400 Subject: [PATCH 6/8] Fix breaking change in pyo3 update. --- changeforest-py/src/control.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/changeforest-py/src/control.rs b/changeforest-py/src/control.rs index 6c1ac18..50c7baf 100644 --- a/changeforest-py/src/control.rs +++ b/changeforest-py/src/control.rs @@ -109,8 +109,8 @@ impl PyMaxFeatures { } } -impl FromPyObject<'_> for PyMaxFeatures { - fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult { +impl<'py> FromPyObject<'_, 'py> for PyMaxFeatures { + fn extract(ob: &Bound<'py, PyAny>) -> PyResult { if let Ok(value) = ob.extract::() { Ok(PyMaxFeatures::new(MaxFeatures::Value(value))) } else if let Ok(value) = ob.extract::() { From be3dcb78e613ef19033e5e4ee8d29fdd1dcfdd5b Mon Sep 17 00:00:00 2001 From: Malte Londschien Date: Thu, 11 Dec 2025 15:22:06 -0400 Subject: [PATCH 7/8] adjust control.rs to new pyo3 --- changeforest-py/src/control.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/changeforest-py/src/control.rs b/changeforest-py/src/control.rs index 50c7baf..8f3f7f8 100644 --- a/changeforest-py/src/control.rs +++ b/changeforest-py/src/control.rs @@ -1,9 +1,9 @@ use biosphere::MaxFeatures; use changeforest::Control; use pyo3::exceptions; -use pyo3::prelude::{pyclass, Bound, FromPyObject, PyAny, PyErr, PyResult}; +use pyo3::prelude::{pyclass, FromPyObject, PyAny, PyErr, PyResult}; use pyo3::prelude::{Py, Python}; -use pyo3::types::PyAnyMethods; +use pyo3::Borrowed; pub fn control_from_pyobj(py: Python, obj: Option>) -> PyResult { let mut control = Control::default(); @@ -110,7 +110,9 @@ impl PyMaxFeatures { } impl<'py> FromPyObject<'_, 'py> for PyMaxFeatures { - fn extract(ob: &Bound<'py, PyAny>) -> PyResult { + type Error = PyErr; + + fn extract(ob: Borrowed<'_, 'py, PyAny>) -> PyResult { if let Ok(value) = ob.extract::() { Ok(PyMaxFeatures::new(MaxFeatures::Value(value))) } else if let Ok(value) = ob.extract::() { From eb1b227226ceae2c1e9d38ba48f8b98b5bf7c7df Mon Sep 17 00:00:00 2001 From: Malte Londschien Date: Thu, 11 Dec 2025 15:44:43 -0400 Subject: [PATCH 8/8] Fix python tests. --- changeforest-py/tests/test_changeforest.py | 30 +++++++++++----------- changeforest-py/tests/test_control.py | 11 ++++---- 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/changeforest-py/tests/test_changeforest.py b/changeforest-py/tests/test_changeforest.py index 6a1c764..5bd13ed 100644 --- a/changeforest-py/tests/test_changeforest.py +++ b/changeforest-py/tests/test_changeforest.py @@ -22,11 +22,11 @@ def test_changeforest_repr(iris_dataset): result.__repr__() == """\ best_split max_gain p_value -(0, 150] 50 96.233 0.005 - ¦--(0, 50] 2 -14.191 1 - °--(50, 150] 100 52.799 0.005 - ¦--(50, 100] 53 5.44 0.245 - °--(100, 150] 136 -2.398 0.875\ +(0, 150] 50 95.867 0.005 + ¦--(0, 50] 2 -14.37 1 + °--(50, 150] 100 52.853 0.005 + ¦--(50, 100] 53 5.152 0.27 + °--(100, 150] 102 -6.981 0.9\ """ ) @@ -42,12 +42,12 @@ def test_changeforest_repr_segments(iris_dataset): result.__repr__() == """\ best_split max_gain p_value -(0, 150] 50 95.1 0.005 +(0, 150] 50 94.844 0.005 ¦--(0, 50] - °--(50, 150] 100 52.799 0.005 - ¦--(50, 100] 53 6.892 0.315 - °--(100, 150] 136 -3.516 0.68\ -""" # noqa: W291 + °--(50, 150] 100 52.853 0.005 + ¦--(50, 100] 53 5.152 0.28 + °--(100, 150] 147 -12.303 0.975\ +""" # noqa W291 ) @@ -62,10 +62,10 @@ def test_changeforest_repr_segments2(iris_dataset): result.__repr__() == """\ best_split max_gain p_value -(0, 150] 49 87.462 0.005 - ¦--(0, 49] 2 -8.889 0.995 - °--(49, 150] 102 41.237 0.005 +(0, 150] 49 87.437 0.005 + ¦--(0, 49] 2 -13.76 0.96 + °--(49, 150] 102 34.621 0.005 ¦--(49, 102] - °--(102, 150] 136 1.114 0.36\ -""" # noqa: W291 + °--(102, 150] 138 0.301 0.475\ +""" # noqa W291 ) diff --git a/changeforest-py/tests/test_control.py b/changeforest-py/tests/test_control.py index f12d896..b0a081f 100644 --- a/changeforest-py/tests/test_control.py +++ b/changeforest-py/tests/test_control.py @@ -27,26 +27,25 @@ "change_in_mean", {"minimal_gain_to_split": None}, [50, 100], - ), # log(150) * 4 / 150 + ), # model_selection_alpha ("iris", "bs", "knn", {"model_selection_alpha": 0.001}, []), ("iris", "bs", "knn", {"model_selection_alpha": 0.05}, [50, 100]), # random_forest_n_estimators - # This is impressive and unexpected. - ("iris", "bs", "random_forest", {"random_forest_n_estimators": 1}, [47, 99]), + ("iris", "bs", "random_forest", {"random_forest_n_estimators": 1}, [48]), ("iris", "bs", "random_forest", {"random_forest_n_estimators": 100}, [50, 100]), # Use X_test instead ("X_test", "bs", "random_forest", {"random_forest_n_estimators": 1}, []), ("X_test", "bs", "random_forest", {"random_forest_n_estimators": 1.0}, []), - ("X_test", "bs", "random_forest", {"random_forest_n_estimators": 100}, [5]), + ("X_test", "bs", "random_forest", {"random_forest_n_estimators": 100}, [3, 5]), ("X_correlated", "bs", "random_forest", {"random_forest_max_depth": 1}, []), - ("X_correlated", "bs", "random_forest", {"random_forest_max_depth": 2}, [49]), + ("X_correlated", "bs", "random_forest", {"random_forest_max_depth": 2}, [50]), ( "X_correlated", "bs", "random_forest", {"random_forest_max_features": "sqrt"}, - [49], + [50], ), ("iris", "bs", "random_forest", {"model_selection_n_permutations": 10}, []), ],