///|
/// Detailed CDS translation result with source coordinates.
pub struct CdsTranslationResult {
feature_index : Int
location : String
nucleotide : String
protein : String
expected : String?
matches_expected : Bool
start_codon : String
terminal_codon : String
codon_count : Int
} derive(Debug, Eq)
///|
pub fn translate_feature(
record : Record,
feature_index : Int,
table? : CodonTable = CodonTable::Standard,
) -> CdsTranslationResult? {
match record.features.get(feature_index) {
None => None
Some(feature) if feature.key != "CDS" => None
Some(feature) => {
let nucleotide = record.sequence_for(feature)
let protein = translate_frame(nucleotide, 1, table)
let expected = feature.qualifier("translation")
{
feature_index,
location: feature.location.raw,
nucleotide,
protein,
expected,
matches_expected: match expected {
Some(value) => value == protein
None => true
},
start_codon: if nucleotide.length() >= 3 {
nucleotide.unsafe_substring(start=0, end=3)
} else {
""
},
terminal_codon: if nucleotide.length() >= 3 {
nucleotide.unsafe_substring(
start=nucleotide.length() - 3,
end=nucleotide.length(),
)
} else {
""
},
codon_count: nucleotide.length() / 3,
}
|> Some
}
}
}
///|
pub fn CdsTranslationResult::to_json_string(
self : CdsTranslationResult,
) -> String {
let expected = match self.expected {
Some(value) => json_string(value)
None => "null"
}
"{\"feature_index\":" +
self.feature_index.to_string() +
",\"location\":" +
json_string(self.location) +
",\"nucleotide\":" +
json_string(self.nucleotide) +
",\"protein\":" +
json_string(self.protein) +
",\"expected\":" +
expected +
",\"matches_expected\":" +
self.matches_expected.to_string() +
",\"start_codon\":" +
json_string(self.start_codon) +
",\"terminal_codon\":" +
json_string(self.terminal_codon) +
",\"codon_count\":" +
self.codon_count.to_string() +
"}"
}
///|
pub fn cds_translation_results(record : Record) -> Array[CdsTranslationResult] {
let result : Array[CdsTranslationResult] = []
for index, feature in record.features {
if feature.key == "CDS" {
match translate_feature(record, index) {
Some(translation) => result.push(translation)
None => ()
}
}
}
result
}
///|
pub fn cds_translation_json(record : Record) -> String {
array_json(
cds_translation_results(record).map(fn(item) { item.to_json_string() }),
)
}
///|
pub fn cds_translation_mismatches(
record : Record,
) -> Array[CdsTranslationResult] {
cds_translation_results(record).filter(fn(item) { !item.matches_expected })
}
///|
pub fn cds_translation_matches(record : Record) -> Array[CdsTranslationResult] {
cds_translation_results(record).filter(fn(item) { item.matches_expected })
}
///|
pub fn cds_translation_error_count(record : Record) -> Int {
cds_translation_mismatches(record).length()
}
///|
pub fn cds_translation_ok(record : Record) -> Bool {
cds_translation_error_count(record) == 0
}
///|
pub fn cds_protein_lengths(record : Record) -> Array[Int] {
cds_translation_results(record).map(fn(item) { item.protein.length() })
}
///|
pub fn cds_nucleotide_lengths(record : Record) -> Array[Int] {
cds_translation_results(record).map(fn(item) { item.nucleotide.length() })
}
///|
pub fn cds_start_codons(record : Record) -> Array[String] {
cds_translation_results(record).map(fn(item) { item.start_codon })
}
///|
pub fn cds_terminal_codons(record : Record) -> Array[String] {
cds_translation_results(record).map(fn(item) { item.terminal_codon })
}
///|
pub fn cds_with_start_codon(record : Record, codon : String) -> Array[Feature] {
let wanted = codon.to_upper()
record.features.filter(fn(feature) {
feature.key == "CDS" &&
record.sequence_for(feature).to_upper().has_prefix(wanted)
})
}
///|
pub fn cds_with_stop_codon(record : Record, codon : String) -> Array[Feature] {
let wanted = codon.to_upper()
record.features.filter(fn(feature) {
if feature.key != "CDS" {
false
} else {
let sequence = record.sequence_for(feature).to_upper()
sequence.length() >= 3 && sequence.has_suffix(wanted)
}
})
}
///|
pub fn cds_complete(record : Record, feature : Feature) -> Bool {
let sequence = record.sequence_for(feature).to_upper()
sequence.length() >= 6 &&
sequence.has_prefix("ATG") &&
(
sequence.has_suffix("TAA") ||
sequence.has_suffix("TAG") ||
sequence.has_suffix("TGA")
)
}
///|
pub fn cds_partial(record : Record, feature : Feature) -> Bool {
feature.key == "CDS" && !cds_complete(record, feature)
}
///|
pub fn cds_frame_length(record : Record, feature : Feature) -> Int {
record.sequence_for(feature).length() % 3
}
///|
pub fn cds_in_frame(record : Record, feature : Feature) -> Bool {
cds_frame_length(record, feature) == 0
}
///|
pub fn cds_translation_summary(record : Record) -> String {
let results = cds_translation_results(record)
let out = StringBuilder()
for item in results {
out.write_string("CDS[" + item.feature_index.to_string() + "] ")
out.write_string(item.location + " ")
out.write_string(item.protein + " ")
out.write_string(if item.matches_expected { "ok" } else { "mismatch" })
out.write_char('\n')
}
out.to_string()
}
///|
pub fn codon_frequency_by_amino(
sequence : String,
table : CodonTable,
) -> Array[(String, Int)] {
let result : Array[(String, Int)] = []
for item in codon_usage(sequence, table) {
match result.search_by(fn(pair) { pair.0 == item.amino }) {
Some(index) => {
let current = result[index]
result[index] = (current.0, current.1 + item.count)
}
None => result.push((item.amino, item.count))
}
}
result
}
///|
pub fn synonymous_codon_count(sequence : String, table : CodonTable) -> Int {
codon_usage(sequence, table).fold(init=0, fn(total, item) {
total + (if item.count > 1 { item.count - 1 } else { 0 })
})
}
///|
pub fn stop_codon_count(sequence : String, table : CodonTable) -> Int {
count_base(translate_frame(sequence, 1, table), '*')
}
///|
pub fn ambiguous_codon_count(sequence : String, table : CodonTable) -> Int {
count_base(translate_frame(sequence, 1, table), 'X')
}
///|
pub fn translation_has_internal_stop(protein : String) -> Bool {
if protein.length() <= 1 {
false
} else {
protein.unsafe_substring(start=0, end=protein.length() - 1).contains("*")
}
}
///|
pub fn translation_is_terminated(protein : String) -> Bool {
protein.has_suffix("*")
}
///|
pub fn translation_without_stop(protein : String) -> String {
if protein.has_suffix("*") {
protein.unsafe_substring(start=0, end=protein.length() - 1)
} else {
protein
}
}
///|
pub fn protein_molecular_weight_approximation(protein : String) -> Double {
protein.length().to_double() * 110.0
}
///|
pub fn protein_has_unknowns(protein : String) -> Bool {
protein.contains("X")
}
///|
pub fn protein_unknown_count(protein : String) -> Int {
count_base(protein, 'X')
}
///|
pub fn protein_stop_count(protein : String) -> Int {
count_base(protein, '*')
}
///|
pub fn protein_is_empty(protein : String) -> Bool {
protein == ""
}
///|
pub fn protein_length(protein : String) -> Int {
protein.length()
}