use gtars_core::models::{Region, RegionSet};
use gtars_overlaprs::{OverlapperType, multi_chrom_overlapper::MultiChromOverlapper};
#[derive(Debug, Clone, PartialEq)]
pub struct ConsensusRegion {
pub chr: String,
pub start: u32,
pub end: u32,
pub count: u32,
}
pub fn consensus(sets: &[RegionSet]) -> Vec<ConsensusRegion> {
if sets.is_empty() {
return Vec::new();
}
let mut all_regions: Vec<Region> = Vec::new();
for set in sets {
all_regions.extend(set.regions.iter().cloned());
}
let union = RegionSet::from(all_regions).reduce();
let hit_cols: Vec<Vec<bool>> = sets
.iter()
.map(|s| {
let ov = MultiChromOverlapper::from_region_set(s.clone(), OverlapperType::AIList);
ov.any_overlaps(&union, None)
})
.collect();
union
.regions
.iter()
.enumerate()
.map(|(ui, r)| {
let count = hit_cols.iter().filter(|col| col[ui]).count() as u32;
ConsensusRegion {
chr: r.chr.clone(),
start: r.start,
end: r.end,
count,
}
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
use rstest::*;
use std::path::PathBuf;
fn get_test_path(file_name: &str) -> PathBuf {
std::env::current_dir()
.unwrap()
.join("../tests/data/regionset")
.join(file_name)
}
fn make_regionset(regions: Vec<(&str, u32, u32)>) -> RegionSet {
let regions: Vec<Region> = regions
.into_iter()
.map(|(chr, start, end)| Region {
chr: chr.to_string(),
start,
end,
rest: None,
})
.collect();
RegionSet::from(regions)
}
#[rstest]
fn test_consensus_two_sets() {
let path_a = get_test_path("dummy.bed");
let path_b = get_test_path("dummy_b.bed");
let a = RegionSet::try_from(path_a.to_str().unwrap()).unwrap();
let b = RegionSet::try_from(path_b.to_str().unwrap()).unwrap();
let result = consensus(&[a, b]);
assert_eq!(result.len(), 1);
assert_eq!(result[0].chr, "chr1");
assert_eq!(result[0].start, 2);
assert_eq!(result[0].end, 12);
assert_eq!(result[0].count, 2);
}
#[rstest]
fn test_consensus_identical() {
let a = make_regionset(vec![("chr1", 10, 20), ("chr1", 30, 40)]);
let result = consensus(&[a.clone(), a]);
assert_eq!(result.len(), 2);
assert!(result.iter().all(|r| r.count == 2));
}
#[rstest]
fn test_consensus_single_set() {
let a = make_regionset(vec![("chr1", 0, 10), ("chr1", 20, 30)]);
let result = consensus(&[a]);
assert_eq!(result.len(), 2);
assert!(result.iter().all(|r| r.count == 1));
}
#[rstest]
fn test_consensus_empty() {
let result = consensus(&[]);
assert!(result.is_empty());
}
#[rstest]
fn test_consensus_partial_overlap() {
let a = make_regionset(vec![("chr1", 0, 10), ("chr1", 20, 30)]);
let b = make_regionset(vec![("chr1", 5, 15)]);
let result = consensus(&[a, b]);
assert_eq!(result.len(), 2);
assert_eq!(result[0].count, 2); assert_eq!(result[1].count, 1); }
#[rstest]
fn test_consensus_three_sets_all_overlap() {
let a = make_regionset(vec![("chr1", 0, 10)]);
let b = make_regionset(vec![("chr1", 2, 8)]);
let c = make_regionset(vec![("chr1", 5, 10)]);
let result = consensus(&[a, b, c]);
assert_eq!(result.len(), 1);
assert_eq!(result[0].chr, "chr1");
assert_eq!(result[0].start, 0);
assert_eq!(result[0].end, 10);
assert_eq!(result[0].count, 3);
}
#[rstest]
fn test_consensus_disjoint_sets() {
let a = make_regionset(vec![("chr1", 0, 10)]);
let b = make_regionset(vec![("chr1", 20, 30)]);
let result = consensus(&[a, b]);
assert_eq!(result.len(), 2);
assert_eq!(result[0].count, 1);
assert_eq!(result[1].count, 1);
}
#[rstest]
fn test_consensus_multi_chromosome() {
let a = make_regionset(vec![("chr1", 0, 10), ("chr2", 0, 10)]);
let b = make_regionset(vec![("chr1", 5, 15)]);
let result = consensus(&[a, b]);
assert_eq!(result.len(), 2);
let chr1 = result.iter().find(|r| r.chr == "chr1").unwrap();
let chr2 = result.iter().find(|r| r.chr == "chr2").unwrap();
assert_eq!(chr1.count, 2);
assert_eq!(chr2.count, 1);
}
#[rstest]
fn test_consensus_three_sets_partial() {
let a = make_regionset(vec![("chr1", 0, 10)]);
let b = make_regionset(vec![("chr1", 5, 15)]);
let c = make_regionset(vec![("chr1", 20, 30)]);
let result = consensus(&[a, b, c]);
assert_eq!(result.len(), 2);
assert_eq!(result[0].start, 0);
assert_eq!(result[0].end, 15);
assert_eq!(result[0].count, 2);
assert_eq!(result[1].start, 20);
assert_eq!(result[1].end, 30);
assert_eq!(result[1].count, 1);
}
#[rstest]
fn test_consensus_empty_among_nonempty() {
let a = make_regionset(vec![("chr1", 0, 10)]);
let empty = RegionSet::from(Vec::<Region>::new());
let b = make_regionset(vec![("chr1", 5, 15)]);
let result = consensus(&[a, empty, b]);
assert_eq!(result.len(), 1);
assert_eq!(result[0].count, 2); }
#[rstest]
fn test_consensus_adjacent_across_sets() {
let a = make_regionset(vec![("chr1", 0, 10)]);
let b = make_regionset(vec![("chr1", 10, 20)]);
let result = consensus(&[a, b]);
assert_eq!(result.len(), 1);
assert_eq!(result[0].start, 0);
assert_eq!(result[0].end, 20);
assert_eq!(result[0].count, 2);
}
#[rstest]
fn test_consensus_verifies_coordinates() {
let a = make_regionset(vec![("chr1", 100, 200), ("chr2", 300, 400)]);
let b = make_regionset(vec![("chr1", 150, 250)]);
let result = consensus(&[a, b]);
assert_eq!(result.len(), 2);
assert_eq!(result[0].chr, "chr1");
assert_eq!(result[0].start, 100);
assert_eq!(result[0].end, 250);
assert_eq!(result[0].count, 2);
assert_eq!(result[1].chr, "chr2");
assert_eq!(result[1].start, 300);
assert_eq!(result[1].end, 400);
assert_eq!(result[1].count, 1);
}
}