use data_beans::alg::bhc::{bhc_cut, bhc_merge, BhcInput};
fn make_input(per_cluster: &[Vec<f64>]) -> (Vec<f64>, Vec<f64>, Vec<usize>) {
assert!(!per_cluster.is_empty());
let m = per_cluster[0].len();
let mut gene_sum = Vec::with_capacity(per_cluster.len() * m);
let mut size_sum = Vec::with_capacity(per_cluster.len());
let mut eff = Vec::with_capacity(per_cluster.len());
for row in per_cluster {
assert_eq!(row.len(), m);
let s: f64 = row.iter().sum();
gene_sum.extend_from_slice(row);
size_sum.push(s);
eff.push(s.ceil().max(0.0) as usize);
}
(gene_sum, size_sum, eff)
}
fn run(per_cluster: &[Vec<f64>], gamma: f64) -> Vec<data_beans::alg::bhc::BhcMerge> {
let (gs, ss, es) = make_input(per_cluster);
bhc_merge(
BhcInput {
k: per_cluster.len(),
m: per_cluster[0].len(),
gene_sum: &gs,
size_sum: &ss,
effective_size: &es,
},
gamma,
)
}
#[test]
fn two_disjoint_clusters_negative_bf() {
let merges = run(
&[vec![10.0, 0.0, 0.0, 0.0], vec![0.0, 10.0, 0.0, 0.0]],
1.0 / 4.0,
);
assert_eq!(merges.len(), 1);
assert!(merges[0].log_bf < 0.0, "got {}", merges[0].log_bf);
}
#[test]
fn two_identical_clusters_positive_bf() {
let merges = run(
&[vec![2.5, 2.5, 2.5, 2.5], vec![2.5, 2.5, 2.5, 2.5]],
1.0 / 4.0,
);
assert_eq!(merges.len(), 1);
assert!(merges[0].log_bf > 0.0, "got {}", merges[0].log_bf);
}
#[test]
fn empty_cluster_skipped() {
let (gs, ss, mut es) = make_input(&[
vec![1.0, 2.0, 0.0, 0.0],
vec![0.0, 0.0, 0.0, 0.0],
vec![0.0, 0.0, 2.0, 1.0],
]);
es[1] = 0;
let merges = bhc_merge(
BhcInput {
k: 3,
m: 4,
gene_sum: &gs,
size_sum: &ss,
effective_size: &es,
},
1.0,
);
assert_eq!(merges.len(), 1);
assert_eq!(merges[0].left, 0);
assert_eq!(merges[0].right, 2);
let labels = bhc_cut(&merges, 3, 0.0);
assert_eq!(labels[1], -1, "empty cluster gets -1");
assert!(labels[0] >= 0 && labels[2] >= 0);
}
#[test]
fn cut_at_zero_collapses_identical_groups_only() {
let merges = run(
&[
vec![8.0, 1.0, 0.5, 0.5],
vec![8.5, 1.0, 0.5, 0.0],
vec![0.5, 0.5, 0.5, 8.0],
],
1.0,
);
assert!(merges[0].log_bf > 0.0);
assert!(merges[1].log_bf < 0.0);
let labels = bhc_cut(&merges, 3, 0.0);
assert_eq!(labels[0], labels[1]);
assert_ne!(labels[0], labels[2]);
}
#[test]
fn analytical_zero_bf_for_m1_gamma1() {
let merges = run(&[vec![5.0], vec![5.0]], 1.0);
assert_eq!(merges.len(), 1);
assert!(merges[0].log_bf.abs() < 1e-9, "got {}", merges[0].log_bf);
}