Skip to main content

bio_seq/translation/
standard.rs

1//! Standard amino acid translation table
2use std::collections::HashMap;
3use std::sync::OnceLock;
4
5use crate::codec::Codec;
6use crate::codec::{amino::Amino, dna::Dna, iupac::Iupac};
7use crate::iupac;
8use crate::prelude::{__bio_seq_Lsb0, __bio_seq_bitarr, __bio_seq_count_words};
9use crate::prelude::{Seq, SeqArray, SeqSlice};
10use crate::translation::{PartialTranslationTable, TranslationError, TranslationTable};
11
12pub struct Standard;
13
14static AMINO_TO_IUPAC: OnceLock<HashMap<Amino, Option<Seq<Iupac>>>> = OnceLock::new();
15
16/// We can also translate from degenerate nucleotide symbols to amino acids where the coding isn't ambiguous
17///
18/// ```
19/// use bio_seq::prelude::*;
20/// use bio_seq::translation::PartialTranslationTable;
21/// use bio_seq::translation::STANDARD;
22///
23/// let seq: Seq<Iupac> = iupac!("AAYATHTTYTGYGTNTGGGGNGGNGTNTTYGTNTGYGCNGGNGCNCCNGCNCCNCCNGTNTAYACNTAYATGGARGGNGAYACNGAYATHCARGCNCAYACNCCNCAYATHAARGARAAYACNCARAAR").into();
24///     let aminos: Seq<Amino> = seq
25///     .chunks(3)
26///     .map(|codon| STANDARD.try_to_amino(&codon).unwrap())
27///     .collect::<Seq<Amino>>();
28/// assert_eq!(
29///     aminos,
30///     Seq::<Amino>::try_from("NIFCVWGGVFVCAGAPAPPVYTYMEGDTDIQAHTPHIKENTQK").unwrap()
31/// );
32/// ```
33static IUPAC_TO_AMINO: OnceLock<[(Seq<Iupac>, Amino); 29]> = OnceLock::new();
34
35fn initialise_iupac_to_amino() -> [(Seq<Iupac>, Amino); 29] {
36    [
37        (iupac!("GCN").into(), Amino::A),
38        (iupac!("TGY").into(), Amino::C),
39        (iupac!("GAY").into(), Amino::D),
40        (iupac!("GAR").into(), Amino::E),
41        (iupac!("TTY").into(), Amino::F),
42        (iupac!("GGN").into(), Amino::G),
43        (iupac!("CAY").into(), Amino::H),
44        (iupac!("ATH").into(), Amino::I),
45        (iupac!("AAR").into(), Amino::K),
46        (iupac!("CTN").into(), Amino::L),
47        (iupac!("TTR").into(), Amino::L),
48        (iupac!("CTY").into(), Amino::L),
49        (iupac!("YTR").into(), Amino::L),
50        (iupac!("ATG").into(), Amino::M),
51        (iupac!("AAY").into(), Amino::N),
52        (iupac!("CCN").into(), Amino::P),
53        (iupac!("CAR").into(), Amino::Q),
54        (iupac!("CGN").into(), Amino::R),
55        (iupac!("AGR").into(), Amino::R),
56        (iupac!("CGY").into(), Amino::R),
57        (iupac!("MGR").into(), Amino::R),
58        (iupac!("TCN").into(), Amino::S),
59        (iupac!("AGY").into(), Amino::S),
60        (iupac!("ACN").into(), Amino::T),
61        (iupac!("GTN").into(), Amino::V),
62        (iupac!("TGG").into(), Amino::W),
63        (iupac!("TAY").into(), Amino::Y),
64        (iupac!("TAR").into(), Amino::X),
65        (iupac!("TRA").into(), Amino::X),
66    ]
67}
68
69fn initialise_amino_to_iupac() -> HashMap<Amino, Option<Seq<Iupac>>> {
70    let mut amino_to_iupac = HashMap::new();
71    for (iupac_set, amino) in IUPAC_TO_AMINO.get_or_init(initialise_iupac_to_amino) {
72        if amino_to_iupac.contains_key(amino) {
73            amino_to_iupac.insert(*amino, None);
74        } else {
75            amino_to_iupac.insert(*amino, Some(iupac_set.clone()));
76        }
77    }
78
79    amino_to_iupac
80}
81
82impl TranslationTable<Dna, Amino> for Standard {
83    fn to_amino(&self, codon: &SeqSlice<Dna>) -> Amino {
84        assert!(codon.len() == 3, "Invalid codon length {}", codon.len());
85        // It should be possible to assert that this is safe at compile time
86        Amino::unsafe_from_bits(Into::<u8>::into(codon))
87    }
88
89    /// There are no unambiguous translations from amino acid to DNA codon except M and W
90    fn to_codon(&self, amino: Amino) -> Result<Seq<Dna>, TranslationError<Dna, Amino>> {
91        Err(TranslationError::AmbiguousCodon(amino))
92    }
93}
94
95impl PartialTranslationTable<Iupac, Amino> for Standard {
96    fn try_to_amino(
97        &self,
98        codon: &SeqSlice<Iupac>,
99    ) -> Result<Amino, TranslationError<Iupac, Amino>> {
100        if codon.len() != 3 {
101            return Err(TranslationError::InvalidCodon(codon.into()));
102        }
103        for (iupac_set, amino) in IUPAC_TO_AMINO.get_or_init(initialise_iupac_to_amino) {
104            if iupac_set.contains(codon) {
105                return Ok(*amino);
106            }
107        }
108
109        Err(TranslationError::AmbiguousTranslation(codon.into()))
110    }
111
112    fn try_to_codon(&self, amino: Amino) -> Result<Seq<Iupac>, TranslationError<Iupac, Amino>> {
113        let amino_to_iupac = AMINO_TO_IUPAC.get_or_init(initialise_amino_to_iupac);
114
115        match amino_to_iupac.get(&amino) {
116            Some(Some(codon)) => Ok(codon.clone()),
117            None | Some(None) => Err(TranslationError::AmbiguousCodon(amino)),
118        }
119    }
120}
121
122pub const STANDARD: Standard = Standard;
123
124#[cfg(test)]
125mod tests {
126    use crate::prelude::*;
127    use crate::translation::STANDARD;
128    use crate::translation::{PartialTranslationTable, TranslationError, TranslationTable};
129
130    #[test]
131    fn dna_to_amino() {
132        let seq: Seq<Dna> =
133            dna!("GCATGCGACGAATTCGGACACATAAAACTAATGAACCCACAAAGAAGCACAGTATGGTACTAA").into();
134        let aminos: Seq<Amino> = seq
135            .chunks(3)
136            .map(|codon| STANDARD.to_amino(codon.into()))
137            .collect::<Seq<Amino>>();
138        assert_eq!(
139            aminos,
140            Seq::<Amino>::try_from("ACDEFGHIKLMNPQRSTVWY*").unwrap()
141        );
142        assert_ne!(
143            aminos,
144            Seq::<Amino>::try_from("ACDEFGHIKLMNPQRSTVWY*A").unwrap()
145        );
146        assert_ne!(
147            aminos,
148            Seq::<Amino>::try_from("CDEFGHIKLMNPQRSTVWY*A").unwrap()
149        );
150    }
151
152    #[test]
153    fn alternate_codons() {
154        let seq: Seq<Dna> = dna!("AGCTCGTCATCCTCTAGTTGATAATAG").into();
155        let aminos: Seq<Amino> = seq
156            .chunks(3)
157            .map(|codon| STANDARD.to_amino(codon.into()))
158            .collect::<Seq<Amino>>();
159        assert_eq!(aminos, Seq::<Amino>::try_from("SSSSSS***").unwrap());
160    }
161
162    #[test]
163    fn test_debruin_sequence() {
164        let seq: Seq<Dna> =
165            dna!("AATTTGTGGGTTCGTCTGCGGCTCCGCCCTTAGTACTATGAGGACGATCAGCACCATAAGAACAAA").into();
166        let aminos: Seq<Amino> = seq
167            .windows(3)
168            .map(|codon| STANDARD.to_amino(&codon))
169            .collect::<Seq<Amino>>();
170        assert_eq!(seq.len() - 2, aminos.len());
171        assert_eq!(
172            aminos,
173            Seq::<Amino>::try_from(
174                "NIFLCVWGGVFSRVSLCARGALSPRAPPLL*SVYTLYM*ERGDTRDISQSAHTPHI*KRENTQK"
175            )
176            .unwrap()
177        );
178    }
179
180    #[test]
181    fn test_iupac_codons() {
182        let seq = iupac!("AATTTGTGGGTTCGTCTGCGGCTCCGCCCTTAGTACTATGAGGACGATCAGCACCATAAGAACAAA");
183        let aminos: Seq<Amino> = seq
184            .windows(3)
185            .map(|codon| STANDARD.try_to_amino(&codon).unwrap())
186            .collect::<Seq<Amino>>();
187        assert_eq!(
188            aminos,
189            Seq::<Amino>::try_from(
190                "NIFLCVWGGVFSRVSLCARGALSPRAPPLL*SVYTLYM*ERGDTRDISQSAHTPHI*KRENTQK"
191            )
192            .unwrap()
193        );
194    }
195
196    #[test]
197    fn test_iupac_aminos() {
198        let seq: Seq<Amino> = Seq::<Amino>::try_from(
199            "NIFLCVWGGVFSRVSLCARGALSPRAPPLL*SVYTLYM*ERGDTRDISQSAHTPHI*KRENTQK",
200        )
201        .unwrap();
202        let mut iupacs: Seq<Iupac> = Seq::new();
203        let mut ambs: Seq<Amino> = Seq::new();
204
205        for amino in &seq {
206            match STANDARD.try_to_codon(amino) {
207                Ok(codon) => iupacs.extend(&codon),
208                Err(TranslationError::AmbiguousCodon(amino)) => ambs.push(amino),
209                _ => panic!(),
210            }
211        }
212        assert_eq!(
213            iupacs,
214            iupac!(
215                "AAYATHTTYTGYGTNTGGGGNGGNGTNTTYGTNTGYGCNGGNGCNCCNGCNCCNCCNGTNTAYACNTAYATGGARGGNGAYACNGAYATHCARGCNCAYACNCCNCAYATHAARGARAAYACNCARAAR"
216            )
217        );
218        assert_eq!(
219            ambs,
220            Seq::<Amino>::try_from("LSRSLRLSRLL*SL*RRSS*R").unwrap()
221        );
222    }
223
224    #[test]
225    fn test_iupac_ambiguous_codons() {
226        let seq: Seq<Iupac> = iupac!("AAYATHTTYTGYGTNTGGGGNGGNGTNTTYGTNTGYGCNGGNGCNCCNGCNCCNCCNGTNTAYACNTAYATGGARGGNGAYACNGAYATHCARGCNCAYACNCCNCAYATHAARGARAAYACNCARAAR").into();
227        let aminos: Seq<Amino> = seq
228            .chunks(3)
229            .map(|codon| STANDARD.try_to_amino(&codon).unwrap())
230            .collect::<Seq<Amino>>();
231        assert_eq!(
232            aminos,
233            Seq::<Amino>::try_from("NIFCVWGGVFVCAGAPAPPVYTYMEGDTDIQAHTPHIKENTQK").unwrap()
234        );
235    }
236
237    #[test]
238    fn iupac_to_amino_errors() {
239        assert_eq!(
240            STANDARD.try_to_amino(&iupac!("TN")),
241            Err(TranslationError::InvalidCodon(
242                Seq::<Iupac>::try_from("TN").unwrap()
243            ))
244        );
245
246        assert_eq!(
247            STANDARD.try_to_amino(&iupac!("NYTN")),
248            Err(TranslationError::InvalidCodon("NYTN".try_into().unwrap()))
249        );
250        assert_eq!(
251            STANDARD.try_to_amino(&iupac!("YTN")),
252            Err(TranslationError::AmbiguousTranslation(
253                "YTN".try_into().unwrap()
254            ))
255        );
256    }
257
258    #[test]
259    fn ambiguous_amino_to_iupac() {
260        assert_eq!(
261            STANDARD.try_to_codon(Amino::L),
262            Err(TranslationError::AmbiguousCodon(Amino::L))
263        );
264        assert_eq!(
265            STANDARD.try_to_codon(Amino::S),
266            Err(TranslationError::AmbiguousCodon(Amino::S))
267        );
268        assert_eq!(
269            STANDARD.try_to_codon(Amino::R),
270            Err(TranslationError::AmbiguousCodon(Amino::R))
271        );
272        assert_eq!(
273            STANDARD.try_to_codon(Amino::X),
274            Err(TranslationError::AmbiguousCodon(Amino::X))
275        );
276
277        assert_ne!(
278            STANDARD.try_to_codon(Amino::A),
279            Err(TranslationError::AmbiguousCodon(Amino::A))
280        );
281        assert_ne!(
282            STANDARD.try_to_codon(Amino::N),
283            Err(TranslationError::AmbiguousCodon(Amino::N))
284        );
285        assert_ne!(
286            STANDARD.try_to_codon(Amino::W),
287            Err(TranslationError::AmbiguousCodon(Amino::W))
288        );
289        assert_ne!(
290            STANDARD.try_to_codon(Amino::M),
291            Err(TranslationError::AmbiguousCodon(Amino::M))
292        );
293    }
294}