Skip to content

Commit 66f77af

Browse files
authored
chore: bump rand to 0.10.1, rand_distr to 0.5, getrandom to 0.3 (#369)
* chore: bump rand to 0.10.1, remove rand_distr from default, getrandom to 0.3 - Update rand 0.8.5 → 0.10.1 - 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) * fix: replace removed small_rng feature with alloc for rand 0.10.1 compatibility 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"]`. * fix: replace rand::Rng with rand::RngExt and gen_range with random_range for rand 0.10 compatibility * 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 * fix: bump getrandom wasm dep to 0.4 to match rand_core 0.9 requirement 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. * 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. * 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. * 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. * 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. * fix: replace rand_distr with rand::distr::StandardNormal for Normal sampling 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. * fix: implement Normal sampling via Box-Muller using rand 0.10 primitives only 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. * style: rustfmt sample_with_replacement signature in random_forest_classifier
1 parent 4c275d7 commit 66f77af

10 files changed

Lines changed: 73 additions & 64 deletions

File tree

Cargo.toml

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,7 @@ cfg-if = "1.0.0"
2525
ndarray = { version = "0.15", optional = true }
2626
num-traits = "0.2.12"
2727
num = "0.4"
28-
rand = { version = "0.8.5", default-features = false, features = ["small_rng"] }
29-
rand_distr = { version = "0.4", optional = true }
28+
rand = { version = "0.10.1", default-features = false, features = ["alloc"] }
3029
serde = { version = "1", features = ["derive"], optional = true }
3130
ordered-float = "5.1.0"
3231

@@ -37,13 +36,13 @@ typetag = { version = "0.2", optional = true }
3736
default = []
3837
serde = ["dep:serde", "dep:typetag"]
3938
ndarray-bindings = ["dep:ndarray"]
40-
datasets = ["dep:rand_distr", "std_rand", "serde"]
41-
std_rand = ["rand/std_rng", "rand/std"]
39+
datasets = ["std_rand", "serde"]
40+
std_rand = ["rand/std_rng", "rand/std", "rand/thread_rng"]
4241
# used by wasm32-unknown-unknown for in-browser usage
43-
js = ["getrandom/js"]
42+
js = ["getrandom/wasm_js"]
4443

4544
[target.'cfg(target_arch = "wasm32")'.dependencies]
46-
getrandom = { version = "0.2.8", optional = true }
45+
getrandom = { version = "0.4", optional = true }
4746

4847
[target.'cfg(all(target_arch = "wasm32", not(target_os = "wasi")))'.dev-dependencies]
4948
wasm-bindgen-test = "0.3"

src/cluster/kmeans.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
use std::fmt::Debug;
5656
use std::marker::PhantomData;
5757

58-
use rand::Rng;
58+
use rand::RngExt;
5959
#[cfg(feature = "serde")]
6060
use serde::{Deserialize, Serialize};
6161

