Skip to main content

packed_seq/
packed_n_seq.rs

1use wide::i8x32;
2
3use crate::packed_seq::read_slice_32;
4
5use super::*;
6
7#[derive(Clone, Copy, Debug, MemSize, MemDbg)]
8#[cfg_attr(feature = "pyo3", pyo3::pyclass)]
9pub 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
22/// Implement a subset of `Seq` for `PackedNSeq`.
23impl<'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
39/// Implement a subset of `SeqVec` for `PackedNSeqVec`.
40impl PackedNSeqVec {
41    pub fn clear(&mut self) {
42        self.seq.clear();
43        self.ambiguous.clear();
44    }
45
46    #[cfg(feature = "rand")]
47    pub fn random(len: usize, n_frac: f32) -> Self {
48        let seq = PackedSeqVec::random(len);
49        let ambiguous = BitSeqVec::random(len, n_frac);
50        Self { seq, ambiguous }
51    }
52
53    pub fn as_slice(&self) -> PackedNSeq<'_> {
54        PackedNSeq {
55            seq: self.seq.as_slice(),
56            ambiguous: self.ambiguous.as_slice(),
57        }
58    }
59
60    pub fn slice(&self, range: Range<usize>) -> PackedNSeq<'_> {
61        self.as_slice().slice(range).to_owned()
62    }
63
64    pub fn push_ascii(&mut self, seq: &[u8]) -> Range<usize> {
65        let r1 = self.seq.push_ascii(seq);
66        let r2 = self.ambiguous.push_ascii(seq);
67        assert_eq!(r1, r2);
68        r1
69    }
70
71    pub fn from_ascii(seq: &[u8]) -> Self {
72        Self {
73            seq: PackedSeqVec::from_ascii(seq),
74            ambiguous: BitSeqVec::from_ascii(seq),
75        }
76    }
77
78    /// Create a mask that is 1 for all non-ACGT bases and for all low-quality bases with quality `<threshold`.
79    pub fn from_ascii_and_quality(seq: &[u8], quality: &[u8], threshold: usize) -> Self {
80        assert_eq!(seq.len(), quality.len());
81
82        let mut ambiguous = BitSeqVec::from_ascii(seq);
83
84        // Low-quality bases are also ambiguous.
85        {
86            let t = (b'!' + threshold as u8) as i8;
87            let t_simd = i8x32::splat(t);
88            let ambiguous = ambiguous.seq.as_chunks_mut::<4>().0;
89            for i in (0..quality.len()).step_by(32) {
90                let chunk = i8x32::from(unsafe {
91                    std::mem::transmute::<_, i8x32>(read_slice_32(quality, i))
92                });
93
94                let mask = t_simd.simd_lt(chunk).to_bitmask() as u32;
95                let ambi = &mut ambiguous[i / 32];
96                *ambi = (u32::from_ne_bytes(*ambi) | mask).to_ne_bytes();
97            }
98        }
99
100        Self {
101            seq: PackedSeqVec::from_ascii(seq),
102            ambiguous,
103        }
104    }
105
106    /// Create a mask that is 1 for all non-ACGT bases and for all low-quality bases with quality `<threshold`.
107    pub fn push_from_ascii_and_quality(&mut self, seq: &[u8], quality: &[u8], min_qual: u8) {
108        let r = self.seq.push_ascii(seq);
109        let r2 = self.ambiguous.push_ascii(seq);
110        assert_eq!(r, r2);
111
112        assert_eq!(seq.len(), quality.len());
113
114        // Low-quality bases are also ambiguous.
115        let t = b'!' + min_qual;
116        let t_simd = i8x32::splat(t as i8);
117
118        let mut idx = r2.start;
119        let mut i = 0;
120        while idx % 8 != 0 {
121            self.ambiguous.seq[idx / 8] |= ((quality[i] < t) as u8) << (idx % 8);
122            idx += 1;
123            i += 1;
124        }
125        let quality = &quality[i..];
126
127        let ambiguous = self.ambiguous.seq[idx / 8..].as_chunks_mut::<4>().0;
128        for i in (0..quality.len()).step_by(32) {
129            let chunk =
130                i8x32::from(unsafe { std::mem::transmute::<_, i8x32>(read_slice_32(quality, i)) });
131
132            let mask = t_simd.simd_lt(chunk).to_bitmask() as u32;
133            let ambi = &mut ambiguous[i / 32];
134            *ambi = (u32::from_ne_bytes(*ambi) | mask).to_ne_bytes();
135        }
136    }
137
138    /// Read a fasta/fastq file at the given path into a `PackedNSeq`, separating records with `N`.
139    #[cfg(feature = "file_io")]
140    pub fn from_fastx(path: &std::path::Path) -> Self {
141        let mut ascii = Vec::with_capacity(16000);
142
143        let mut seq = Self::default();
144
145        let mut reader = needletail::parse_fastx_file(&path).unwrap();
146        while let Some(record) = reader.next() {
147            let seqrec = record.expect("Invalid FASTA/Q record");
148            ascii.extend_from_slice(&seqrec.seq());
149            ascii.push(b'N');
150
151            // Convert from ascii dna to packed dna+mask in batches of 16kbp.
152            if ascii.len() > 16000 {
153                seq.push_ascii(&ascii);
154                ascii.clear();
155            }
156        }
157
158        if ascii.len() > 0 {
159            seq.push_ascii(&ascii);
160        }
161
162        seq
163    }
164
165    /// Read a fasta/fastq file at the given path into a `PackedNSeq`, separating records with `N`.
166    /// Low-quality bases are masked out.
167    #[cfg(feature = "file_io")]
168    pub fn from_fastq_with_quality(path: &std::path::Path, min_qual: u8) -> Self {
169        let mut ascii = vec![];
170        let mut qual = vec![];
171
172        let mut seq = Self::default();
173
174        let mut reader = needletail::parse_fastx_file(&path).unwrap();
175        while let Some(record) = reader.next() {
176            let seqrec = record.expect("Invalid FASTA/Q record");
177            ascii.extend_from_slice(&seqrec.seq());
178            qual.extend_from_slice(&seqrec.qual().unwrap());
179            ascii.push(b'N');
180            qual.push(0);
181
182            // Convert from ascii dna+qual to packed dna+mask in batches of 16kbp.
183            if ascii.len() > 16000 {
184                seq.push_from_ascii_and_quality(&ascii, &qual, min_qual);
185                ascii.clear();
186                qual.clear();
187            }
188        }
189
190        if ascii.len() > 0 {
191            seq.push_from_ascii_and_quality(&ascii, &qual, min_qual);
192        }
193
194        seq
195    }
196}