bio_seq/codec.rs
1//! Coding/Decoding trait for bit-packable enums representing sets of genomic symbols
2//!
3//! The [dna], [iupac], [text], and [amino] alphabets are built in.
4//!
5//! This trait implements the translation between the UTF-8 representation of an alphabet and its efficient bit-packing.
6//! The `BITS` attribute stores the number of bits used by the representation.
7//! ```
8//! use bio_seq::prelude::{Dna, Codec};
9//! use bio_seq::codec::text;
10//! assert_eq!(Dna::BITS, 2);
11//! assert_eq!(text::Dna::BITS, 8);
12//! ```
13//!
14//! ## Deriving custom Codecs
15//!
16//! Custom encodings can be easily defined on enums using the derivable `Codec` trait.
17//!
18//! ```
19//! use bio_seq::prelude::*;
20//!
21//! #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Codec)]
22//! #[repr(u8)]
23//! pub enum MyDna {
24//! A = 0b00,
25//! C = 0b01,
26//! G = 0b10,
27//! T = 0b11,
28//! }
29//!
30//! assert_eq!(MyDna::BITS, 2);
31//! ```
32//! ## Implementing custom Codecs
33//!
34//! Custom encodings can be defined on enums by implementing the `Codec` trait.
35//!
36//! ```
37//! use bio_seq::prelude;
38//! use bio_seq::prelude::Codec;
39//!
40//! #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
41//! pub enum Dna {
42//! A = 0b00,
43//! C = 0b01,
44//! G = 0b10,
45//! T = 0b11,
46//! }
47//!
48//! impl From<Dna> for u8 {
49//! fn from(base: Dna) -> u8 {
50//! match base {
51//! Dna::A => 0b00,
52//! Dna::C => 0b01,
53//! Dna::G => 0b10,
54//! Dna::T => 0b11,
55//! }
56//! }
57//! }
58//!
59//! impl Codec for Dna {
60//! const BITS: u8 = 2;
61//!
62//! fn unsafe_from_bits(bits: u8) -> Self {
63//! if let Some(base) = Self::try_from_bits(bits) {
64//! base
65//! } else {
66//! panic!("Unrecognised bit pattern!")
67//! }
68//! }
69//!
70//! fn try_from_bits(bits: u8) -> Option<Self> {
71//! match bits {
72//! 0b00 => Some(Dna::A),
73//! 0b01 => Some(Dna::C),
74//! 0b10 => Some(Dna::G),
75//! 0b11 => Some(Dna::T),
76//! _ => None,
77//! }
78//! }
79//!
80//! fn unsafe_from_ascii(chr: u8) -> Self {
81//! if let Some(base) = Self::try_from_ascii(chr) {
82//! base
83//! } else {
84//! panic!("Unrecognised bit pattern!")
85//! }
86//! }
87//!
88//! fn try_from_ascii(chr: u8) -> Option<Self> {
89//! match chr {
90//! b'A' => Some(Dna::A),
91//! b'C' => Some(Dna::C),
92//! b'G' => Some(Dna::G),
93//! b'T' => Some(Dna::T),
94//! _ => None,
95//! }
96//! }
97//!
98//! fn to_char(self) -> char {
99//! match self {
100//! Dna::A => 'A',
101//! Dna::C => 'C',
102//! Dna::G => 'G',
103//! Dna::T => 'T',
104//! }
105//! }
106//!
107//! fn to_bits(self) -> u8 {
108//! self as u8
109//! }
110//!
111//! fn items() -> impl Iterator<Item = Self> {
112//! vec![Dna::A, Dna::C, Dna::G, Dna::T].into_iter()
113//! }
114//! }
115//!
116//! ```
117
118use core::fmt;
119use core::hash::Hash;
120
121pub mod amino;
122pub mod dna;
123pub mod iupac;
124
125#[cfg(feature = "extra_codecs")]
126pub mod masked;
127
128#[cfg(feature = "extra_codecs")]
129pub mod degenerate;
130
131pub mod text;
132
133pub use bio_seq_derive::Codec;
134
135/// The binary encoding of an alphabet's symbols can be represented with any type.
136/// Encoding from ASCII bytes and decoding the representation is implemented through
137/// the `Codec` trait.
138///
139/// The intended representation is an `Enum`, transparently represented as a `u8`.
140pub trait Codec: fmt::Debug + Copy + Clone + PartialEq + Hash + Eq {
141 /// The number of bits used to encode the symbols. e.g. `Dna::BITS` = 2, `Iupac::BITS` = 4.
142 const BITS: u8;
143
144 /// Convert raw bits of binary encoding into enum item. Binary values
145 /// that don't match an enum member's discriminant will result in panic or random enum
146 /// item
147 fn unsafe_from_bits(b: u8) -> Self;
148
149 /// Fallibly convert raw bits into enum. If the binary value does not
150 /// match a discriminant, return `None`
151 fn try_from_bits(b: u8) -> Option<Self>;
152
153 /// Encode an ASCII byte as a codec enum item
154 fn unsafe_from_ascii(c: u8) -> Self;
155
156 /// Fallibly encode an ASCII byte as a codec enum item
157 fn try_from_ascii(c: u8) -> Option<Self>;
158
159 /// Decode enum item as a UTF-8 character
160 fn to_char(self) -> char;
161
162 /// Encode as raw bits
163 fn to_bits(self) -> u8;
164
165 /// Iterator over the symbols of the codec
166 fn items() -> impl Iterator<Item = Self>;
167}
168
169#[cfg(test)]
170mod tests {
171 use crate::ComplementMut;
172 #[cfg(feature = "extra_codecs")]
173 use crate::MaskableMut;
174 use crate::codec::{Codec, amino, dna::Dna, iupac::Iupac, text};
175 #[cfg(feature = "extra_codecs")]
176 use crate::codec::{degenerate, masked};
177
178 fn check_codec<C: Codec>() {
179 for symbol in C::items() {
180 assert_eq!(C::try_from_bits(symbol.to_bits()), Some(symbol));
181 assert_eq!(C::unsafe_from_bits(symbol.to_bits()), symbol);
182 assert_eq!(C::try_from_ascii(symbol.to_char() as u8), Some(symbol));
183 assert_eq!(C::unsafe_from_ascii(symbol.to_char() as u8), symbol);
184 }
185 }
186
187 fn check_comp_comp<C: Codec + ComplementMut>() {
188 for symbol in C::items() {
189 let mut symcomp = symbol;
190 symcomp.comp();
191 symcomp.comp();
192 assert_eq!(symcomp, symbol);
193 }
194 }
195
196 #[cfg(feature = "extra_codecs")]
197 fn check_mask_comp<C: Codec + ComplementMut + MaskableMut>() {
198 for symbol in C::items() {
199 let mut symcomp = symbol;
200 symcomp.comp();
201 symcomp.mask();
202 symcomp.comp();
203 symcomp.unmask();
204 assert_eq!(symcomp, symbol);
205 }
206 }
207
208 #[test]
209 fn dna_to_iupac() {
210 assert_eq!(Iupac::from(Dna::A), Iupac::A);
211 assert_eq!(Iupac::from(Dna::C), Iupac::C);
212 assert_eq!(Iupac::from(Dna::G), Iupac::G);
213 assert_eq!(Iupac::from(Dna::T), Iupac::T);
214
215 assert_ne!(Iupac::from(Dna::A), Iupac::T);
216 assert_ne!(Iupac::from(Dna::T), Iupac::A);
217 assert_ne!(Iupac::from(Dna::C), Iupac::T);
218 assert_ne!(Iupac::from(Dna::G), Iupac::T);
219 }
220
221 #[test]
222 fn check_codecs() {
223 check_codec::<Dna>();
224 check_codec::<Iupac>();
225 check_codec::<amino::Amino>();
226 check_codec::<text::Dna>();
227
228 #[cfg(feature = "extra_codecs")]
229 check_codec::<degenerate::MK>();
230 #[cfg(feature = "extra_codecs")]
231 check_codec::<degenerate::RY>();
232 #[cfg(feature = "extra_codecs")]
233 check_codec::<degenerate::WS>();
234 }
235
236 #[test]
237 fn check_comp_codecs() {
238 check_comp_comp::<Dna>();
239 check_comp_comp::<Iupac>();
240
241 #[cfg(feature = "extra_codecs")]
242 check_comp_comp::<degenerate::MK>();
243 #[cfg(feature = "extra_codecs")]
244 check_comp_comp::<degenerate::RY>();
245 #[cfg(feature = "extra_codecs")]
246 check_comp_comp::<degenerate::WS>();
247 //check_comp_comp::<text::Dna>();
248 }
249
250 #[test]
251 #[cfg(feature = "extra_codecs")]
252 fn check_mask_comp_codecs() {
253 check_mask_comp::<masked::Dna>();
254 //check_mask_comp::<masked::Iupac>();
255 }
256
257 #[test]
258 fn derive_weird_codec() {
259 #[derive(Codec, PartialEq, Clone, Copy, Debug, Hash, Eq)]
260 enum Weird {
261 A = 0,
262 B = 0b01,
263 C = 16,
264 }
265
266 assert_eq!(Weird::BITS, 5);
267 }
268}