@@ -356,7 +356,7 @@ impl<TX: Number, TY: Number, X: Array2<TX>, Y: Array1<TY>> KMeans<TX, TY, X, Y>
356356
let (n, _) = data.shape();
357357
let mut y = vec![0; n];
358358
let mut centroid: Vec<TX> = data
359-
.get_row(rng.gen_range(0..n))
359+
.get_row(rng.random_range(0..n))
360360
.iterator(0)
361361
.cloned()
362362
.collect();
@@ -382,7 +382,7 @@ impl<TX: Number, TY: Number, X: Array2<TX>, Y: Array1<TY>> KMeans<TX, TY, X, Y>
382382
for i in d.iter() {
383383
sum += *i;
384384
}
385-
let cutoff = rng.gen::<f64>() * sum;
385+
let cutoff = rng.random::<f64>() * sum;
386386
let mut cost = 0f64;
387387
let mut index = 0;
388388
while index < n {

src/dataset/generator.rs

Lines changed: 34 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,47 @@
11
//! # Dataset Generators
22
//!
3-
use rand::distributions::Uniform;
4-
use rand::prelude::*;
5-
use rand_distr::Normal;
3+
use rand::distr::Distribution;
4+
use rand::distr::Uniform;
65

76
use crate::dataset::Dataset;
87

8+
/// Sample from N(mean, std) via Box-Muller transform using only rand 0.10
9+
#[inline]
10+
fn sample_normal(mean: f32, std: f32, rng: &mut impl rand::Rng) -> f32 {
11+
let unit = Uniform::new(f32::EPSILON, 1.0f32).unwrap();
12+
let u1 = unit.sample(rng);
13+
let u2 = unit.sample(rng);
14+
let z = (-2.0 * u1.ln()).sqrt() * (2.0 * std::f32::consts::PI * u2).cos();
15+
mean + std * z
16+
}
17+
918
/// Generate `num_centers` clusters of normally distributed points
1019
pub fn make_blobs(
1120
num_samples: usize,
1221
num_features: usize,
1322
num_centers: usize,
1423
) -> Dataset<f32, f32> {
15-
let center_box = Uniform::from(-10.0..10.0);
16-
let cluster_std = 1.0;
17-
let mut centers: Vec<Vec<Normal<f32>>> = Vec::with_capacity(num_centers);
24+
let center_box = Uniform::new(-10.0f32, 10.0f32).expect("Invalid uniform range");
25+
let cluster_std = 1.0f32;
26+
let mut rng = rand::rng();
1827

19-
let mut rng = rand::thread_rng();
20-
for _ in 0..num_centers {
21-
centers.push(
28+
// Pre-compute cluster centers (one mean per feature per cluster)
29+
let centers: Vec<Vec<f32>> = (0..num_centers)
30+
.map(|_| {
2231
(0..num_features)
23-
.map(|_| Normal::new(center_box.sample(&mut rng), cluster_std).unwrap())
24-
.collect(),
25-
);
26-
}
32+
.map(|_| center_box.sample(&mut rng))
33+
.collect()
34+
})
35+
.collect();
2736

2837
let mut y: Vec<f32> = Vec::with_capacity(num_samples);
29-
let mut x: Vec<f32> = Vec::with_capacity(num_samples);
38+
let mut x: Vec<f32> = Vec::with_capacity(num_samples * num_features);
3039

3140
for i in 0..num_samples {
3241
let label = i % num_centers;
3342
y.push(label as f32);
3443
for j in 0..num_features {
35-
x.push(centers[label][j].sample(&mut rng));
44+
x.push(sample_normal(centers[label][j], cluster_std, &mut rng));
3645
}
3746
}
3847

@@ -59,21 +68,20 @@ pub fn make_circles(num_samples: usize, factor: f32, noise: f32) -> Dataset<f32,
5968
let linspace_out = linspace(0.0, 2.0 * std::f32::consts::PI, num_samples_out);
6069
let linspace_in = linspace(0.0, 2.0 * std::f32::consts::PI, num_samples_in);
6170

62-
let noise = Normal::new(0.0, noise).unwrap();
63-
let mut rng = rand::thread_rng();
71+
let mut rng = rand::rng();
6472

6573
let mut x: Vec<f32> = Vec::with_capacity(num_samples * 2);
6674
let mut y: Vec<f32> = Vec::with_capacity(num_samples);
6775

6876
for v in linspace_out {
69-
x.push(v.cos() + noise.sample(&mut rng));
70-
x.push(v.sin() + noise.sample(&mut rng));
77+
x.push(v.cos() + sample_normal(0.0, noise, &mut rng));
78+
x.push(v.sin() + sample_normal(0.0, noise, &mut rng));
7179
y.push(0.0);
7280
}
7381

7482
for v in linspace_in {
75-
x.push(v.cos() * factor + noise.sample(&mut rng));
76-
x.push(v.sin() * factor + noise.sample(&mut rng));
83+
x.push(v.cos() * factor + sample_normal(0.0, noise, &mut rng));
84+
x.push(v.sin() * factor + sample_normal(0.0, noise, &mut rng));
7785
y.push(1.0);
7886
}
7987

@@ -96,21 +104,20 @@ pub fn make_moons(num_samples: usize, noise: f32) -> Dataset<f32, u32> {
96104
let linspace_out = linspace(0.0, std::f32::consts::PI, num_samples_out);
97105
let linspace_in = linspace(0.0, std::f32::consts::PI, num_samples_in);
98106

99-
let noise = Normal::new(0.0, noise).unwrap();
100-
let mut rng = rand::thread_rng();
107+
let mut rng = rand::rng();
101108

102109
let mut x: Vec<f32> = Vec::with_capacity(num_samples * 2);
103110
let mut y: Vec<f32> = Vec::with_capacity(num_samples);
104111

105112
for v in linspace_out {
106-
x.push(v.cos() + noise.sample(&mut rng));
107-
x.push(v.sin() + noise.sample(&mut rng));
113+
x.push(v.cos() + sample_normal(0.0, noise, &mut rng));
114+
x.push(v.sin() + sample_normal(0.0, noise, &mut rng));
108115
y.push(0.0);
109116
}
110117

111118
for v in linspace_in {
112-
x.push(1.0 - v.cos() + noise.sample(&mut rng));
113-
x.push(1.0 - v.sin() + noise.sample(&mut rng) - 0.5);
119+
x.push(1.0 - v.cos() + sample_normal(0.0, noise, &mut rng));
120+
x.push(1.0 - v.sin() + sample_normal(0.0, noise, &mut rng) - 0.5);
114121
y.push(1.0);
115122
}
116123

src/ensemble/base_forest_regressor.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use rand::Rng;
1+
use rand::RngExt;
22
use std::fmt::Debug;
33

44
#[cfg(feature = "serde")]
@@ -209,10 +209,10 @@ impl<TX: Number + FloatNumber + PartialOrd, TY: Number, X: Array2<TX>, Y: Array1
209209
result / TY::from(n_trees).unwrap()
210210
}
211211

212-
fn sample_with_replacement(nrows: usize, rng: &mut impl Rng) -> Vec<usize> {
212+
fn sample_with_replacement(nrows: usize, rng: &mut impl rand::Rng) -> Vec<usize> {
213213
let mut samples = vec![0; nrows];
214214
for _ in 0..nrows {
215-
let xi = rng.gen_range(0..nrows);
215+
let xi = rng.random_range(0..nrows);
216216
samples[xi] += 1;
217217
}
218218
samples

src/ensemble/random_forest_classifier.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
//!
4646
//! <script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script>
4747
//! <script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
48-
use rand::Rng;
48+
use rand::RngExt;
4949

5050
use std::default::Default;
5151
use std::fmt::Debug;
@@ -587,7 +587,11 @@ impl<TX: FloatNumber + PartialOrd, TY: Number + Ord, X: Array2<TX>, Y: Array1<TY
587587
which_max(&result)
588588
}
589589

590-
fn sample_with_replacement(y: &[usize], num_classes: usize, rng: &mut impl Rng) -> Vec<usize> {
590+
fn sample_with_replacement(
591+
y: &[usize],
592+
num_classes: usize,
593+
rng: &mut impl rand::Rng,
594+
) -> Vec<usize> {
591595
let class_weight = vec![1.; num_classes];
592596
let nrows = y.len();
593597
let mut samples = vec![0; nrows];
@@ -603,7 +607,7 @@ impl<TX: FloatNumber + PartialOrd, TY: Number + Ord, X: Array2<TX>, Y: Array1<TY
603607

604608
let size = ((n_samples as f64) / *class_weight_l) as usize;
605609
for _ in 0..size {
606-
let xi: usize = rng.gen_range(0..n_samples);
610+
let xi: usize = rng.random_range(0..n_samples);
607611
samples[index[xi]] += 1;
608612
}
609613
}

src/numbers/floatnum.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,9 @@ impl FloatNumber for f64 {
5656
}
5757

5858
fn rand() -> f64 {
59-
use rand::Rng;
59+
use rand::RngExt;
6060
let mut rng = get_rng_impl(None);
61-
rng.gen()
61+
rng.random()
6262
}
6363

6464
fn two() -> Self {
@@ -98,9 +98,9 @@ impl FloatNumber for f32 {
9898
}
9999

100100
fn rand() -> f32 {
101-
use rand::Rng;
101+
use rand::RngExt;
102102
let mut rng = get_rng_impl(None);
103-
rng.gen()
103+
rng.random()
104104
}
105105

106106
fn two() -> Self {

src/numbers/realnum.rs

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
//! This module defines real number and some useful functions that are used in [Linear Algebra](../../linalg/index.html) module.
44
55
use rand::rngs::SmallRng;
6-
use rand::{Rng, SeedableRng};
6+
use rand::{RngExt, SeedableRng};
77

88
use num_traits::Float;
99

@@ -69,10 +69,8 @@ impl RealNumber for f64 {
6969
fn rand() -> f64 {
7070
let mut small_rng = get_rng_impl(None);
7171

72-
let mut rngs: Vec<SmallRng> = (0..3)
73-
.map(|_| SmallRng::from_rng(&mut small_rng).unwrap())
74-
.collect();
75-
rngs[0].gen::<f64>()
72+
let mut rngs: Vec<SmallRng> = (0..3).map(|_| SmallRng::from_rng(&mut small_rng)).collect();
73+
rngs[0].random::<f64>()
7674
}
7775

7876
fn two() -> Self {
@@ -118,10 +116,8 @@ impl RealNumber for f32 {
118116
fn rand() -> f32 {
119117
let mut small_rng = get_rng_impl(None);
120118

121-
let mut rngs: Vec<SmallRng> = (0..3)
122-
.map(|_| SmallRng::from_rng(&mut small_rng).unwrap())
123-
.collect();
124-
rngs[0].gen::<f32>()
119+
let mut rngs: Vec<SmallRng> = (0..3).map(|_| SmallRng::from_rng(&mut small_rng)).collect();
120+
rngs[0].random::<f32>()
125121
}
126122

127123
fn two() -> Self {

src/rand_custom.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,11 @@ pub fn get_rng_impl(seed: Option<u64>) -> RngImpl {
1111
None => {
1212
cfg_if::cfg_if! {
1313
if #[cfg(feature = "std_rand")] {
14-
use rand::RngCore;
15-
RngImpl::seed_from_u64(rand::thread_rng().next_u64())
14+
use rand::Rng;
15+
// FIX: thread_rng() deprecated in rand 0.9 → use rng()
16+
// FIX: rand 0.10 no longer re-exports RngCore at root;
17+
// import rand::Rng (supertrait) instead so next_u64() resolves
18+
RngImpl::seed_from_u64(rand::rng().next_u64())
1619
} else {
1720
// no std_random feature build, use getrandom
1821
#[cfg(feature = "js")]

src/readers/io_testing.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! This module contains functionality to test IO. It has both functions that write
22
//! to the file-system for end-to-end tests, but also abstractions to avoid this by
33
//! reading from strings instead.
4-
use rand::distributions::{Alphanumeric, DistString};
4+
use rand::distr::{Alphanumeric, SampleString};
55
use std::fs;
66
use std::io::Bytes;
77
use std::io::Read;
@@ -16,7 +16,7 @@ pub struct TemporaryTextFile {
1616
impl TemporaryTextFile {
1717
pub fn new(contents: &str) -> std::io::Result<Self> {
1818
let test_text_file = TemporaryTextFile {
19-
random_path: Alphanumeric.sample_string(&mut rand::thread_rng(), 16),
19+
random_path: Alphanumeric.sample_string(&mut rand::rng(), 16),
2020
};
2121
string_to_file(contents, &test_text_file.random_path)?;
2222
Ok(test_text_file)

src/tree/base_tree_regressor.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::fmt::Debug;
44
use std::marker::PhantomData;
55

66
use rand::seq::SliceRandom;
7-
use rand::Rng;
7+
use rand::RngExt;
88

99
#[cfg(feature = "serde")]
1010
use serde::{Deserialize, Serialize};
@@ -292,7 +292,7 @@ impl<TX: Number + PartialOrd, TY: Number, X: Array2<TX>, Y: Array1<TY>>
292292
&mut self,
293293
visitor: &mut NodeVisitor<'_, TX, TY, X, Y>,
294294
mtry: usize,
295-
rng: &mut impl Rng,
295+
rng: &mut impl rand::Rng,
296296
) -> bool {
297297
let (_, n_attr) = visitor.x.shape();
298298

@@ -336,7 +336,7 @@ impl<TX: Number + PartialOrd, TY: Number, X: Array2<TX>, Y: Array1<TY>>
336336
sum: f64,
337337
parent_gain: f64,
338338
j: usize,
339-
rng: &mut impl Rng,
339+
rng: &mut impl rand::Rng,
340340
) {
341341
let (min_val, max_val) = {
342342
let mut min_opt = None;
@@ -363,7 +363,7 @@ impl<TX: Number + PartialOrd, TY: Number, X: Array2<TX>, Y: Array1<TY>>
363363
return;
364364
}
365365

366-
let split_value = rng.gen_range(min_val.to_f64().unwrap()..max_val.to_f64().unwrap());
366+
let split_value = rng.random_range(min_val.to_f64().unwrap()..max_val.to_f64().unwrap());
367367

368368
let mut true_sum = 0f64;
369369
let mut true_count = 0;
@@ -476,7 +476,7 @@ impl<TX: Number + PartialOrd, TY: Number, X: Array2<TX>, Y: Array1<TY>>
476476
mut visitor: NodeVisitor<'a, TX, TY, X, Y>,
477477
mtry: usize,
478478
visitor_queue: &mut LinkedList<NodeVisitor<'a, TX, TY, X, Y>>,
479-
rng: &mut impl Rng,
479+
rng: &mut impl rand::Rng,
480480
) -> bool {
481481
let (n, _) = visitor.x.shape();
482482
let mut tc = 0;

0 commit comments

Comments
 (0)