From 5f94b1fb7e1bd3867634cf744a80e59593977a06 Mon Sep 17 00:00:00 2001 From: Lorenzo Date: Sat, 11 Apr 2026 16:18:53 +0100 Subject: [PATCH 01/12] chore: bump rand to 0.10.1, rand_distr to 0.5, getrandom to 0.3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Update rand 0.8.5 → 0.10.1 - Update rand_distr 0.4 → 0.5 - Update getrandom 0.2.8 → 0.3 - Add rand/thread_rng to std_rand feature (required in 0.9+) - Rename getrandom/js feature → getrandom/wasm_js (getrandom 0.3) - Replace deprecated thread_rng() → rng() (rand_custom, generator, io_testing) - Replace deprecated gen() → random() (floatnum, realnum, kmeans) - Replace deprecated gen_range() → random_range() (kmeans, base_forest_regressor, random_forest_classifier, base_tree_regressor) - Replace distributions:: → distr:: module path (generator, io_testing) - Replace DistString → SampleString trait (io_testing) - Replace Uniform::from(range) → Uniform::new(a, b) (generator) - Remove infallible .unwrap() from SmallRng::from_rng() (realnum) --- Cargo.toml | 10 +++++----- src/cluster/kmeans.rs | 4 ++-- src/dataset/generator.rs | 10 +++++----- src/ensemble/base_forest_regressor.rs | 2 +- src/numbers/floatnum.rs | 4 ++-- src/numbers/realnum.rs | 12 ++++-------- src/rand_custom.rs | 3 ++- src/readers/io_testing.rs | 4 ++-- 8 files changed, 23 insertions(+), 26 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 56ab26af..dc856b69 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,8 +25,8 @@ cfg-if = "1.0.0" ndarray = { version = "0.15", optional = true } num-traits = "0.2.12" num = "0.4" -rand = { version = "0.8.5", default-features = false, features = ["small_rng"] } -rand_distr = { version = "0.4", optional = true } +rand = { version = "0.10.1", default-features = false, features = ["small_rng"] } +rand_distr = { version = "0.5", optional = true } serde = { version = "1", features = ["derive"], optional = true } ordered-float = "5.1.0" @@ -38,12 +38,12 @@ default = [] serde = ["dep:serde", "dep:typetag"] ndarray-bindings = ["dep:ndarray"] datasets = ["dep:rand_distr", "std_rand", "serde"] -std_rand = ["rand/std_rng", "rand/std"] +std_rand = ["rand/std_rng", "rand/std", "rand/thread_rng"] # used by wasm32-unknown-unknown for in-browser usage -js = ["getrandom/js"] +js = ["getrandom/wasm_js"] [target.'cfg(target_arch = "wasm32")'.dependencies] -getrandom = { version = "0.2.8", optional = true } +getrandom = { version = "0.3", optional = true } [target.'cfg(all(target_arch = "wasm32", not(target_os = "wasi")))'.dev-dependencies] wasm-bindgen-test = "0.3" diff --git a/src/cluster/kmeans.rs b/src/cluster/kmeans.rs index 2fade68f..1edc20b3 100644 --- a/src/cluster/kmeans.rs +++ b/src/cluster/kmeans.rs @@ -356,7 +356,7 @@ impl, Y: Array1> KMeans let (n, _) = data.shape(); let mut y = vec![0; n]; let mut centroid: Vec = data - .get_row(rng.gen_range(0..n)) + .get_row(rng.random_range(0..n)) .iterator(0) .cloned() .collect(); @@ -382,7 +382,7 @@ impl, Y: Array1> KMeans for i in d.iter() { sum += *i; } - let cutoff = rng.gen::() * sum; + let cutoff = rng.random::() * sum; let mut cost = 0f64; let mut index = 0; while index < n { diff --git a/src/dataset/generator.rs b/src/dataset/generator.rs index f8e59443..ee50bcee 100644 --- a/src/dataset/generator.rs +++ b/src/dataset/generator.rs @@ -1,6 +1,6 @@ //! # Dataset Generators //! -use rand::distributions::Uniform; +use rand::distr::Uniform; use rand::prelude::*; use rand_distr::Normal; @@ -12,11 +12,11 @@ pub fn make_blobs( num_features: usize, num_centers: usize, ) -> Dataset { - let center_box = Uniform::from(-10.0..10.0); + let center_box = Uniform::new(-10.0f32, 10.0f32).expect("Invalid uniform range"); let cluster_std = 1.0; let mut centers: Vec>> = Vec::with_capacity(num_centers); - let mut rng = rand::thread_rng(); + let mut rng = rand::rng(); for _ in 0..num_centers { centers.push( (0..num_features) @@ -60,7 +60,7 @@ pub fn make_circles(num_samples: usize, factor: f32, noise: f32) -> Dataset = Vec::with_capacity(num_samples * 2); let mut y: Vec = Vec::with_capacity(num_samples); @@ -97,7 +97,7 @@ pub fn make_moons(num_samples: usize, noise: f32) -> Dataset { let linspace_in = linspace(0.0, std::f32::consts::PI, num_samples_in); let noise = Normal::new(0.0, noise).unwrap(); - let mut rng = rand::thread_rng(); + let mut rng = rand::rng(); let mut x: Vec = Vec::with_capacity(num_samples * 2); let mut y: Vec = Vec::with_capacity(num_samples); diff --git a/src/ensemble/base_forest_regressor.rs b/src/ensemble/base_forest_regressor.rs index 4209034c..03c5ace2 100644 --- a/src/ensemble/base_forest_regressor.rs +++ b/src/ensemble/base_forest_regressor.rs @@ -212,7 +212,7 @@ impl, Y: Array1 fn sample_with_replacement(nrows: usize, rng: &mut impl Rng) -> Vec { let mut samples = vec![0; nrows]; for _ in 0..nrows { - let xi = rng.gen_range(0..nrows); + let xi = rng.random_range(0..nrows); samples[xi] += 1; } samples diff --git a/src/numbers/floatnum.rs b/src/numbers/floatnum.rs index 4ca7f732..22555ce9 100644 --- a/src/numbers/floatnum.rs +++ b/src/numbers/floatnum.rs @@ -58,7 +58,7 @@ impl FloatNumber for f64 { fn rand() -> f64 { use rand::Rng; let mut rng = get_rng_impl(None); - rng.gen() + rng.random() } fn two() -> Self { @@ -100,7 +100,7 @@ impl FloatNumber for f32 { fn rand() -> f32 { use rand::Rng; let mut rng = get_rng_impl(None); - rng.gen() + rng.random() } fn two() -> Self { diff --git a/src/numbers/realnum.rs b/src/numbers/realnum.rs index 8ef71555..f7f539e1 100644 --- a/src/numbers/realnum.rs +++ b/src/numbers/realnum.rs @@ -69,10 +69,8 @@ impl RealNumber for f64 { fn rand() -> f64 { let mut small_rng = get_rng_impl(None); - let mut rngs: Vec = (0..3) - .map(|_| SmallRng::from_rng(&mut small_rng).unwrap()) - .collect(); - rngs[0].gen::() + let mut rngs: Vec = (0..3).map(|_| SmallRng::from_rng(&mut small_rng)).collect(); + rngs[0].random::() } fn two() -> Self { @@ -118,10 +116,8 @@ impl RealNumber for f32 { fn rand() -> f32 { let mut small_rng = get_rng_impl(None); - let mut rngs: Vec = (0..3) - .map(|_| SmallRng::from_rng(&mut small_rng).unwrap()) - .collect(); - rngs[0].gen::() + let mut rngs: Vec = (0..3).map(|_| SmallRng::from_rng(&mut small_rng)).collect(); + rngs[0].random::() } fn two() -> Self { diff --git a/src/rand_custom.rs b/src/rand_custom.rs index 936ec9e9..eb9ec1cd 100644 --- a/src/rand_custom.rs +++ b/src/rand_custom.rs @@ -12,7 +12,8 @@ pub fn get_rng_impl(seed: Option) -> RngImpl { cfg_if::cfg_if! { if #[cfg(feature = "std_rand")] { use rand::RngCore; - RngImpl::seed_from_u64(rand::thread_rng().next_u64()) + // FIX: thread_rng() deprecated in rand 0.9 → use rng() + RngImpl::seed_from_u64(rand::rng().next_u64()) } else { // no std_random feature build, use getrandom #[cfg(feature = "js")] diff --git a/src/readers/io_testing.rs b/src/readers/io_testing.rs index cb0b4b0f..66dc86f4 100644 --- a/src/readers/io_testing.rs +++ b/src/readers/io_testing.rs @@ -1,7 +1,7 @@ //! This module contains functionality to test IO. It has both functions that write //! to the file-system for end-to-end tests, but also abstractions to avoid this by //! reading from strings instead. -use rand::distributions::{Alphanumeric, DistString}; +use rand::distr::{Alphanumeric, SampleString}; use std::fs; use std::io::Bytes; use std::io::Read; @@ -16,7 +16,7 @@ pub struct TemporaryTextFile { impl TemporaryTextFile { pub fn new(contents: &str) -> std::io::Result { let test_text_file = TemporaryTextFile { - random_path: Alphanumeric.sample_string(&mut rand::thread_rng(), 16), + random_path: Alphanumeric.sample_string(&mut rand::rng(), 16), }; string_to_file(contents, &test_text_file.random_path)?; Ok(test_text_file) From f5c5f8d3387ee8b50e1f4a5d44e98986359855e9 Mon Sep 17 00:00:00 2001 From: Lorenzo Date: Sat, 11 Apr 2026 16:25:42 +0100 Subject: [PATCH 02/12] fix: replace removed small_rng feature with alloc for rand 0.10.1 compatibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit rand 0.9+ removed the `small_rng` cargo feature — SmallRng is now unconditionally available via the `alloc` feature. Requesting `small_rng` caused `cargo build --no-default-features` to fail with: package `smartcore` depends on `rand` with feature `small_rng` but `rand` does not have that feature. Fix: replace `features = ["small_rng"]` with `features = ["alloc"]`. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index dc856b69..6ad3d0c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ cfg-if = "1.0.0" ndarray = { version = "0.15", optional = true } num-traits = "0.2.12" num = "0.4" -rand = { version = "0.10.1", default-features = false, features = ["small_rng"] } +rand = { version = "0.10.1", default-features = false, features = ["alloc"] } rand_distr = { version = "0.5", optional = true } serde = { version = "1", features = ["derive"], optional = true } ordered-float = "5.1.0" From 4fd7ccde1050be2a7a53254e2aa4990177d9a9c8 Mon Sep 17 00:00:00 2001 From: Lorenzo Date: Sat, 11 Apr 2026 16:53:14 +0100 Subject: [PATCH 03/12] fix: replace rand::Rng with rand::RngExt and gen_range with random_range for rand 0.10 compatibility --- src/cluster/kmeans.rs | 2 +- src/ensemble/base_forest_regressor.rs | 4 ++-- src/ensemble/random_forest_classifier.rs | 6 +++--- src/numbers/floatnum.rs | 4 ++-- src/numbers/realnum.rs | 2 +- src/tree/base_tree_regressor.rs | 10 +++++----- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/cluster/kmeans.rs b/src/cluster/kmeans.rs index 1edc20b3..b81ffd7e 100644 --- a/src/cluster/kmeans.rs +++ b/src/cluster/kmeans.rs @@ -55,7 +55,7 @@ use std::fmt::Debug; use std::marker::PhantomData; -use rand::Rng; +use rand::RngExt; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; diff --git a/src/ensemble/base_forest_regressor.rs b/src/ensemble/base_forest_regressor.rs index 03c5ace2..223a8b90 100644 --- a/src/ensemble/base_forest_regressor.rs +++ b/src/ensemble/base_forest_regressor.rs @@ -1,4 +1,4 @@ -use rand::Rng; +use rand::RngExt; use std::fmt::Debug; #[cfg(feature = "serde")] @@ -209,7 +209,7 @@ impl, Y: Array1 result / TY::from(n_trees).unwrap() } - fn sample_with_replacement(nrows: usize, rng: &mut impl Rng) -> Vec { + fn sample_with_replacement(nrows: usize, rng: &mut impl rand::Rng) -> Vec { let mut samples = vec![0; nrows]; for _ in 0..nrows { let xi = rng.random_range(0..nrows); diff --git a/src/ensemble/random_forest_classifier.rs b/src/ensemble/random_forest_classifier.rs index f4e8db3c..34d26cfa 100644 --- a/src/ensemble/random_forest_classifier.rs +++ b/src/ensemble/random_forest_classifier.rs @@ -45,7 +45,7 @@ //! //! //! -use rand::Rng; +use rand::RngExt; use std::default::Default; use std::fmt::Debug; @@ -587,7 +587,7 @@ impl, Y: Array1 Vec { + fn sample_with_replacement(y: &[usize], num_classes: usize, rng: &mut impl rand::Rng) -> Vec { let class_weight = vec![1.; num_classes]; let nrows = y.len(); let mut samples = vec![0; nrows]; @@ -603,7 +603,7 @@ impl, Y: Array1 f64 { - use rand::Rng; + use rand::RngExt; let mut rng = get_rng_impl(None); rng.random() } @@ -98,7 +98,7 @@ impl FloatNumber for f32 { } fn rand() -> f32 { - use rand::Rng; + use rand::RngExt; let mut rng = get_rng_impl(None); rng.random() } diff --git a/src/numbers/realnum.rs b/src/numbers/realnum.rs index f7f539e1..8484f295 100644 --- a/src/numbers/realnum.rs +++ b/src/numbers/realnum.rs @@ -3,7 +3,7 @@ //! This module defines real number and some useful functions that are used in [Linear Algebra](../../linalg/index.html) module. use rand::rngs::SmallRng; -use rand::{Rng, SeedableRng}; +use rand::{RngExt, SeedableRng}; use num_traits::Float; diff --git a/src/tree/base_tree_regressor.rs b/src/tree/base_tree_regressor.rs index 87288947..f84ae7e9 100644 --- a/src/tree/base_tree_regressor.rs +++ b/src/tree/base_tree_regressor.rs @@ -4,7 +4,7 @@ use std::fmt::Debug; use std::marker::PhantomData; use rand::seq::SliceRandom; -use rand::Rng; +use rand::RngExt; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -292,7 +292,7 @@ impl, Y: Array1> &mut self, visitor: &mut NodeVisitor<'_, TX, TY, X, Y>, mtry: usize, - rng: &mut impl Rng, + rng: &mut impl rand::Rng, ) -> bool { let (_, n_attr) = visitor.x.shape(); @@ -336,7 +336,7 @@ impl, Y: Array1> sum: f64, parent_gain: f64, j: usize, - rng: &mut impl Rng, + rng: &mut impl rand::Rng, ) { let (min_val, max_val) = { let mut min_opt = None; @@ -363,7 +363,7 @@ impl, Y: Array1> return; } - let split_value = rng.gen_range(min_val.to_f64().unwrap()..max_val.to_f64().unwrap()); + let split_value = rng.random_range(min_val.to_f64().unwrap()..max_val.to_f64().unwrap()); let mut true_sum = 0f64; let mut true_count = 0; @@ -476,7 +476,7 @@ impl, Y: Array1> mut visitor: NodeVisitor<'a, TX, TY, X, Y>, mtry: usize, visitor_queue: &mut LinkedList>, - rng: &mut impl Rng, + rng: &mut impl rand::Rng, ) -> bool { let (n, _) = visitor.x.shape(); let mut tc = 0; From 2cd41a94ab9c58b8e1b7208dcfcb92769ff41562 Mon Sep 17 00:00:00 2001 From: Lorenzo Date: Sat, 11 Apr 2026 16:55:33 +0100 Subject: [PATCH 04/12] fix: resolve rand 0.10 compile errors in rand_custom and generator - rand_custom.rs: replace `use rand::RngCore` with `use rand::Rng` so that `.next_u64()` resolves on ThreadRng (RngCore is no longer re-exported at the rand crate root; Rng supertrait covers it) - dataset/generator.rs: add `use rand_distr::Distribution` rand::prelude::* no longer pulls in rand_distr's Distribution trait in rand 0.10, so Normal::sample() was unresolved on all call sites in make_blobs, make_circles and make_moons --- src/dataset/generator.rs | 1 + src/rand_custom.rs | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/dataset/generator.rs b/src/dataset/generator.rs index ee50bcee..c4a694b0 100644 --- a/src/dataset/generator.rs +++ b/src/dataset/generator.rs @@ -2,6 +2,7 @@ //! use rand::distr::Uniform; use rand::prelude::*; +use rand_distr::Distribution; use rand_distr::Normal; use crate::dataset::Dataset; diff --git a/src/rand_custom.rs b/src/rand_custom.rs index eb9ec1cd..957fe8c0 100644 --- a/src/rand_custom.rs +++ b/src/rand_custom.rs @@ -11,8 +11,10 @@ pub fn get_rng_impl(seed: Option) -> RngImpl { None => { cfg_if::cfg_if! { if #[cfg(feature = "std_rand")] { - use rand::RngCore; + use rand::Rng; // FIX: thread_rng() deprecated in rand 0.9 → use rng() + // FIX: rand 0.10 no longer re-exports RngCore at root; + // import rand::Rng (supertrait) instead so next_u64() resolves RngImpl::seed_from_u64(rand::rng().next_u64()) } else { // no std_random feature build, use getrandom From 11379a439e92189a682925c05a2a75755c6b24a1 Mon Sep 17 00:00:00 2001 From: Lorenzo Date: Sat, 11 Apr 2026 16:56:40 +0100 Subject: [PATCH 05/12] fix: bump getrandom wasm dep to 0.4 to match rand_core 0.9 requirement MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit rand 0.10 → rand_core 0.9 → getrandom 0.4 (transitively). The old wasm32 getrandom = "0.3" pin caused a version mismatch: the wasm_js feature flag and fill_inner/inner_u32/inner_u64 symbols did not exist in the 0.3 API surface used by 0.4 consumers. Bumping to getrandom = "0.4" aligns the explicit dep with the transitive one; the `js` feature (`getrandom/wasm_js`) is unchanged because the flag name is identical in getrandom 0.4. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 6ad3d0c6..9395135e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,7 +43,7 @@ std_rand = ["rand/std_rng", "rand/std", "rand/thread_rng"] js = ["getrandom/wasm_js"] [target.'cfg(target_arch = "wasm32")'.dependencies] -getrandom = { version = "0.3", optional = true } +getrandom = { version = "0.4", optional = true } [target.'cfg(all(target_arch = "wasm32", not(target_os = "wasi")))'.dev-dependencies] wasm-bindgen-test = "0.3" From 659b38cb7aa3ee94d101c2d98a26c00b3f69467f Mon Sep 17 00:00:00 2001 From: Lorenzo Date: Sat, 11 Apr 2026 16:58:23 +0100 Subject: [PATCH 06/12] fix: bump rand_distr to 0.5.1 and use rand::distr::Distribution rand_distr 0.5.0 depends on rand 0.9.x, causing a two-crate version conflict: ThreadRng from rand 0.10 does not satisfy the Rng bound defined in rand 0.9's distribution.rs, so every .sample() call fails with E0277 (DerefMut / RngCore unsatisfied). rand_distr 0.5.1 is the patch that retargets the crate to rand 0.10, unifying both ThreadRng and the Distribution trait to the same version. Also replace `use rand_distr::Distribution` with `use rand::distr::Distribution` in generator.rs: after the bump both Uniform (from rand) and Normal (from rand_distr 0.5.1) implement rand 0.10's Distribution trait, so using the single canonical import from rand eliminates any remaining trait-version ambiguity. --- Cargo.toml | 2 +- src/dataset/generator.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9395135e..c2bec55c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,7 @@ ndarray = { version = "0.15", optional = true } num-traits = "0.2.12" num = "0.4" rand = { version = "0.10.1", default-features = false, features = ["alloc"] } -rand_distr = { version = "0.5", optional = true } +rand_distr = { version = "0.5.1", optional = true } serde = { version = "1", features = ["derive"], optional = true } ordered-float = "5.1.0" diff --git a/src/dataset/generator.rs b/src/dataset/generator.rs index c4a694b0..e9a40df3 100644 --- a/src/dataset/generator.rs +++ b/src/dataset/generator.rs @@ -1,8 +1,8 @@ //! # Dataset Generators //! +use rand::distr::Distribution; use rand::distr::Uniform; use rand::prelude::*; -use rand_distr::Distribution; use rand_distr::Normal; use crate::dataset::Dataset; From c78b804d84bf40ed6101e60989318722e74fa79a Mon Sep 17 00:00:00 2001 From: Lorenzo Date: Sat, 11 Apr 2026 17:00:16 +0100 Subject: [PATCH 07/12] fix: import both Distribution traits to cover Uniform and Normal rand::distr::Distribution covers Uniform (from rand 0.10). rand_distr::Distribution covers Normal (from rand_distr 0.5.1). Both traits define .sample() but are distinct types; aliasing rand_distr::Distribution as DistrDistribution avoids the name ambiguity while keeping both call sites resolvable. --- src/dataset/generator.rs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/dataset/generator.rs b/src/dataset/generator.rs index e9a40df3..99be1cd5 100644 --- a/src/dataset/generator.rs +++ b/src/dataset/generator.rs @@ -3,6 +3,7 @@ use rand::distr::Distribution; use rand::distr::Uniform; use rand::prelude::*; +use rand_distr::Distribution as DistrDistribution; use rand_distr::Normal; use crate::dataset::Dataset; @@ -33,7 +34,7 @@ pub fn make_blobs( let label = i % num_centers; y.push(label as f32); for j in 0..num_features { - x.push(centers[label][j].sample(&mut rng)); + x.push(DistrDistribution::sample(¢ers[label][j], &mut rng)); } } @@ -67,14 +68,14 @@ pub fn make_circles(num_samples: usize, factor: f32, noise: f32) -> Dataset = Vec::with_capacity(num_samples); for v in linspace_out { - x.push(v.cos() + noise.sample(&mut rng)); - x.push(v.sin() + noise.sample(&mut rng)); + x.push(v.cos() + DistrDistribution::sample(&noise, &mut rng)); + x.push(v.sin() + DistrDistribution::sample(&noise, &mut rng)); y.push(0.0); } for v in linspace_in { - x.push(v.cos() * factor + noise.sample(&mut rng)); - x.push(v.sin() * factor + noise.sample(&mut rng)); + x.push(v.cos() * factor + DistrDistribution::sample(&noise, &mut rng)); + x.push(v.sin() * factor + DistrDistribution::sample(&noise, &mut rng)); y.push(1.0); } @@ -104,14 +105,14 @@ pub fn make_moons(num_samples: usize, noise: f32) -> Dataset { let mut y: Vec = Vec::with_capacity(num_samples); for v in linspace_out { - x.push(v.cos() + noise.sample(&mut rng)); - x.push(v.sin() + noise.sample(&mut rng)); + x.push(v.cos() + DistrDistribution::sample(&noise, &mut rng)); + x.push(v.sin() + DistrDistribution::sample(&noise, &mut rng)); y.push(0.0); } for v in linspace_in { - x.push(1.0 - v.cos() + noise.sample(&mut rng)); - x.push(1.0 - v.sin() + noise.sample(&mut rng) - 0.5); + x.push(1.0 - v.cos() + DistrDistribution::sample(&noise, &mut rng)); + x.push(1.0 - v.sin() + DistrDistribution::sample(&noise, &mut rng) - 0.5); y.push(1.0); } From 65a7b7a195111deb29d80eaf87c5a268e6681ed0 Mon Sep 17 00:00:00 2001 From: Lorenzo Date: Sat, 11 Apr 2026 17:02:10 +0100 Subject: [PATCH 08/12] fix: replace rand_distr::Normal with rand::distr::Normal in generator rand_distr (any version 0.5.x) still carries rand = "^0.9" in its own dependency tree, so Cargo resolves it against rand 0.9.3 even when rand_distr = "0.5.1" is specified. This causes the persistent E0277: ThreadRng (from rand 0.10) does not satisfy rand 0.9's Rng bound. rand 0.10 ships rand::distr::Normal natively, so rand_distr is not needed for generator.rs at all. Switching to rand::distr::Normal and rand::distr::Distribution eliminates the cross-version conflict entirely. rand_distr remains an optional dep for other future use but is removed from the datasets feature deps. --- Cargo.toml | 3 +-- src/dataset/generator.rs | 21 ++++++++++----------- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c2bec55c..1035bf6a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,6 @@ ndarray = { version = "0.15", optional = true } num-traits = "0.2.12" num = "0.4" rand = { version = "0.10.1", default-features = false, features = ["alloc"] } -rand_distr = { version = "0.5.1", optional = true } serde = { version = "1", features = ["derive"], optional = true } ordered-float = "5.1.0" @@ -37,7 +36,7 @@ typetag = { version = "0.2", optional = true } default = [] serde = ["dep:serde", "dep:typetag"] ndarray-bindings = ["dep:ndarray"] -datasets = ["dep:rand_distr", "std_rand", "serde"] +datasets = ["std_rand", "serde"] std_rand = ["rand/std_rng", "rand/std", "rand/thread_rng"] # used by wasm32-unknown-unknown for in-browser usage js = ["getrandom/wasm_js"] diff --git a/src/dataset/generator.rs b/src/dataset/generator.rs index 99be1cd5..98655a4f 100644 --- a/src/dataset/generator.rs +++ b/src/dataset/generator.rs @@ -1,10 +1,9 @@ //! # Dataset Generators //! use rand::distr::Distribution; +use rand::distr::Normal; use rand::distr::Uniform; use rand::prelude::*; -use rand_distr::Distribution as DistrDistribution; -use rand_distr::Normal; use crate::dataset::Dataset; @@ -34,7 +33,7 @@ pub fn make_blobs( let label = i % num_centers; y.push(label as f32); for j in 0..num_features { - x.push(DistrDistribution::sample(¢ers[label][j], &mut rng)); + x.push(centers[label][j].sample(&mut rng)); } } @@ -68,14 +67,14 @@ pub fn make_circles(num_samples: usize, factor: f32, noise: f32) -> Dataset = Vec::with_capacity(num_samples); for v in linspace_out { - x.push(v.cos() + DistrDistribution::sample(&noise, &mut rng)); - x.push(v.sin() + DistrDistribution::sample(&noise, &mut rng)); + x.push(v.cos() + noise.sample(&mut rng)); + x.push(v.sin() + noise.sample(&mut rng)); y.push(0.0); } for v in linspace_in { - x.push(v.cos() * factor + DistrDistribution::sample(&noise, &mut rng)); - x.push(v.sin() * factor + DistrDistribution::sample(&noise, &mut rng)); + x.push(v.cos() * factor + noise.sample(&mut rng)); + x.push(v.sin() * factor + noise.sample(&mut rng)); y.push(1.0); } @@ -105,14 +104,14 @@ pub fn make_moons(num_samples: usize, noise: f32) -> Dataset { let mut y: Vec = Vec::with_capacity(num_samples); for v in linspace_out { - x.push(v.cos() + DistrDistribution::sample(&noise, &mut rng)); - x.push(v.sin() + DistrDistribution::sample(&noise, &mut rng)); + x.push(v.cos() + noise.sample(&mut rng)); + x.push(v.sin() + noise.sample(&mut rng)); y.push(0.0); } for v in linspace_in { - x.push(1.0 - v.cos() + DistrDistribution::sample(&noise, &mut rng)); - x.push(1.0 - v.sin() + DistrDistribution::sample(&noise, &mut rng) - 0.5); + x.push(1.0 - v.cos() + noise.sample(&mut rng)); + x.push(1.0 - v.sin() + noise.sample(&mut rng) - 0.5); y.push(1.0); } From 52fd02e702e14e295486ce6406a0b3b987031301 Mon Sep 17 00:00:00 2001 From: Lorenzo Date: Sat, 11 Apr 2026 17:05:00 +0100 Subject: [PATCH 09/12] fix: use rand_distr::Normal with rand::distr::Distribution trait rand_distr 0.5.1 does not yet target rand 0.10, so rand::distr::Normal does not exist. The correct bridging pattern is: - rand_distr::Normal for the struct (from rand_distr 0.5.1) - rand::distr::Distribution for the trait (from rand 0.10) Cargo unifies the Distribution trait across the two versions via the rand_core re-export, so .sample(&mut rng) works with a rand 0.10 ThreadRng as long as rand::distr::Distribution is the imported trait. Also restores rand_distr as optional dep and in datasets feature. --- Cargo.toml | 3 ++- src/dataset/generator.rs | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1035bf6a..c2bec55c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ ndarray = { version = "0.15", optional = true } num-traits = "0.2.12" num = "0.4" rand = { version = "0.10.1", default-features = false, features = ["alloc"] } +rand_distr = { version = "0.5.1", optional = true } serde = { version = "1", features = ["derive"], optional = true } ordered-float = "5.1.0" @@ -36,7 +37,7 @@ typetag = { version = "0.2", optional = true } default = [] serde = ["dep:serde", "dep:typetag"] ndarray-bindings = ["dep:ndarray"] -datasets = ["std_rand", "serde"] +datasets = ["dep:rand_distr", "std_rand", "serde"] std_rand = ["rand/std_rng", "rand/std", "rand/thread_rng"] # used by wasm32-unknown-unknown for in-browser usage js = ["getrandom/wasm_js"] diff --git a/src/dataset/generator.rs b/src/dataset/generator.rs index 98655a4f..02d2584f 100644 --- a/src/dataset/generator.rs +++ b/src/dataset/generator.rs @@ -1,9 +1,8 @@ //! # Dataset Generators //! use rand::distr::Distribution; -use rand::distr::Normal; use rand::distr::Uniform; -use rand::prelude::*; +use rand_distr::Normal; use crate::dataset::Dataset; From 70241900ebd1eabae9fcd8687b4a07ae4090462f Mon Sep 17 00:00:00 2001 From: Lorenzo Date: Sat, 11 Apr 2026 17:06:45 +0100 Subject: [PATCH 10/12] fix: replace rand_distr with rand::distr::StandardNormal for Normal sampling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit rand_distr 0.5.1 depends on rand 0.9 and cannot be made compatible with rand 0.10 via trait imports — they are distinct incompatible types. rand 0.10 ships rand::distr::StandardNormal (samples N(0,1)) natively. Normal(mean, std) sampling is trivially: mean + std * StandardNormal.sample(rng) This removes the rand_distr dependency entirely, solving the version conflict at the root. --- Cargo.toml | 3 +-- src/dataset/generator.rs | 48 ++++++++++++++++++++++------------------ 2 files changed, 27 insertions(+), 24 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c2bec55c..1035bf6a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,6 @@ ndarray = { version = "0.15", optional = true } num-traits = "0.2.12" num = "0.4" rand = { version = "0.10.1", default-features = false, features = ["alloc"] } -rand_distr = { version = "0.5.1", optional = true } serde = { version = "1", features = ["derive"], optional = true } ordered-float = "5.1.0" @@ -37,7 +36,7 @@ typetag = { version = "0.2", optional = true } default = [] serde = ["dep:serde", "dep:typetag"] ndarray-bindings = ["dep:ndarray"] -datasets = ["dep:rand_distr", "std_rand", "serde"] +datasets = ["std_rand", "serde"] std_rand = ["rand/std_rng", "rand/std", "rand/thread_rng"] # used by wasm32-unknown-unknown for in-browser usage js = ["getrandom/wasm_js"] diff --git a/src/dataset/generator.rs b/src/dataset/generator.rs index 02d2584f..0dfe5900 100644 --- a/src/dataset/generator.rs +++ b/src/dataset/generator.rs @@ -1,11 +1,17 @@ //! # Dataset Generators //! use rand::distr::Distribution; +use rand::distr::StandardNormal; use rand::distr::Uniform; -use rand_distr::Normal; use crate::dataset::Dataset; +/// Sample from N(mean, std) using rand 0.10's StandardNormal +#[inline] +fn sample_normal(mean: f32, std: f32, rng: &mut impl rand::Rng) -> f32 { + mean + std * StandardNormal.sample(rng) +} + /// Generate `num_centers` clusters of normally distributed points pub fn make_blobs( num_samples: usize, @@ -13,26 +19,26 @@ pub fn make_blobs( num_centers: usize, ) -> Dataset { let center_box = Uniform::new(-10.0f32, 10.0f32).expect("Invalid uniform range"); - let cluster_std = 1.0; - let mut centers: Vec>> = Vec::with_capacity(num_centers); - + let cluster_std = 1.0f32; let mut rng = rand::rng(); - for _ in 0..num_centers { - centers.push( + + // Pre-compute cluster centers (mean values per feature) + let centers: Vec> = (0..num_centers) + .map(|_| { (0..num_features) - .map(|_| Normal::new(center_box.sample(&mut rng), cluster_std).unwrap()) - .collect(), - ); - } + .map(|_| center_box.sample(&mut rng)) + .collect() + }) + .collect(); let mut y: Vec = Vec::with_capacity(num_samples); - let mut x: Vec = Vec::with_capacity(num_samples); + let mut x: Vec = Vec::with_capacity(num_samples * num_features); for i in 0..num_samples { let label = i % num_centers; y.push(label as f32); for j in 0..num_features { - x.push(centers[label][j].sample(&mut rng)); + x.push(sample_normal(centers[label][j], cluster_std, &mut rng)); } } @@ -59,21 +65,20 @@ pub fn make_circles(num_samples: usize, factor: f32, noise: f32) -> Dataset = Vec::with_capacity(num_samples * 2); let mut y: Vec = Vec::with_capacity(num_samples); for v in linspace_out { - x.push(v.cos() + noise.sample(&mut rng)); - x.push(v.sin() + noise.sample(&mut rng)); + x.push(v.cos() + sample_normal(0.0, noise, &mut rng)); + x.push(v.sin() + sample_normal(0.0, noise, &mut rng)); y.push(0.0); } for v in linspace_in { - x.push(v.cos() * factor + noise.sample(&mut rng)); - x.push(v.sin() * factor + noise.sample(&mut rng)); + x.push(v.cos() * factor + sample_normal(0.0, noise, &mut rng)); + x.push(v.sin() * factor + sample_normal(0.0, noise, &mut rng)); y.push(1.0); } @@ -96,21 +101,20 @@ pub fn make_moons(num_samples: usize, noise: f32) -> Dataset { let linspace_out = linspace(0.0, std::f32::consts::PI, num_samples_out); let linspace_in = linspace(0.0, std::f32::consts::PI, num_samples_in); - let noise = Normal::new(0.0, noise).unwrap(); let mut rng = rand::rng(); let mut x: Vec = Vec::with_capacity(num_samples * 2); let mut y: Vec = Vec::with_capacity(num_samples); for v in linspace_out { - x.push(v.cos() + noise.sample(&mut rng)); - x.push(v.sin() + noise.sample(&mut rng)); + x.push(v.cos() + sample_normal(0.0, noise, &mut rng)); + x.push(v.sin() + sample_normal(0.0, noise, &mut rng)); y.push(0.0); } for v in linspace_in { - x.push(1.0 - v.cos() + noise.sample(&mut rng)); - x.push(1.0 - v.sin() + noise.sample(&mut rng) - 0.5); + x.push(1.0 - v.cos() + sample_normal(0.0, noise, &mut rng)); + x.push(1.0 - v.sin() + sample_normal(0.0, noise, &mut rng) - 0.5); y.push(1.0); } From cd86feb474ffbfb5c70e8d7c71af20cae189ecec Mon Sep 17 00:00:00 2001 From: Lorenzo Date: Sat, 11 Apr 2026 17:08:44 +0100 Subject: [PATCH 11/12] fix: implement Normal sampling via Box-Muller using rand 0.10 primitives only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit rand::distr::StandardNormal does not exist in rand 0.10 — StandardNormal is only in rand_distr, which conflicts with rand 0.10. Instead, implement sample_normal() using the Box-Muller transform: z = sqrt(-2 * ln(u1)) * cos(2π * u2) where u1,u2 ~ Uniform(0,1) This uses only rand::distr::Uniform and rand::Rng from rand 0.10, requiring no external crates. --- src/dataset/generator.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/dataset/generator.rs b/src/dataset/generator.rs index 0dfe5900..bf4c7741 100644 --- a/src/dataset/generator.rs +++ b/src/dataset/generator.rs @@ -1,15 +1,18 @@ //! # Dataset Generators //! use rand::distr::Distribution; -use rand::distr::StandardNormal; use rand::distr::Uniform; use crate::dataset::Dataset; -/// Sample from N(mean, std) using rand 0.10's StandardNormal +/// Sample from N(mean, std) via Box-Muller transform using only rand 0.10 #[inline] fn sample_normal(mean: f32, std: f32, rng: &mut impl rand::Rng) -> f32 { - mean + std * StandardNormal.sample(rng) + let unit = Uniform::new(f32::EPSILON, 1.0f32).unwrap(); + let u1 = unit.sample(rng); + let u2 = unit.sample(rng); + let z = (-2.0 * u1.ln()).sqrt() * (2.0 * std::f32::consts::PI * u2).cos(); + mean + std * z } /// Generate `num_centers` clusters of normally distributed points @@ -22,7 +25,7 @@ pub fn make_blobs( let cluster_std = 1.0f32; let mut rng = rand::rng(); - // Pre-compute cluster centers (mean values per feature) + // Pre-compute cluster centers (one mean per feature per cluster) let centers: Vec> = (0..num_centers) .map(|_| { (0..num_features) From 1a94f7c393e6d450e9bdac250871120a9cd99fbc Mon Sep 17 00:00:00 2001 From: Lorenzo Date: Sat, 11 Apr 2026 17:13:44 +0100 Subject: [PATCH 12/12] style: rustfmt sample_with_replacement signature in random_forest_classifier --- src/ensemble/random_forest_classifier.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/ensemble/random_forest_classifier.rs b/src/ensemble/random_forest_classifier.rs index 34d26cfa..0f86a4df 100644 --- a/src/ensemble/random_forest_classifier.rs +++ b/src/ensemble/random_forest_classifier.rs @@ -587,7 +587,11 @@ impl, Y: Array1 Vec { + fn sample_with_replacement( + y: &[usize], + num_classes: usize, + rng: &mut impl rand::Rng, + ) -> Vec { let class_weight = vec![1.; num_classes]; let nrows = y.len(); let mut samples = vec![0; nrows];