use super::super::*;
use super::*;
fn grid(w: usize, h: usize) -> (usize, Vec<(usize, usize, f32)>, Mat) {
let id = |x: usize, y: usize| y * w + x;
let mut edges = Vec::new();
for y in 0..h {
for x in 0..w {
let weight = |k: usize| 1.0 + 0.1 * ((id(x, y) * 7 + k) % 5) as f32;
if x + 1 < w {
edges.push((id(x, y), id(x + 1, y), weight(0)));
}
if y + 1 < h {
edges.push((id(x, y), id(x, y + 1), weight(1)));
}
if x + 1 < w && y + 1 < h {
edges.push((id(x, y), id(x + 1, y + 1), weight(2)));
}
}
}
let n = w * h;
let features = Mat::from_fn(n, 3, |i, d| {
let (x, y) = ((i % w) as f32, (i / w) as f32);
[x, y, 0.3 * x.sin()][d]
});
(n, edges, features)
}
type Layout = Box<dyn Fn(&[(usize, usize, f32)]) -> Mat>;
#[test]
fn a_weight_not_above_zero_or_not_finite_is_no_edge_in_any_layout() {
let (n, kept, features) = grid(5, 4);
let mut noisy = kept.clone();
noisy.extend([
(0, 19, 0.0),
(3, 10, -1.0),
(2, 15, f32::NAN),
(4, 12, f32::INFINITY),
]);
let layouts: Vec<(&str, Layout)> = vec![
(
"stress",
Box::new(move |e| stress_layout_2d_on_graph(n, e, 3)),
),
(
"forceatlas2",
Box::new(move |e| forceatlas2_layout_2d_on_graph(n, e, 50, 3)),
),
(
"diffusion",
Box::new(move |e| diffusion_map_2d_on_graph(n, e, 1)),
),
(
"radial",
Box::new(move |e| radial_tree_layout_2d_on_graph(n, e, None)),
),
(
"sugiyama",
Box::new(move |e| sugiyama_layout_2d_on_graph(n, e, None)),
),
(
"lle",
Box::new(move |e| lle_layout_2d_on_graph(&features, e)),
),
];
for (name, layout) in &layouts {
let (with, without) = (layout(&noisy), layout(&kept));
assert!(without.iter().all(|v| v.is_finite()), "{name}: {without}");
assert_eq!(with, without, "{name}");
}
}
#[test]
fn preparing_cleans_and_sorts_the_edges() {
let edges = [
(2, 0, 1.0),
(3, 2, 2.0),
(0, 2, 0.5),
(1, 1, 3.0),
(1, 3, 0.0),
(3, 1, -2.0),
(0, 3, f32::NAN),
(2, 3, f32::INFINITY),
(0, 4, 1.0),
];
let g = PreparedGraph::new(5, &edges);
assert_eq!(g.n(), 5);
assert_eq!(g.edges(), &[(0, 2, 1.5), (0, 4, 1.0), (2, 3, 2.0)]);
assert_eq!(g.neighbours(0), &[(2, 1.5), (4, 1.0)]);
assert_eq!(g.neighbours(1), &[]);
assert_eq!(g.neighbours(2), &[(0, 1.5), (3, 2.0)]);
assert_eq!(g.neighbour_ids(3).collect::<Vec<_>>(), vec![2]);
}
#[test]
#[should_panic(expected = "not below the node count")]
fn an_edge_end_out_of_range_panics() {
let _ = PreparedGraph::new(3, &[(0, 3, 1.0)]);
}
fn box_gap(a: &[f32; 4], b: &[f32; 4]) -> f32 {
let gx = (b[0] - a[2]).max(a[0] - b[2]);
let gy = (b[1] - a[3]).max(a[1] - b[3]);
gx.max(gy)
}
fn slant(g: &PreparedGraph, _: Option<usize>) -> Mat {
assert!(g.n() > 1, "a lone node was laid out");
Mat::from_fn(g.n(), 2, |k, d| [2.0 * k as f32, k as f32][d])
}
#[test]
fn pieces_are_placed_apart_and_the_first_keeps_its_place() {
let edges = [(0, 1, 1.0), (2, 3, 1.0), (3, 4, 1.0), (6, 7, 1.0)];
let g = PreparedGraph::new(8, &edges);
let pieces: [&[usize]; 4] = [&[0, 1], &[2, 3, 4], &[5], &[6, 7]];
let largest = 4.0_f32;
let y = lay_out_by_piece(&g, slant, None);
let boxes: Vec<[f32; 4]> = pieces
.iter()
.map(|p| bounding_box(&Mat::from_fn(p.len(), 2, |k, d| y[(p[k], d)])))
.collect();
for a in 0..boxes.len() {
for b in (a + 1)..boxes.len() {
let gap = box_gap(&boxes[a], &boxes[b]);
assert!(gap >= 0.1 * largest - 1e-6, "pieces {a} and {b}: {gap}");
}
}
let all = bounding_box(&y);
assert_eq!((all[0], all[1]), (0.0, 0.0));
let seen = std::sync::Mutex::new(Vec::new());
let y = lay_out_by_piece(
&g,
|p, at| {
seen.lock().unwrap().push((p.n(), at));
slant(p, at)
},
Some(3),
);
for (k, &i) in pieces[1].iter().enumerate() {
assert_eq!((y[(i, 0)], y[(i, 1)]), (2.0 * k as f32, k as f32));
}
let mut seen = seen.into_inner().unwrap();
seen.sort_unstable();
assert_eq!(seen, vec![(2, None), (2, None), (3, Some(1))]);
}
#[test]
fn lone_nodes_sit_one_apart() {
let y = lay_out_by_piece(&PreparedGraph::new(5, &[]), slant, None);
for i in 0..5 {
for j in 0..i {
assert!((y.row(i) - y.row(j)).norm() >= 1.0 - 1e-6, "{i} {j}");
}
}
}