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}