1use core::cmp::Eq;
85use core::fmt;
86use std::collections::HashMap;
87
88use crate::codec::Codec;
89use crate::prelude::{Amino, Dna, Seq, SeqSlice};
90
91mod standard;
92
93pub use crate::translation::standard::STANDARD;
94
95#[derive(Debug, PartialEq, Eq, Clone)]
97pub enum TranslationError<A: Codec = Dna, B: Codec = Amino> {
98 AmbiguousCodon(B),
100 AmbiguousTranslation(Seq<A>),
102 InvalidCodon(Seq<A>),
104 InvalidAmino(B),
106}
107
108impl<A: Codec, B: Codec> fmt::Display for TranslationError<A, B> {
109 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110 match self {
111 TranslationError::AmbiguousCodon(amino) => {
112 let amino = amino.to_char();
113 write!(f, "Multiple codon sequences: {amino}")
114 }
115 TranslationError::AmbiguousTranslation(codon) => {
116 write!(f, "Ambiguous translations for codon: {codon}")
117 }
118 TranslationError::InvalidCodon(codon) => write!(f, "Invalid codon sequence: {codon}"),
119 TranslationError::InvalidAmino(amino) => {
120 let amino = amino.to_char();
121 write!(f, "Invalid amino acid character: {amino}")
122 }
123 }
124 }
125}
126
127impl<A: Codec, B: Codec> std::error::Error for TranslationError<A, B> {}
129
130pub trait TranslationTable<A: Codec, B: Codec> {
132 fn to_amino(&self, codon: &SeqSlice<A>) -> B;
133
134 fn to_codon(&self, amino: B) -> Result<Seq<A>, TranslationError<A, B>>;
138}
139
140pub trait PartialTranslationTable<A: Codec, B: Codec> {
142 fn try_to_amino(&self, codon: &SeqSlice<A>) -> Result<B, TranslationError<A, B>>;
147 fn try_to_codon(&self, amino: B) -> Result<Seq<A>, TranslationError<A, B>>;
151}
152
153pub struct CodonTable<A: Codec, B: Codec> {
155 table: HashMap<Seq<A>, B>,
157 inverse_table: HashMap<B, Option<Seq<A>>>,
158}
159
160impl<A: Codec, B: Codec> CodonTable<A, B> {
161 pub fn from_map<T>(table: T) -> Self
162 where
163 T: Into<HashMap<Seq<A>, B>>,
164 {
165 let table: HashMap<Seq<A>, B> = table.into();
166 let mut inverse_table = HashMap::new();
167 for (codon, amino) in &table {
168 if inverse_table.contains_key(amino) {
169 inverse_table.insert(*amino, None);
170 } else {
171 inverse_table.insert(*amino, Some(codon.clone()));
172 }
173 }
174 CodonTable {
175 table,
176 inverse_table,
177 }
178 }
179}
180
181impl<A: Codec, B: Codec> PartialTranslationTable<A, B> for CodonTable<A, B> {
182 fn try_to_amino(&self, codon: &SeqSlice<A>) -> Result<B, TranslationError<A, B>> {
183 self.table
184 .get(codon)
185 .ok_or_else(|| TranslationError::InvalidCodon(codon.into()))
186 .copied()
187 }
188
189 fn try_to_codon(&self, amino: B) -> Result<Seq<A>, TranslationError<A, B>> {
190 if let Some(codon) = self.inverse_table.get(&amino) {
191 match codon {
192 Some(codon) => Ok(codon.clone()),
193 None => Err(TranslationError::AmbiguousCodon(amino)),
194 }
195 } else {
196 Err(TranslationError::InvalidAmino(amino))
197 }
198 }
199}
200
201#[cfg(test)]
202mod tests {
203 use crate::prelude::*;
204 use crate::translation::{
205 CodonTable, PartialTranslationTable, TranslationError, TranslationTable,
206 };
207
208 #[test]
209 fn custom_codon_table() {
210 let mito: [(Seq<Dna>, Amino); 6] = [
211 (dna!("AAA").into(), Amino::A),
212 (dna!("ATG").into(), Amino::A),
213 (dna!("CCC").into(), Amino::C),
214 (dna!("GGG").into(), Amino::E),
215 (dna!("TTT").into(), Amino::D),
216 (dna!("TTA").into(), Amino::F),
217 ];
218
219 let table = CodonTable::from_map(mito);
220
221 let seq: Seq<Dna> = dna!("AAACCCGGGTTTTTATTAATG").into();
222 let mut amino_seq: Seq<Amino> = Seq::new();
223 for codon in seq.chunks(3) {
224 amino_seq.push(table.try_to_amino(codon).unwrap());
225 }
226 assert_eq!(amino_seq, Seq::<Amino>::try_from("ACEDFFA").unwrap());
227
228 assert_ne!(table.try_to_codon(Amino::E), Ok(dna!("CCC").into()));
229 assert_eq!(table.try_to_codon(Amino::C), Ok(dna!("CCC").into()));
230 assert_eq!(
231 table.try_to_codon(Amino::A),
232 Err(TranslationError::AmbiguousCodon(Amino::A))
233 );
234 assert_eq!(
235 table.try_to_codon(Amino::X),
236 Err(TranslationError::InvalidAmino(Amino::X))
237 );
238 }
239
240 #[test]
241 fn mitochondrial_coding_table() {
242 struct Mitochondria;
243
244 impl TranslationTable<Dna, Amino> for Mitochondria {
245 fn to_amino(&self, codon: &SeqSlice<Dna>) -> Amino {
246 if codon == dna!("AGA") || codon == dna!("AGG") {
247 Amino::X
248 } else if codon == dna!("ATA") {
249 Amino::M
250 } else if codon == dna!("TGA") {
251 Amino::W
252 } else {
253 Amino::unsafe_from_bits(Into::<u8>::into(codon))
254 }
255 }
256
257 fn to_codon(&self, _amino: Amino) -> Result<Seq<Dna>, TranslationError> {
258 unimplemented!()
259 }
260 }
261
262 let seq: Seq<Dna> =
263 dna!("AATTTGTGGGTTCGTCTGCGGCTCCGCCCTTAGTACTATGAGGACGATCAGCACCATAAGAACAAA").into();
264 let aminos: Seq<Amino> = seq
265 .windows(3)
266 .map(|codon| Mitochondria.to_amino(codon))
267 .collect::<Seq<Amino>>();
268 assert_eq!(seq.len() - 2, aminos.len());
269
270 for (x, y) in aminos.into_iter().zip(
271 &Seq::<Amino>::try_from(
272 "NIFLCVWGGVFSRVSLCARGALSPRAPPLL*SVYTLYMWE*GDTRDISQSAHTPHM*K*ENTQK",
273 )
274 .unwrap(),
275 ) {
276 assert_eq!(x, y);
277 }
278 }
279}