Skip to main content

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}