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 self.inverse_table
191 .get(&amino)
192 .ok_or(TranslationError::InvalidAmino(amino))?
193 .clone()
194 .ok_or(TranslationError::AmbiguousCodon(amino))
195 }
196}
197
198#[cfg(test)]
199mod tests {
200 use crate::prelude::*;
201 use crate::translation::{
202 CodonTable, PartialTranslationTable, TranslationError, TranslationTable,
203 };
204
205 #[test]
206 fn custom_codon_table() {
207 let mito: [(Seq<Dna>, Amino); 6] = [
208 (dna!("AAA").into(), Amino::A),
209 (dna!("ATG").into(), Amino::A),
210 (dna!("CCC").into(), Amino::C),
211 (dna!("GGG").into(), Amino::E),
212 (dna!("TTT").into(), Amino::D),
213 (dna!("TTA").into(), Amino::F),
214 ];
215
216 let table = CodonTable::from_map(mito);
217
218 let seq: Seq<Dna> = dna!("AAACCCGGGTTTTTATTAATG").into();
219 let mut amino_seq: Seq<Amino> = Seq::new();
220 for codon in seq.chunks(3) {
221 amino_seq.push(table.try_to_amino(codon).unwrap());
222 }
223 assert_eq!(amino_seq, Seq::<Amino>::try_from("ACEDFFA").unwrap());
224
225 assert_ne!(table.try_to_codon(Amino::E), Ok(dna!("CCC").into()));
226 assert_eq!(table.try_to_codon(Amino::C), Ok(dna!("CCC").into()));
227 assert_eq!(
228 table.try_to_codon(Amino::A),
229 Err(TranslationError::AmbiguousCodon(Amino::A))
230 );
231 assert_eq!(
232 table.try_to_codon(Amino::X),
233 Err(TranslationError::InvalidAmino(Amino::X))
234 );
235 }
236
237 #[test]
238 fn mitochondrial_coding_table() {
239 struct Mitochondria;
240
241 impl TranslationTable<Dna, Amino> for Mitochondria {
242 fn to_amino(&self, codon: &SeqSlice<Dna>) -> Amino {
243 if codon == dna!("AGA") || codon == dna!("AGG") {
244 Amino::X
245 } else if codon == dna!("ATA") {
246 Amino::M
247 } else if codon == dna!("TGA") {
248 Amino::W
249 } else {
250 Amino::unsafe_from_bits(Into::<u8>::into(codon))
251 }
252 }
253
254 fn to_codon(&self, _amino: Amino) -> Result<Seq<Dna>, TranslationError> {
255 unimplemented!()
256 }
257 }
258
259 let seq: Seq<Dna> =
260 dna!("AATTTGTGGGTTCGTCTGCGGCTCCGCCCTTAGTACTATGAGGACGATCAGCACCATAAGAACAAA").into();
261 let aminos: Seq<Amino> = seq
262 .windows(3)
263 .map(|codon| Mitochondria.to_amino(codon))
264 .collect::<Seq<Amino>>();
265 assert_eq!(seq.len() - 2, aminos.len());
266
267 for (x, y) in aminos.into_iter().zip(
268 &Seq::<Amino>::try_from(
269 "NIFLCVWGGVFSRVSLCARGALSPRAPPLL*SVYTLYMWE*GDTRDISQSAHTPHM*K*ENTQK",
270 )
271 .unwrap(),
272 ) {
273 assert_eq!(x, y);
274 }
275 }
276}