///|
/// Aggregate coding-sequence statistics for reporting and QC.
pub struct CodingSequenceReport {
nucleotide_length : Int
complete_codons : Int
trailing_bases : Int
start_codons : Int
stop_codons : Int
ambiguous_codons : Int
gc_first : Double
gc_second : Double
gc_third : Double
usage : Array[CodonUsage]
} derive(Debug, Eq)
///|
/// Calculate codon-position GC and translation diagnostics.
pub fn coding_sequence_report(
sequence : String,
table : CodonTable,
) -> CodingSequenceReport {
let dna = normalize_sequence(sequence).replace_all(old="U", new="T")
let codons = dna.length() / 3
let usage = codon_usage(dna, table)
let mut starts = 0
let mut stops = 0
let mut ambiguous = 0
let gc = [0, 0, 0]
let seen = [0, 0, 0]
for index in 0.. Double {
if denominator == 0 {
0.0
} else {
numerator.to_double() * 100.0 / denominator.to_double()
}
}
///|
/// Return codons sorted by descending usage, preserving first-seen ties.
pub fn codon_usage_ranked(
sequence : String,
table : CodonTable,
) -> Array[CodonUsage] {
let result = codon_usage(sequence, table)
result.sort_by(fn(left, right) { right.count - left.count })
result
}
///|
/// Calculate the proportion of each codon among complete codons.
pub fn codon_usage_frequencies(
sequence : String,
table : CodonTable,
) -> Array[(String, Double)] {
let usage = codon_usage(sequence, table)
let total = codon_usage_total(usage)
if total == 0 {
return []
}
usage.map(fn(item) {
(item.codon, item.count.to_double() * 100.0 / total.to_double())
})
}
///|
/// Calculate the effective number of distinct codons in a sequence.
pub fn distinct_codon_count(sequence : String, table : CodonTable) -> Int {
codon_usage(sequence, table).length()
}
///|
/// Emit a stable tabular codon report for downstream scripts.
pub fn CodingSequenceReport::to_tsv(self : CodingSequenceReport) -> String {
let out = StringBuilder()
out.write_string("codon\tamino\tcount\n")
for item in self.usage {
out.write_string(item.codon)
out.write_char('\t')
out.write_string(item.amino)
out.write_char('\t')
out.write_string(item.count.to_string())
out.write_char('\n')
}
out.to_string()
}
///|
pub fn CodingSequenceReport::to_json_string(
self : CodingSequenceReport,
) -> String {
let usage_json = self.usage.map(fn(item) { item.to_json_string() }).join(",")
"{\"nucleotide_length\":" +
self.nucleotide_length.to_string() +
",\"complete_codons\":" +
self.complete_codons.to_string() +
",\"trailing_bases\":" +
self.trailing_bases.to_string() +
",\"start_codons\":" +
self.start_codons.to_string() +
",\"stop_codons\":" +
self.stop_codons.to_string() +
",\"ambiguous_codons\":" +
self.ambiguous_codons.to_string() +
",\"gc_first\":" +
self.gc_first.to_string() +
",\"gc_second\":" +
self.gc_second.to_string() +
",\"gc_third\":" +
self.gc_third.to_string() +
",\"usage\":[" +
usage_json +
"]}"
}
///|
/// Calculate codon usage for every CDS feature in one record.
pub fn record_coding_reports(
record : Record,
table : CodonTable,
) -> Array[CodingSequenceReport] {
record.features
.filter(fn(feature) { feature.key == "CDS" })
.map(fn(feature) {
coding_sequence_report(record.sequence_for(feature), table)
})
}
///|
/// Sum complete codons across all CDS features.
pub fn record_complete_codon_count(record : Record) -> Int {
record_coding_reports(record, standard_codon_table()).fold(init=0, fn(
total,
item,
) {
total + item.complete_codons
})
}
///|
/// Return the fraction of a coding sequence represented by a stop codon.
pub fn stop_codon_fraction(sequence : String, table : CodonTable) -> Double {
let report = coding_sequence_report(sequence, table)
if report.complete_codons == 0 {
0.0
} else {
report.stop_codons.to_double() / report.complete_codons.to_double()
}
}