use rustnum as np;
fn main() {
let mut rng = np::random::Rng::seed(3);
let blob0 = rng.normal(0.0, 0.6, &[100, 2]) + np::array![0.0, 0.0];
let blob1 = rng.normal(0.0, 0.6, &[100, 2]) + np::array![6.0, 6.0];
let blob2 = rng.normal(0.0, 0.6, &[100, 2]) + np::array![0.0, 7.0];
let x = np::concatenate(&[&blob0, &blob1, &blob2], 0);
println!("data shape = {:?}", x.shape());
let k: usize = 3;
let mut centroids = x.take(&[0, 100, 200], 0);
let cluster_ids = np::arange(0, k, 1);
for step in 0..100 {
let diff = x.expand_dims(1) - centroids.expand_dims(0);
let d2 = diff.square().sum_axis(2);
let labels = d2.argmin_axis(1);
let onehot = labels.expand_dims(1).eq_elem(&cluster_ids).astype::<f64>();
let counts = onehot.sum_axis(0); let sums = onehot.t().matmul(&x); let updated = sums / counts.expand_dims(1);
let done = updated.allclose(¢roids, 0.0, 1e-9);
centroids = updated;
if done {
println!("converged after {} steps", step + 1);
println!("points per cluster = {counts}");
break;
}
}
println!("centroids =\n{centroids}");
println!("true centers: [0, 0], [6, 6], [0, 7]");
let truth = np::array![[0.0, 0.0], [6.0, 6.0], [0.0, 7.0]];
assert!(
centroids.allclose(&truth, 0.0, 0.3),
"centroids are far from the true centers"
);
}