pub mod codon;
pub use codon::{Base, Codon, CodonTable};
use crate::hgvs::location::AminoAcid;
#[derive(Debug, Clone, PartialEq)]
pub struct CodonChange {
pub ref_codon: Codon,
pub alt_codon: Codon,
pub changed_positions: Vec<u8>,
pub nucleotide_changes: Vec<(u8, Base, Base)>,
}
impl std::fmt::Display for CodonChange {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} -> {}", self.ref_codon, self.alt_codon)
}
}
#[derive(Debug, Clone)]
pub struct BacktranslationResult {
pub protein_variant: String,
pub dna_variants: Vec<CodonChange>,
}
#[derive(Debug, Clone)]
pub struct Backtranslator {
codon_table: CodonTable,
}
impl Backtranslator {
pub fn new(codon_table: CodonTable) -> Self {
Self { codon_table }
}
pub fn standard() -> Self {
Self::new(CodonTable::standard())
}
pub fn backtranslate_substitution(
&self,
ref_aa: &AminoAcid,
alt_aa: &AminoAcid,
) -> Vec<CodonChange> {
let ref_codons = self.codon_table.codons_for(ref_aa);
let alt_codons = self.codon_table.codons_for(alt_aa);
let mut results = Vec::new();
for ref_codon in ref_codons {
for alt_codon in alt_codons {
if let Some(change) = self.single_nucleotide_change(ref_codon, alt_codon) {
results.push(change);
}
}
}
results
}
pub fn backtranslate_with_context(
&self,
ref_codon: &Codon,
alt_aa: &AminoAcid,
) -> Vec<CodonChange> {
let alt_codons = self.codon_table.codons_for(alt_aa);
let mut results: Vec<CodonChange> = alt_codons
.iter()
.map(|alt| self.codon_difference(ref_codon, alt))
.collect();
results.sort_by_key(|c| c.nucleotide_changes.len());
results
}
pub fn backtranslate_to_stop(&self, ref_aa: &AminoAcid) -> Vec<CodonChange> {
let ref_codons = self.codon_table.codons_for(ref_aa);
let stop_codons = self.codon_table.stop_codons();
let mut results = Vec::new();
for ref_codon in ref_codons {
for stop_codon in stop_codons {
if let Some(change) = self.single_nucleotide_change(ref_codon, stop_codon) {
results.push(change);
}
}
}
results
}
pub fn backtranslate_stop_loss(&self, alt_aa: &AminoAcid) -> Vec<CodonChange> {
let stop_codons = self.codon_table.stop_codons();
let alt_codons = self.codon_table.codons_for(alt_aa);
let mut results = Vec::new();
for stop_codon in stop_codons {
for alt_codon in alt_codons {
if let Some(change) = self.single_nucleotide_change(stop_codon, alt_codon) {
results.push(change);
}
}
}
results
}
pub fn codon_table(&self) -> &CodonTable {
&self.codon_table
}
fn single_nucleotide_change(
&self,
ref_codon: &Codon,
alt_codon: &Codon,
) -> Option<CodonChange> {
let diff = self.codon_difference(ref_codon, alt_codon);
if diff.nucleotide_changes.len() == 1 {
Some(diff)
} else {
None
}
}
fn codon_difference(&self, ref_codon: &Codon, alt_codon: &Codon) -> CodonChange {
let ref_bases = ref_codon.bases();
let alt_bases = alt_codon.bases();
let mut changed_positions = Vec::new();
let mut nucleotide_changes = Vec::new();
for i in 0..3 {
if ref_bases[i] != alt_bases[i] {
changed_positions.push((i + 1) as u8);
nucleotide_changes.push(((i + 1) as u8, ref_bases[i], alt_bases[i]));
}
}
CodonChange {
ref_codon: ref_codon.clone(),
alt_codon: alt_codon.clone(),
changed_positions,
nucleotide_changes,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_backtranslate_leu_to_phe() {
let bt = Backtranslator::standard();
let changes = bt.backtranslate_substitution(&AminoAcid::Leu, &AminoAcid::Phe);
assert!(!changes.is_empty());
for change in &changes {
assert_eq!(change.nucleotide_changes.len(), 1);
}
}
#[test]
fn test_backtranslate_with_context() {
let bt = Backtranslator::standard();
let ref_codon = Codon::parse("CTT").unwrap();
let changes = bt.backtranslate_with_context(&ref_codon, &AminoAcid::Phe);
assert!(!changes.is_empty());
assert!(
changes[0].nucleotide_changes.len() <= changes.last().unwrap().nucleotide_changes.len()
);
}
#[test]
fn test_backtranslate_to_stop() {
let bt = Backtranslator::standard();
let changes = bt.backtranslate_to_stop(&AminoAcid::Gln);
assert!(changes.len() >= 2);
}
#[test]
fn test_backtranslate_stop_loss() {
let bt = Backtranslator::standard();
let changes = bt.backtranslate_stop_loss(&AminoAcid::Gln);
assert!(changes.len() >= 2);
}
#[test]
fn test_val_to_glu_braf() {
let bt = Backtranslator::standard();
let changes = bt.backtranslate_substitution(&AminoAcid::Val, &AminoAcid::Glu);
assert!(!changes.is_empty());
let gtg_to_gag = changes
.iter()
.find(|c| c.ref_codon.to_string() == "GTG" && c.alt_codon.to_string() == "GAG");
assert!(gtg_to_gag.is_some());
}
}