use ndarray::{Array1, Array2};
use wass::barycenter::{barycenter, free_support_barycenter};
fn main() {
shape_morph();
println!();
hump_vs_average();
}
fn shape_morph() {
println!("=== Free-support barycenter: morphing a square into a circle ===\n");
let k = 28;
let square = polygon_outline(k, 4); let circle = polygon_outline(k, k); let unit = Array1::from_elem(k, 1.0 / k as f32);
for &t in &[0.0, 0.25, 0.5, 0.75, 1.0] {
let (y, _) = free_support_barycenter(
&[
(square.clone(), unit.clone()),
(circle.clone(), unit.clone()),
],
&[1.0 - t, t],
k,
0.02, 400,
120,
None,
)
.expect("barycenter");
println!(" weight on circle = {t:.2}");
render_points(&y);
println!();
}
println!(" (square at 0.00, circle at 1.00; the outline bends between them)");
}
fn polygon_outline(n: usize, sides: usize) -> Array2<f32> {
Array2::from_shape_fn((n, 2), |(i, col)| {
let frac = i as f32 / n as f32; let seg = frac * sides as f32; let s = seg.floor() as usize % sides;
let u = seg - seg.floor();
let ang0 = std::f32::consts::TAU * s as f32 / sides as f32;
let ang1 = std::f32::consts::TAU * (s + 1) as f32 / sides as f32;
let (x0, y0) = (ang0.cos(), ang0.sin());
let (x1, y1) = (ang1.cos(), ang1.sin());
if col == 0 {
x0 + u * (x1 - x0)
} else {
y0 + u * (y1 - y0)
}
})
}
fn render_points(pts: &Array2<f32>) {
const W: usize = 33;
const H: usize = 17;
const LO: f32 = -1.35;
const HI: f32 = 1.35;
let mut grid = vec![vec![' '; W]; H];
for i in 0..pts.nrows() {
let (x, y) = (pts[[i, 0]], pts[[i, 1]]);
let col = (((x - LO) / (HI - LO)) * (W - 1) as f32).round();
let row = (((HI - y) / (HI - LO)) * (H - 1) as f32).round();
if (0.0..W as f32).contains(&col) && (0.0..H as f32).contains(&row) {
grid[row as usize][col as usize] = '#';
}
}
for row in &grid {
let line: String = row.iter().collect();
println!(" {}", line.trim_end());
}
}
fn hump_vs_average() {
println!("=== Fixed-support barycenter vs naive average of two 1D humps ===\n");
let n = 41;
let cost = Array2::from_shape_fn((n, n), |(i, j)| {
let d = i as f32 - j as f32;
d * d
});
let left = hump(n, 9.0, 2.5);
let right = hump(n, 31.0, 2.5);
let bary = barycenter(&[left.clone(), right.clone()], &cost, &[0.5, 0.5], 1.0, 500).unwrap();
let avg = Array1::from_shape_fn(n, |i| 0.5 * (left[i] + right[i]));
println!(" inputs: two separated humps (left + right, overlaid):");
let both = Array1::from_shape_fn(n, |i| left[i].max(right[i]));
render_hist(&both);
println!("\n naive average -- keeps BOTH humps (not a transport average):");
render_hist(&avg);
println!("\n OT barycenter -- a single hump BETWEEN them:");
render_hist(&bary);
}
fn hump(n: usize, mean: f32, std: f32) -> Array1<f32> {
let mut v = Array1::from_shape_fn(n, |i| {
let z = (i as f32 - mean) / std;
(-0.5 * z * z).exp()
});
let s = v.sum();
v.mapv_inplace(|x| x / s);
v
}
fn render_hist(h: &Array1<f32>) {
const ROWS: usize = 8;
let max = h.iter().copied().fold(0.0f32, f32::max);
let heights: Vec<usize> = h
.iter()
.map(|&p| {
if max > 0.0 {
(p / max * ROWS as f32).round() as usize
} else {
0
}
})
.collect();
for level in (1..=ROWS).rev() {
let line: String = heights
.iter()
.map(|&hgt| if hgt >= level { '#' } else { ' ' })
.collect();
println!(" {}", line.trim_end());
}
}