use crate::atomic::AtomicF64;
use crate::math::{log_add, LOG_0};
use std::sync::atomic::{AtomicU64, Ordering};
#[derive(Debug)]
pub struct Transcript {
pub id: u32,
pub name: String,
pub ref_length: u32,
pub complete_length: u32,
pub is_decoy: bool,
log_eff_length: AtomicF64,
unique_count: AtomicU64,
total_count: AtomicU64,
mass: AtomicF64,
}
impl Transcript {
pub fn new(id: u32, name: impl Into<String>, complete_length: u32) -> Self {
Self {
id,
name: name.into(),
ref_length: complete_length,
complete_length,
is_decoy: false,
log_eff_length: AtomicF64::new(LOG_0),
unique_count: AtomicU64::new(0),
total_count: AtomicU64::new(0),
mass: AtomicF64::new(LOG_0),
}
}
pub fn with_ref_length(mut self, ref_length: u32) -> Self {
self.ref_length = ref_length;
self
}
pub fn as_decoy(mut self) -> Self {
self.is_decoy = true;
self
}
pub fn unique_count(&self) -> u64 {
self.unique_count.load(Ordering::Relaxed)
}
pub fn total_count(&self) -> u64 {
self.total_count.load(Ordering::Relaxed)
}
pub fn add_unique(&self, n: u64) {
self.unique_count.fetch_add(n, Ordering::Relaxed);
}
pub fn add_total(&self, n: u64) {
self.total_count.fetch_add(n, Ordering::Relaxed);
}
pub fn mass(&self) -> f64 {
self.mass.load()
}
pub fn set_mass(&self, log_mass: f64) {
self.mass.store(log_mass);
}
pub fn add_mass(&self, log_inc: f64) {
self.mass.log_add_assign(log_inc);
}
pub fn log_effective_length(&self) -> f64 {
self.log_eff_length.load()
}
pub fn set_log_effective_length(&self, v: f64) {
self.log_eff_length.store(v);
}
pub fn effective_length(&self) -> f64 {
self.log_eff_length.load().exp()
}
pub fn compute_log_effective_length(&self, log_pmf: &[f64], min_val: usize) -> f64 {
let ref_len = self.ref_length as usize;
let max_val = min_val + log_pmf.len().saturating_sub(1);
let upper = ref_len.min(max_val);
let mut eff = LOG_0;
if ref_len >= min_val {
let mut l = min_val;
while l <= upper {
let pmf = log_pmf[l - min_val];
let span = ((ref_len - l + 1) as f64).ln();
eff = log_add(eff, pmf + span);
l += 1;
}
}
if !(eff.is_finite()) || eff < 1.0_f64.ln() {
(ref_len.max(1) as f64).ln()
} else {
eff
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::math::LOG_1;
#[test]
fn mass_accumulates_in_log_space() {
let t = Transcript::new(0, "t0", 1000);
assert_eq!(t.mass(), LOG_0);
t.add_mass(LOG_1); t.add_mass(LOG_1); assert!((t.mass() - 2.0_f64.ln()).abs() < 1e-9);
}
#[test]
fn counts_accumulate() {
let t = Transcript::new(1, "t1", 500);
t.add_unique(3);
t.add_total(5);
assert_eq!(t.unique_count(), 3);
assert_eq!(t.total_count(), 5);
}
#[test]
fn eff_length_point_mass_is_intuitive() {
let t = Transcript::new(0, "t", 1000);
let min_val = 100usize;
let log_pmf = vec![LOG_1]; let eff = t.compute_log_effective_length(&log_pmf, min_val).exp();
assert!((eff - 901.0).abs() < 1e-6, "got {eff}");
}
#[test]
fn eff_length_short_transcript_falls_back() {
let t = Transcript::new(0, "t", 50);
let log_pmf = vec![LOG_1];
let eff = t.compute_log_effective_length(&log_pmf, 100).exp();
assert!((eff - 50.0).abs() < 1e-6, "got {eff}");
}
}