use crate::{
traits::{ChromBounds, ValueBounds},
Intersect, Overlap, Strand, Subtract,
};
use std::cmp::Ordering;
use super::{
overlap::{StrandedOverlap, UnstrandedOverlap},
Distance, Segment,
};
#[allow(clippy::len_without_is_empty)]
pub trait Coordinates<C, T>
where
C: ChromBounds,
T: ValueBounds,
{
fn start(&self) -> T;
fn end(&self) -> T;
fn chr(&self) -> &C;
fn strand(&self) -> Option<Strand> {
None
}
fn update_start(&mut self, val: &T);
fn update_end(&mut self, val: &T);
fn update_chr(&mut self, val: &C);
fn update_strand(&mut self, _strand: Option<Strand>) {
}
fn from<Iv: Coordinates<C, T>>(other: &Iv) -> Self;
fn empty() -> Self;
fn len(&self) -> T {
self.end().sub(self.start())
}
fn update_all(&mut self, chr: &C, start: &T, end: &T) {
self.update_chr(chr);
self.update_endpoints(start, end);
}
fn update_endpoints(&mut self, start: &T, end: &T) {
self.update_start(start);
self.update_end(end);
}
fn update_all_from<I: Coordinates<C, T>>(&mut self, other: &I) {
self.update_chr(other.chr());
self.update_endpoints(&other.start(), &other.end());
}
fn update_endpoints_from<I: Coordinates<C, T>>(&mut self, other: &I) {
self.update_start(&other.start());
self.update_end(&other.end());
}
fn extend_left(&mut self, val: &T) {
if self.start().lt(val) {
self.update_start(&T::zero());
} else {
self.update_start(&self.start().sub(*val));
}
}
fn extend_right(&mut self, val: &T, max_bound: Option<T>) {
let new_end = self.end().add(*val);
if let Some(max) = max_bound {
self.update_end(&new_end.min(max));
} else {
self.update_end(&new_end);
}
}
fn extend(&mut self, val: &T, max_bound: Option<T>) {
self.extend_left(val);
self.extend_right(val, max_bound);
}
fn f_len(&self, frac: f64) -> T {
let len_f: f64 = self.len().to_f64().unwrap();
let n = len_f * frac;
T::from_f64(n.round()).unwrap()
}
fn coord_cmp<I: Coordinates<C, T>>(&self, other: &I) -> Ordering {
match self.chr().cmp(other.chr()) {
Ordering::Equal => match self.start().cmp(&other.start()) {
Ordering::Equal => match self.end().cmp(&other.end()) {
Ordering::Equal => self.strand().cmp(&other.strand()),
order => order,
},
order => order,
},
order => order,
}
}
fn biased_coord_cmp<I: Coordinates<C, T>>(&self, other: &I, bias: T) -> Ordering {
match self.chr().cmp(other.chr()) {
Ordering::Equal => {
let comp = if other.start() < bias {
None } else {
Some(self.start().cmp(&other.start().sub(bias)))
};
if let Some(comp) = comp {
match comp {
Ordering::Equal => match self.end().cmp(&other.end()) {
Ordering::Equal => self.strand().cmp(&other.strand()),
order => order,
},
order => order,
}
} else {
Ordering::Equal
}
}
order => order,
}
}
fn biased_lt<I: Coordinates<C, T>>(&self, other: &I, bias: T) -> bool {
self.biased_coord_cmp(other, bias) == Ordering::Less
}
fn lt<I: Coordinates<C, T>>(&self, other: &I) -> bool {
self.coord_cmp(other) == Ordering::Less
}
fn gt<I: Coordinates<C, T>>(&self, other: &I) -> bool {
self.coord_cmp(other) == Ordering::Greater
}
fn eq<I: Coordinates<C, T>>(&self, other: &I) -> bool {
self.coord_cmp(other) == Ordering::Equal
}
fn pprint(&self) -> String {
format!(
"{:?}:{:?}-{:?}:{}",
self.chr(),
self.start(),
self.end(),
self.strand().unwrap_or(Strand::Unknown)
)
}
}
impl<I, C, T> Distance<C, T> for I
where
I: Coordinates<C, T>,
C: ChromBounds,
T: ValueBounds,
{
}
impl<I, C, T> Intersect<C, T> for I
where
I: Coordinates<C, T>,
C: ChromBounds,
T: ValueBounds,
{
}
impl<I, C, T> Overlap<C, T> for I
where
I: Coordinates<C, T>,
C: ChromBounds,
T: ValueBounds,
{
}
impl<I, C, T> StrandedOverlap<C, T> for I
where
I: Coordinates<C, T>,
C: ChromBounds,
T: ValueBounds,
{
}
impl<I, C, T> UnstrandedOverlap<C, T> for I
where
I: Coordinates<C, T>,
C: ChromBounds,
T: ValueBounds,
{
}
impl<I, C, T> Subtract<C, T> for I
where
I: Coordinates<C, T>,
C: ChromBounds,
T: ValueBounds,
{
}
impl<I, C, T> Segment<C, T> for I
where
I: Coordinates<C, T>,
C: ChromBounds,
T: ValueBounds,
{
}
#[cfg(test)]
mod testing {
use crate::{traits::Coordinates, BaseInterval, Bed3};
struct CustomInterval {
left: usize,
right: usize,
}
impl Coordinates<usize, usize> for CustomInterval {
fn empty() -> Self {
Self { left: 0, right: 0 }
}
fn start(&self) -> usize {
self.left
}
fn end(&self) -> usize {
self.right
}
fn chr(&self) -> &usize {
&0
}
fn update_start(&mut self, val: &usize) {
self.left = *val;
}
fn update_end(&mut self, val: &usize) {
self.right = *val;
}
#[allow(unused)]
fn update_chr(&mut self, val: &usize) {}
fn from<Iv: Coordinates<usize, usize>>(other: &Iv) -> Self {
Self {
left: other.start(),
right: other.end(),
}
}
}
struct CustomIntervalMeta {
left: usize,
right: usize,
meta: String,
}
impl Coordinates<usize, usize> for CustomIntervalMeta {
fn empty() -> Self {
Self {
left: 0,
right: 0,
meta: String::new(),
}
}
fn start(&self) -> usize {
self.left
}
fn end(&self) -> usize {
self.right
}
fn chr(&self) -> &usize {
&0
}
fn update_start(&mut self, val: &usize) {
self.left = *val;
}
fn update_end(&mut self, val: &usize) {
self.right = *val;
}
#[allow(unused)]
fn update_chr(&mut self, val: &usize) {}
fn from<Iv: Coordinates<usize, usize>>(other: &Iv) -> Self {
Self {
left: other.start(),
right: other.end(),
meta: String::new(),
}
}
}
#[test]
fn test_custom_interval() {
let left = 10;
let right = 100;
let a = CustomInterval { left, right };
assert_eq!(a.start(), 10);
assert_eq!(a.end(), 100);
assert_eq!(*a.chr(), 0);
let mut a = CustomInterval::empty();
a.update_chr(&0);
}
#[test]
fn test_custom_interval_update() {
let mut a = CustomInterval {
left: 10,
right: 100,
};
assert_eq!(a.start(), 10);
assert_eq!(a.end(), 100);
a.update_start(&30);
a.update_end(&120);
assert_eq!(a.start(), 30);
assert_eq!(a.end(), 120);
}
#[test]
fn test_custom_interval_transcode() {
let a = CustomInterval {
left: 10,
right: 100,
};
let b: CustomInterval = Coordinates::from(&a);
assert_eq!(a.start(), b.start());
assert_eq!(a.end(), b.end());
assert_eq!(a.chr(), b.chr());
}
#[test]
fn test_custom_interval_with_meta() {
let left = 10;
let right = 100;
let meta = "some_meta".to_string();
let a = CustomIntervalMeta { left, right, meta };
assert_eq!(a.start(), 10);
assert_eq!(a.end(), 100);
assert_eq!(*a.chr(), 0);
}
#[test]
fn test_custom_interval_meta_update() {
let mut a = CustomIntervalMeta {
left: 10,
right: 100,
meta: String::from("hello"),
};
assert_eq!(a.start(), 10);
assert_eq!(a.end(), 100);
a.update_start(&30);
a.update_end(&120);
a.update_chr(&0);
assert_eq!(a.start(), 30);
assert_eq!(a.end(), 120);
let _a = CustomIntervalMeta::empty();
}
#[test]
fn test_custom_interval_meta_transcode() {
let a = CustomIntervalMeta {
left: 10,
right: 100,
meta: String::from("hello"),
};
let b: CustomIntervalMeta = Coordinates::from(&a);
assert_eq!(a.start(), b.start());
assert_eq!(a.end(), b.end());
assert_eq!(a.chr(), b.chr());
assert_ne!(a.meta, b.meta);
}
#[test]
fn test_convenience_methods() {
let a = BaseInterval::new(10, 20);
let b = BaseInterval::new(30, 50);
let c = BaseInterval::new(30, 50);
assert!(a.lt(&b));
assert!(b.gt(&a));
assert!(b.eq(&c));
}
#[test]
fn test_extend_left() {
let mut a = BaseInterval::new(10, 20);
let val = 5;
a.extend_left(&val);
assert_eq!(a.start(), 5);
assert_eq!(a.end(), 20);
}
#[test]
fn test_extend_left_bounded() {
let mut a = BaseInterval::new(10, 20);
let val = 11;
a.extend_left(&val);
assert_eq!(a.start(), 0);
assert_eq!(a.end(), 20);
}
#[test]
fn test_extend_right() {
let mut a = BaseInterval::new(10, 20);
let val = 5;
a.extend_right(&val, None);
assert_eq!(a.start(), 10);
assert_eq!(a.end(), 25);
}
#[test]
fn test_extend_right_bounded() {
let mut a = BaseInterval::new(10, 20);
let val = 5;
a.extend_right(&val, Some(22));
assert_eq!(a.start(), 10);
assert_eq!(a.end(), 22);
}
#[test]
fn test_extend_both() {
let mut a = BaseInterval::new(10, 20);
let val = 5;
a.extend(&val, None);
assert_eq!(a.start(), 5);
assert_eq!(a.end(), 25);
}
#[test]
fn test_extend_both_bounded() {
let mut a = BaseInterval::new(10, 20);
let val = 5;
a.extend(&val, Some(22));
assert_eq!(a.start(), 5);
assert_eq!(a.end(), 22);
}
#[test]
fn test_update_from() {
let mut a = Bed3::new(1, 10, 20);
let b = Bed3::new(2, 30, 50);
a.update_endpoints_from(&b);
assert_eq!(a.chr(), &1);
assert_eq!(a.start(), 30);
assert_eq!(a.end(), 50);
}
}