1use crate::haplotype::Haplotype;
17
18#[must_use]
22fn complement(base: u8) -> u8 {
23 let upper = match base.to_ascii_uppercase() {
24 b'A' => b'T',
25 b'T' => b'A',
26 b'C' => b'G',
27 b'G' => b'C',
28 _ => b'N',
29 };
30 if base.is_ascii_lowercase() { upper.to_ascii_lowercase() } else { upper }
31}
32
33pub fn reverse_complement(seq: &mut [u8]) {
35 seq.reverse();
36 for base in seq.iter_mut() {
37 *base = complement(*base);
38 }
39}
40
41#[derive(Debug)]
46pub struct Fragment {
47 pub bases: Vec<u8>,
49 pub ref_positions: Vec<u32>,
51 pub ref_start: u32,
53 pub hap_start: u32,
58 pub is_forward: bool,
60 pub haplotype_index: usize,
62}
63
64#[must_use]
76pub fn extract_fragment(
77 haplotype: &Haplotype,
78 reference: &[u8],
79 ref_start: u32,
80 fragment_len: usize,
81 is_forward: bool,
82) -> Fragment {
83 let (bases, ref_positions, hap_start) =
84 haplotype.extract_fragment(reference, ref_start, fragment_len);
85
86 Fragment {
87 bases,
88 ref_positions,
89 ref_start,
90 hap_start,
91 is_forward,
92 haplotype_index: haplotype.allele_index(),
93 }
94}
95
96#[must_use]
112pub fn extract_read_bases(
113 fragment_bases: &[u8],
114 read_length: usize,
115 adapter: &[u8],
116 is_r2: bool,
117) -> Vec<u8> {
118 let frag_len = fragment_bases.len();
119
120 if is_r2 {
121 let mut bases = Vec::with_capacity(read_length);
124 let take = frag_len.min(read_length);
125 let start = frag_len.saturating_sub(take);
126 bases.extend_from_slice(&fragment_bases[start..]);
127 reverse_complement(&mut bases);
128 append_adapter_and_pad(&mut bases, read_length, adapter);
129 bases
130 } else {
131 let mut bases = Vec::with_capacity(read_length);
133 let take = frag_len.min(read_length);
134 bases.extend_from_slice(&fragment_bases[..take]);
135 append_adapter_and_pad(&mut bases, read_length, adapter);
136 bases
137 }
138}
139
140#[must_use]
147pub fn lowercase_fraction(bases: &[u8]) -> f64 {
148 if bases.is_empty() {
149 return 0.0;
150 }
151 let lower = bases.iter().filter(|&&b| b.is_ascii_lowercase()).count();
156 lower as f64 / bases.len() as f64
157}
158
159pub fn uppercase_in_place(bases: &mut [u8]) {
161 for b in bases.iter_mut() {
162 b.make_ascii_uppercase();
163 }
164}
165
166fn append_adapter_and_pad(bases: &mut Vec<u8>, target_len: usize, adapter: &[u8]) {
168 if bases.len() < target_len {
169 let need = target_len - bases.len();
170 let adapter_take = need.min(adapter.len());
171 bases.extend_from_slice(&adapter[..adapter_take]);
172 while bases.len() < target_len {
174 bases.push(b'N');
175 }
176 }
177}
178
179#[cfg(test)]
180mod tests {
181 use super::*;
182
183 #[test]
184 fn test_reverse_complement() {
185 let mut seq = b"ACGT".to_vec();
186 reverse_complement(&mut seq);
187 assert_eq!(&seq, b"ACGT"); let mut seq2 = b"AACG".to_vec();
190 reverse_complement(&mut seq2);
191 assert_eq!(&seq2, b"CGTT");
192 }
193
194 #[test]
195 fn test_reverse_complement_with_n() {
196 let mut seq = b"ANGC".to_vec();
197 reverse_complement(&mut seq);
198 assert_eq!(&seq, b"GCNT");
199 }
200
201 #[test]
202 fn test_reverse_complement_preserves_lowercase() {
203 let mut seq = b"aCgT".to_vec();
206 reverse_complement(&mut seq);
207 assert_eq!(&seq, b"AcGt");
208 }
209
210 #[test]
211 fn test_extract_r1_full_fragment() {
212 let fragment = b"ACGTACGTAC";
213 let bases = extract_read_bases(fragment, 10, b"ADAPTER", false);
214 assert_eq!(&bases, b"ACGTACGTAC");
215 }
216
217 #[test]
218 fn test_extract_r1_fragment_longer_than_read() {
219 let fragment = b"ACGTACGTAC";
220 let bases = extract_read_bases(fragment, 5, b"ADAPTER", false);
221 assert_eq!(&bases, b"ACGTA");
222 }
223
224 #[test]
225 fn test_extract_r1_short_fragment_with_adapter() {
226 let fragment = b"ACG";
227 let adapter = b"TTTTTT";
228 let bases = extract_read_bases(fragment, 8, adapter, false);
229 assert_eq!(&bases, b"ACGTTTTT");
230 }
231
232 #[test]
233 fn test_extract_r2_full_fragment() {
234 let fragment = b"ACGTACGTAC";
235 let bases = extract_read_bases(fragment, 10, b"ADAPTER", true);
236 assert_eq!(&bases, b"GTACGTACGT");
238 }
239
240 #[test]
241 fn test_extract_r2_fragment_longer_than_read() {
242 let fragment = b"ACGTACGTAC";
243 let bases = extract_read_bases(fragment, 5, b"ADAPTER", true);
244 assert_eq!(&bases, b"GTACG");
246 }
247
248 #[test]
249 fn test_extract_r2_short_fragment_with_adapter() {
250 let fragment = b"ACG";
251 let adapter = b"TTTTTT";
252 let bases = extract_read_bases(fragment, 8, adapter, true);
253 assert_eq!(&bases, b"CGTTTTTT"); assert_eq!(bases.len(), 8);
256 }
257
258 #[test]
259 fn test_lowercase_fraction_empty() {
260 assert!(lowercase_fraction(b"").abs() < 1e-12);
261 }
262
263 #[test]
264 fn test_lowercase_fraction_all_upper() {
265 assert!(lowercase_fraction(b"ACGTACGT").abs() < 1e-12);
266 }
267
268 #[test]
269 fn test_lowercase_fraction_all_lower() {
270 assert!((lowercase_fraction(b"acgtacgt") - 1.0).abs() < 1e-12);
271 }
272
273 #[test]
274 fn test_lowercase_fraction_mixed() {
275 let f = lowercase_fraction(b"ACaGcTAtCA");
277 assert!((f - 0.3).abs() < 1e-10, "expected 0.3, got {f}");
278 }
279
280 #[test]
281 fn test_lowercase_fraction_ignores_non_letters() {
282 let f = lowercase_fraction(b"A-Na");
285 assert!((f - 0.25).abs() < 1e-10, "expected 0.25, got {f}");
286 }
287
288 #[test]
289 fn test_uppercase_in_place() {
290 let mut bases = b"aCgTnN".to_vec();
291 uppercase_in_place(&mut bases);
292 assert_eq!(&bases, b"ACGTNN");
293 }
294
295 #[test]
296 fn test_uppercase_in_place_empty() {
297 let mut bases: Vec<u8> = Vec::new();
298 uppercase_in_place(&mut bases);
299 assert!(bases.is_empty());
300 }
301
302 #[test]
303 fn test_extract_empty_fragment_all_adapter() {
304 let fragment = b"";
305 let adapter = b"AGATCGG";
306 let bases = extract_read_bases(fragment, 5, b"AGATCGG", false);
307 assert_eq!(&bases, b"AGATC");
308
309 let bases_r2 = extract_read_bases(fragment, 5, adapter, true);
310 assert_eq!(&bases_r2, b"AGATC");
311 }
312}