1use 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
16static 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 Amino::unsafe_from_bits(Into::<u8>::into(codon))
87 }
88
89 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}