omics_coordinate/interval/
base.rs

1//! Base intervals.
2
3use crate::base::Coordinate;
4use crate::interval::r#trait;
5use crate::position::Number;
6use crate::system::Base;
7use crate::system::Interbase;
8
9////////////////////////////////////////////////////////////////////////////////////////
10// Intervals
11////////////////////////////////////////////////////////////////////////////////////////
12
13/// A base interval.
14///
15/// Base intervals consist of two in-base positions. The range is represented by
16/// the interval `[start, end]`.
17pub type Interval = crate::Interval<Base>;
18
19impl Interval {
20    /// Consumes `self` and returns the equivalent interbase interval.
21    ///
22    /// # Examples
23    ///
24    /// ```
25    /// use omics_coordinate::Interval;
26    /// use omics_coordinate::system::Base;
27    /// use omics_coordinate::system::Interbase;
28    ///
29    /// let interval = "seq0:+:1-1000".parse::<Interval<Base>>()?;
30    /// let equivalent = interval.into_equivalent_interbase();
31    ///
32    /// assert_eq!("seq0:+:0-1000".parse::<Interval<Interbase>>()?, equivalent);
33    ///
34    /// # Ok::<(), Box<dyn std::error::Error>>(())
35    /// ```
36    pub fn into_equivalent_interbase(self) -> crate::interval::Interval<Interbase> {
37        let (start, end) = self.into_coordinates();
38
39        // SAFETY: given the rules of how interbase and base coordinate systems
40        // work, this should always unwrap.
41        let start = start.nudge_backward().unwrap();
42        let end = end.nudge_forward().unwrap();
43
44        // SAFETY: since this was previously a valid interbase interval, as long
45        // as the two nudges above succeed, this should always unwrap.
46        crate::interval::Interval::<Interbase>::try_new(start, end).unwrap()
47    }
48}
49
50////////////////////////////////////////////////////////////////////////////////////////
51// Trait implementations
52////////////////////////////////////////////////////////////////////////////////////////
53
54impl r#trait::Interval<Base> for Interval {
55    fn contains_entity(&self, coordinate: &Coordinate) -> bool {
56        // NOTE: for in-base positions whether or not the entity is contained in
57        // the interval matches the implementation of whether or not the
58        // coordinate is contained within the interval, as entities and
59        // coordinates are essentially the same thing in this system.
60        self.contains_coordinate(coordinate)
61    }
62
63    /// Gets the number of entities within the interval.
64    fn count_entities(&self) -> Number {
65        self.start()
66            .position()
67            .distance_unchecked(self.end().position())
68            + 1
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75    use crate::Coordinate;
76    use crate::system::Base;
77
78    fn create_coordinate(contig: &str, strand: &str, position: Number) -> crate::Coordinate<Base> {
79        Coordinate::try_new(contig, strand, position).unwrap()
80    }
81
82    fn create_interval(contig: &str, strand: &str, start: Number, end: Number) -> Interval {
83        Interval::try_new(
84            create_coordinate(contig, strand, start),
85            create_coordinate(contig, strand, end),
86        )
87        .unwrap()
88    }
89
90    #[test]
91    fn contains() {
92        let interval = create_interval("seq0", "+", 10, 20);
93
94        // An interval contains the coordinate representing its start position.
95        assert!(interval.contains_coordinate(interval.start()));
96
97        // An interval contains the coordinate representing its end position.
98        assert!(interval.contains_coordinate(interval.end()));
99
100        // An interval contains a coordinate in the middle of its range.
101        assert!(interval.contains_coordinate(&create_coordinate("seq0", "+", 15)));
102
103        // An interval does not contain the position _before_ its start
104        // position.
105        assert!(!interval.contains_coordinate(&create_coordinate("seq0", "+", 9)));
106
107        // An interval does not contain the position _after_ its end position.
108        assert!(!interval.contains_coordinate(&create_coordinate("seq0", "+", 21)));
109
110        // An interval does not contain a random other position.
111        assert!(!interval.contains_coordinate(&create_coordinate("seq0", "+", 1000)));
112
113        // An interval does not contain a coordinate on another contig.
114        assert!(!interval.contains_coordinate(&create_coordinate("seq1", "+", 15)));
115
116        // An interval does not contain a coordinate on another strand.
117        assert!(!interval.contains_coordinate(&create_coordinate("seq0", "-", 15)));
118
119        let interval = create_interval("seq0", "-", 20, 10);
120
121        // An interval contains the coordinate representing its start position.
122        assert!(interval.contains_coordinate(interval.start()));
123
124        // An interval contains the coordinate representing its end position.
125        assert!(interval.contains_coordinate(interval.end()));
126
127        // An interval contains a coordinate in the middle of its range.
128        assert!(interval.contains_coordinate(&create_coordinate("seq0", "-", 15)));
129
130        // An interval does not contain the position _before_ its start
131        // position.
132        assert!(!interval.contains_coordinate(&create_coordinate("seq0", "-", 21)));
133
134        // An interval does not contain the position _after_ its end position.
135        assert!(!interval.contains_coordinate(&create_coordinate("seq0", "-", 9)));
136
137        // An interval does not contain a random other position.
138        assert!(!interval.contains_coordinate(&create_coordinate("seq0", "-", 1)));
139
140        // An interval does not contain a coordinate on another contig.
141        assert!(!interval.contains_coordinate(&create_coordinate("seq1", "-", 15)));
142
143        // An interval does not contain a coordinate on another strand.
144        assert!(!interval.contains_coordinate(&create_coordinate("seq0", "+", 15)));
145    }
146
147    #[test]
148    fn contains_entity() {
149        let interval = create_interval("seq0", "+", 10, 20);
150
151        // A base interval does not contain the entity at the full-step _before_
152        // its start position when on the positive strand.
153        assert!(!interval.contains_entity(&create_coordinate("seq0", "+", 9)));
154
155        // A base interval does contain the entity at it start position when on
156        // the positive strand.
157        assert!(interval.contains_entity(&create_coordinate("seq0", "+", 10)));
158
159        // A base interval does contain the entity at the its end position when
160        // on the positive strand.
161        assert!(interval.contains_entity(&create_coordinate("seq0", "+", 20)));
162
163        // A base interval does not contain the entity at the full-step _after_
164        // its end position when on the positive strand.
165        assert!(!interval.contains_entity(&create_coordinate("seq0", "+", 21)));
166
167        // A base interval does contain an entity midway through its range on
168        // the positive strand.
169        assert!(interval.contains_entity(&create_coordinate("seq0", "+", 15)));
170
171        // A base interval does not contain an entity on a different contig on
172        // the positive strand.
173        assert!(!interval.contains_entity(&create_coordinate("seq1", "+", 15)));
174
175        // A base interval does not contain an entity on a different strand on
176        // the positive strand.
177        assert!(!interval.contains_entity(&create_coordinate("seq0", "-", 15)));
178
179        let interval = create_interval("seq0", "-", 20, 10);
180
181        // A base interval does not contain the entity at the full-step _before_
182        // its start position when on the negative strand.
183        assert!(!interval.contains_entity(&create_coordinate("seq0", "-", 21)));
184
185        // A base interval does contain the entity at its start position when on
186        // the negative strand.
187        assert!(interval.contains_entity(&create_coordinate("seq0", "-", 20)));
188
189        // A base interval does contain the entity at its end position when on
190        // the negative strand.
191        assert!(interval.contains_entity(&create_coordinate("seq0", "-", 10)));
192
193        // A base interval does not contain the entity at the full-step _after_
194        // its end position when on the negative strand.
195        assert!(!interval.contains_entity(&create_coordinate("seq0", "-", 9)));
196
197        // A base interval does contain an entity midway through its range on
198        // the negative strand.
199        assert!(interval.contains_entity(&create_coordinate("seq0", "-", 15)));
200
201        // A base interval does not contain an entity on a different contig on
202        // the negative strand.
203        assert!(!interval.contains_entity(&create_coordinate("seq1", "-", 15)));
204
205        // A base interval does not contain an entity on a different strand on
206        // the negative strand.
207        assert!(!interval.contains_entity(&create_coordinate("seq0", "+", 15)));
208    }
209}