packed_seq/
packed_n_seq.rs1use wide::i8x32;
2
3use crate::packed_seq::read_slice_32;
4
5use super::*;
6
7#[derive(Clone, Copy, Debug, MemSize, MemDbg)]
8pub struct PackedNSeq<'s> {
10 pub seq: PackedSeq<'s>,
11 pub ambiguous: BitSeq<'s>,
12}
13
14#[derive(Clone, Debug, MemSize, MemDbg, Default)]
15#[cfg_attr(feature = "pyo3", pyo3::pyclass)]
16#[cfg_attr(feature = "epserde", derive(epserde::Epserde))]
17pub struct PackedNSeqVec {
18 pub seq: PackedSeqVec,
19 pub ambiguous: BitSeqVec,
20}
21
22impl<'s> PackedNSeq<'s> {
24 pub fn to_revcomp(&self) -> PackedNSeqVec {
25 PackedNSeqVec {
26 seq: self.seq.to_revcomp(),
27 ambiguous: self.ambiguous.to_revcomp(),
28 }
29 }
30
31 pub fn slice(&self, range: Range<usize>) -> PackedNSeq<'s> {
32 PackedNSeq {
33 seq: self.seq.slice(range.clone()),
34 ambiguous: self.ambiguous.slice(range),
35 }
36 }
37}
38
39impl PackedNSeqVec {
41 #[cfg(feature = "file_io")]
43 fn assemble_parallel_chunks(chunks: Vec<Self>, num_threads: usize) -> Self {
44 let len = chunks
45 .iter()
46 .map(|chunk| chunk.seq.len().next_multiple_of(8))
47 .sum();
48 let mut output = Self {
49 seq: PackedSeqVec::from_raw_parts(vec![0; len / 4], len),
50 ambiguous: BitSeqVec::with_len(len),
51 };
52 let mut seq_out = &mut output.seq.seq[..len / 4];
53 let mut mask_out = &mut output.ambiguous.seq[..len / 8];
54 let mut jobs = Vec::with_capacity(chunks.len());
55 for chunk in chunks {
56 let aligned_len = chunk.seq.len().next_multiple_of(8);
57 let (seq_dest, rest) = seq_out.split_at_mut(aligned_len / 4);
58 seq_out = rest;
59 let (mask_dest, rest) = mask_out.split_at_mut(aligned_len / 8);
60 mask_out = rest;
61 jobs.push((chunk, seq_dest, mask_dest));
62 }
63 let jobs = std::sync::Mutex::new(jobs.into_iter());
64 std::thread::scope(|scope| {
65 for _ in 0..num_threads.max(1) {
66 let jobs = &jobs;
67 scope.spawn(move || {
68 loop {
69 let job = jobs.lock().unwrap().next();
70 let Some((chunk, seq_dest, mask_dest)) = job else {
71 break;
72 };
73 let chunk_len = chunk.seq.len();
74 let seq_bytes = chunk_len.div_ceil(4);
75 let mask_bytes = chunk_len.div_ceil(8);
76 seq_dest[..seq_bytes].copy_from_slice(&chunk.seq.seq[..seq_bytes]);
77 mask_dest[..mask_bytes].copy_from_slice(&chunk.ambiguous.seq[..mask_bytes]);
78 seq_dest[seq_bytes..].fill(u8::MAX);
80 if chunk_len % 4 != 0 {
81 seq_dest[seq_bytes - 1] |= u8::MAX << (2 * (chunk_len % 4));
82 }
83 if chunk_len % 8 != 0 {
84 mask_dest[mask_bytes - 1] |= u8::MAX << (chunk_len % 8);
85 }
86 }
87 });
88 }
89 });
90 output
91 }
92
93 pub fn clear(&mut self) {
94 self.seq.clear();
95 self.ambiguous.clear();
96 }
97
98 #[cfg(feature = "rand")]
99 pub fn random(len: usize, n_frac: f32) -> Self {
100 let seq = PackedSeqVec::random(len);
101 let ambiguous = BitSeqVec::random(len, n_frac);
102 Self { seq, ambiguous }
103 }
104
105 pub fn as_slice(&self) -> PackedNSeq<'_> {
106 PackedNSeq {
107 seq: self.seq.as_slice(),
108 ambiguous: self.ambiguous.as_slice(),
109 }
110 }
111
112 pub fn slice(&self, range: Range<usize>) -> PackedNSeq<'_> {
113 self.as_slice().slice(range).to_owned()
114 }
115
116 pub fn push_ascii(&mut self, seq: &[u8]) -> Range<usize> {
117 let r1 = self.seq.push_ascii(seq);
118 let r2 = self.ambiguous.push_ascii(seq);
119 assert_eq!(r1, r2);
120 r1
121 }
122
123 pub fn from_ascii(seq: &[u8]) -> Self {
124 Self {
125 seq: PackedSeqVec::from_ascii(seq),
126 ambiguous: BitSeqVec::from_ascii(seq),
127 }
128 }
129
130 pub fn from_ascii_and_quality(seq: &[u8], quality: &[u8], threshold: usize) -> Self {
132 assert_eq!(seq.len(), quality.len());
133
134 let mut ambiguous = BitSeqVec::from_ascii(seq);
135
136 {
138 let t = (b'!' + threshold as u8) as i8;
139 let t_simd = i8x32::splat(t);
140 let ambiguous = ambiguous.seq.as_chunks_mut::<4>().0;
141 for i in (0..quality.len()).step_by(32) {
142 let chunk = i8x32::from(unsafe {
143 std::mem::transmute::<_, i8x32>(read_slice_32(quality, i))
144 });
145
146 let mut mask = chunk.simd_lt(t_simd).to_bitmask() as u32;
147 let remaining = quality.len() - i;
148 if remaining < 32 {
149 mask &= (1u32 << remaining) - 1;
150 }
151 let ambi = &mut ambiguous[i / 32];
152 *ambi = (u32::from_ne_bytes(*ambi) | mask).to_ne_bytes();
153 }
154 }
155
156 Self {
157 seq: PackedSeqVec::from_ascii(seq),
158 ambiguous,
159 }
160 }
161
162 pub fn push_from_ascii_and_quality(&mut self, seq: &[u8], quality: &[u8], min_qual: u8) {
164 let r = self.seq.push_ascii(seq);
165 let r2 = self.ambiguous.push_ascii(seq);
166 assert_eq!(r, r2);
167
168 assert_eq!(seq.len(), quality.len());
169
170 let t = b'!' + min_qual;
172 let t_simd = i8x32::splat(t as i8);
173
174 let shift = r2.start % 8;
175 let ambiguous = &mut self.ambiguous.seq[r2.start / 8..];
176 for i in (0..quality.len()).step_by(32) {
177 let chunk =
178 i8x32::from(unsafe { std::mem::transmute::<_, i8x32>(read_slice_32(quality, i)) });
179
180 let mut mask = chunk.simd_lt(t_simd).to_bitmask() as u32;
181 let remaining = quality.len() - i;
182 if remaining < 32 {
183 mask &= (1u32 << remaining) - 1;
184 }
185 let mask = (mask as u64) << shift;
186 let offset = i / 8;
187 let word = u32::from_le_bytes(ambiguous[offset..offset + 4].try_into().unwrap());
188 ambiguous[offset..offset + 4].copy_from_slice(&(word | mask as u32).to_le_bytes());
189 ambiguous[offset + 4] |= (mask >> 32) as u8;
190 }
191 }
192
193 #[cfg(feature = "file_io")]
195 pub fn from_fastx(path: &std::path::Path) -> Self {
196 use helicase::{HelicaseParser, input::FromFile};
197
198 const CONFIG: helicase::Config = helicase::ParserOptions::default()
199 .ignore_headers()
200 .dna_string()
201 .config();
202 let mut ascii = Vec::with_capacity(16000);
203
204 let mut seq = Self::default();
205
206 let mut reader = helicase::FastxParser::<CONFIG>::from_file(path).unwrap();
207 while let Some(event) = reader.next() {
208 event.expect("Invalid FASTA/Q record");
209 ascii.extend_from_slice(reader.get_dna_string());
210 ascii.push(b'N');
211
212 if ascii.len() > 16000 {
214 seq.push_ascii(&ascii);
215 ascii.clear();
216 }
217 }
218
219 if ascii.len() > 0 {
220 seq.push_ascii(&ascii);
221 }
222
223 seq
224 }
225
226 #[cfg(feature = "file_io")]
232 pub fn from_fastx_parallel(path: &std::path::Path, num_threads: usize) -> Self {
233 use helicase::HelicaseParser;
234
235 const CONFIG: helicase::Config = helicase::ParserOptions::default()
236 .ignore_headers()
237 .ignore_quality()
238 .dna_string()
239 .config();
240 let chunks = std::sync::Mutex::new(Vec::new());
241 helicase::ParallelFastxParser::<CONFIG>::from_file(path)
242 .unwrap()
243 .threads(num_threads)
244 .chunks(num_threads.max(1))
245 .for_each(|mut parser| {
246 let mut seq = Self::default();
247 let mut ascii = Vec::with_capacity(16000);
248 while let Some(event) = parser.next() {
249 event?;
250 ascii.extend_from_slice(parser.get_dna_string());
251 ascii.push(b'N');
252 if ascii.len() >= 16000 {
253 seq.push_ascii(&ascii);
254 ascii.clear();
255 }
256 }
257 if !ascii.is_empty() {
258 seq.push_ascii(&ascii);
259 }
260 chunks.lock().unwrap().push(seq);
261 Ok(())
262 })
263 .unwrap();
264 let chunks = chunks.into_inner().unwrap();
265 Self::assemble_parallel_chunks(chunks, num_threads)
266 }
267
268 #[cfg(feature = "file_io")]
271 pub fn from_fastq_with_quality(path: &std::path::Path, min_qual: u8) -> Self {
272 use helicase::{HelicaseParser, input::FromFile};
273
274 const CONFIG: helicase::Config = helicase::ParserOptions::default()
275 .ignore_headers()
276 .dna_string()
277 .compute_quality()
278 .config();
279 let mut ascii = vec![];
280 let mut qual = vec![];
281
282 let mut seq = Self::default();
283
284 let mut reader = helicase::FastxParser::<CONFIG>::from_file(path).unwrap();
285 while let Some(event) = reader.next() {
286 event.expect("Invalid FASTA/Q record");
287 ascii.extend_from_slice(reader.get_dna_string());
288 qual.extend_from_slice(reader.get_quality().expect("Expected FASTQ quality scores"));
289 ascii.push(b'N');
290 qual.push(0);
291
292 if ascii.len() > 16000 {
294 seq.push_from_ascii_and_quality(&ascii, &qual, min_qual);
295 ascii.clear();
296 qual.clear();
297 }
298 }
299
300 if ascii.len() > 0 {
301 seq.push_from_ascii_and_quality(&ascii, &qual, min_qual);
302 }
303
304 seq
305 }
306
307 #[cfg(feature = "file_io")]
313 pub fn from_fastq_with_quality_parallel(
314 path: &std::path::Path,
315 min_qual: u8,
316 num_threads: usize,
317 ) -> Self {
318 use helicase::HelicaseParser;
319
320 const CONFIG: helicase::Config = helicase::ParserOptions::default()
321 .ignore_headers()
322 .dna_string()
323 .compute_quality()
324 .config();
325 let chunks = std::sync::Mutex::new(Vec::new());
326 helicase::ParallelFastxParser::<CONFIG>::from_file(path)
327 .unwrap()
328 .threads(num_threads)
329 .chunks(num_threads.max(1))
330 .for_each(|mut parser| {
331 let mut seq = Self::default();
332 let mut ascii = Vec::with_capacity(16000);
333 let mut quality = Vec::with_capacity(16000);
334 while let Some(event) = parser.next() {
335 event?;
336 ascii.extend_from_slice(parser.get_dna_string());
337 ascii.push(b'N');
338 quality.extend_from_slice(
339 parser.get_quality().expect("Expected FASTQ quality scores"),
340 );
341 quality.push(0);
342 if ascii.len() >= 16000 {
343 seq.push_from_ascii_and_quality(&ascii, &quality, min_qual);
344 ascii.clear();
345 quality.clear();
346 }
347 }
348 if !ascii.is_empty() {
349 seq.push_from_ascii_and_quality(&ascii, &quality, min_qual);
350 }
351 chunks.lock().unwrap().push(seq);
352 Ok(())
353 })
354 .unwrap();
355 let chunks = chunks.into_inner().unwrap();
356 Self::assemble_parallel_chunks(chunks, num_threads)
357 }
358}