pub mod run;
use crate::sim::copula::sampler::first_two_moments;
use nalgebra::{DMatrix, DVector};
use rand_distr::{Beta, Distribution, Normal, Uniform};
#[derive(Debug, Clone)]
pub struct LineageTree {
pub parent: Vec<Option<usize>>,
pub depth: Vec<usize>,
pub leaves: Vec<usize>,
on_lineage: Vec<Vec<bool>>,
}
impl LineageTree {
pub fn star(k: usize) -> anyhow::Result<Self> {
anyhow::ensure!(k >= 1, "a tree needs at least one lineage");
let parents = if k == 1 { Vec::new() } else { vec![0; k] };
Self::from_parents(&parents)
}
pub fn from_parents(parents: &[usize]) -> anyhow::Result<Self> {
let n = parents.len() + 1;
let mut parent = vec![None];
let mut depth = vec![0];
for (i, &p) in parents.iter().enumerate() {
anyhow::ensure!(
p <= i,
"branch {}'s parent {p} must be an earlier branch (0..={i})",
i + 1
);
parent.push(Some(p));
depth.push(depth[p] + 1);
}
let mut has_child = vec![false; n];
for &p in parents {
has_child[p] = true;
}
let leaves: Vec<usize> = (0..n).filter(|&b| !has_child[b]).collect();
let mut on_lineage = vec![vec![false; leaves.len()]; n];
for (l, &leaf) in leaves.iter().enumerate() {
let mut b = Some(leaf);
while let Some(x) = b {
on_lineage[x][l] = true;
b = parent[x];
}
}
Ok(Self {
parent,
depth,
leaves,
on_lineage,
})
}
pub fn n_branches(&self) -> usize {
self.parent.len()
}
pub fn n_lineages(&self) -> usize {
self.leaves.len()
}
pub fn on_lineage(&self, branch: usize, lineage: usize) -> bool {
self.on_lineage[branch][lineage]
}
}
#[derive(Debug, Clone)]
pub struct CellPlacement {
pub branch: Vec<usize>,
pub time: Vec<f32>,
}
impl CellPlacement {
pub fn sample(
tree: &LineageTree,
n: usize,
weights: &[f64],
shape: (f64, f64),
rng: &mut impl rand::Rng,
) -> anyhow::Result<Self> {
let nb = tree.n_branches();
let weights: Vec<f64> = if weights.is_empty() {
vec![1.0; nb]
} else {
weights.to_vec()
};
anyhow::ensure!(
weights.len() == nb,
"{} branch weights for {nb} branches",
weights.len()
);
let total: f64 = weights.iter().sum();
anyhow::ensure!(
weights.iter().all(|w| w.is_finite() && *w >= 0.0) && total > 0.0,
"branch weights must be non-negative with a positive sum"
);
let last = weights.iter().rposition(|&w| w > 0.0).unwrap_or(0);
let cumulative: Vec<f64> = weights
.iter()
.scan(0.0, |s, w| {
*s += w / total;
Some(*s)
})
.collect();
let beta = Beta::new(shape.0, shape.1)
.map_err(|e| anyhow::anyhow!("pseudotime shape {shape:?}: {e}"))?;
let unif = Uniform::new(0.0_f64, 1.0)?;
let mut branch = Vec::with_capacity(n);
let mut time = Vec::with_capacity(n);
for _ in 0..n {
let u = unif.sample(rng);
let b = cumulative.partition_point(|&c| c <= u).min(last);
branch.push(b);
time.push(beta.sample(rng) as f32);
}
Ok(Self { branch, time })
}
pub fn n_cells(&self) -> usize {
self.branch.len()
}
pub fn tree_time(&self, tree: &LineageTree, j: usize) -> f32 {
tree.depth[self.branch[j]] as f32 + self.time[j]
}
}
const MIN_CURVE_VARIANCE: f64 = 1e-10;
fn basis(k: usize, t: f32) -> f32 {
let w = (k as f32 + 0.5) * std::f32::consts::PI;
(w * t).sin() / w
}
#[derive(Debug, Clone)]
pub struct GeneCurves {
pub n_basis: usize,
pub dynamic: Vec<usize>,
pub coef: DMatrix<f32>,
pub start: DMatrix<f32>,
}
impl GeneCurves {
pub fn sample(
tree: &LineageTree,
n_genes: usize,
candidates: Option<&[usize]>,
share: f32,
n_basis: usize,
rng: &mut impl rand::Rng,
) -> anyhow::Result<Self> {
anyhow::ensure!(n_basis >= 1, "a curve needs at least one basis function");
let pool: std::borrow::Cow<[usize]> = match candidates {
Some(c) => c.into(),
None => (0..n_genes).collect::<Vec<_>>().into(),
};
anyhow::ensure!(
pool.iter().all(|&g| g < n_genes),
"a dynamic-gene candidate is past the {n_genes} genes"
);
let n_dynamic = ((share.clamp(0.0, 1.0) as f64) * pool.len() as f64).round() as usize;
let mut dynamic: Vec<usize> = rand::seq::index::sample(rng, pool.len(), n_dynamic)
.into_iter()
.map(|i| pool[i])
.collect();
dynamic.sort_unstable();
let nb = tree.n_branches();
let z: f32 = (0..n_basis).map(|k| basis(k, 1.0).powi(2)).sum();
let sd = z.sqrt().recip();
let normal = Normal::new(0.0_f32, 1.0)?;
let mut coef = DMatrix::<f32>::zeros(n_genes, nb * n_basis);
for &g in &dynamic {
for c in 0..nb * n_basis {
coef[(g, c)] = sd * normal.sample(rng);
}
}
let mut curves = Self {
n_basis,
dynamic,
coef,
start: DMatrix::zeros(n_genes, nb),
};
for b in 1..nb {
let p = tree.parent[b].expect("a non-root branch has a parent");
for i in 0..curves.dynamic.len() {
let g = curves.dynamic[i];
curves.start[(g, b)] = curves.start[(g, p)] + curves.increment(g, p, 1.0);
}
}
Ok(curves)
}
pub fn standardize(&mut self, cells: &CellPlacement) {
let (m1, m2) = self.moments_over(cells, |f| f as f64);
let mut kept = Vec::with_capacity(self.dynamic.len());
for (i, &g) in self.dynamic.iter().enumerate() {
let var = m2[i] - m1[i] * m1[i];
if var <= MIN_CURVE_VARIANCE {
self.coef.row_mut(g).fill(0.0);
self.start.row_mut(g).fill(0.0);
continue;
}
let inv = (1.0 / var.sqrt()) as f32;
self.coef.row_mut(g).scale_mut(inv);
self.start.row_mut(g).add_scalar_mut(-m1[i] as f32);
self.start.row_mut(g).scale_mut(inv);
kept.push(g);
}
self.dynamic = kept;
}
fn basis_at(&self, t: f32) -> Vec<f32> {
(0..self.n_basis).map(|k| basis(k, t)).collect()
}
fn value_at(&self, g: usize, b: usize, phi: &[f32]) -> f32 {
let c = b * self.n_basis;
self.start[(g, b)]
+ phi
.iter()
.enumerate()
.map(|(k, p)| self.coef[(g, c + k)] * p)
.sum::<f32>()
}
pub fn value(&self, g: usize, b: usize, t: f32) -> f32 {
self.value_at(g, b, &self.basis_at(t))
}
fn increment(&self, g: usize, b: usize, t: f32) -> f32 {
self.value(g, b, t) - self.start[(g, b)]
}
pub fn n_genes(&self) -> usize {
self.coef.nrows()
}
pub fn at(&self, b: usize, t: f32) -> DVector<f32> {
let mut f = self.start.column(b).into_owned();
for (k, p) in self.basis_at(t).into_iter().enumerate() {
f.axpy(p, &self.coef.column(b * self.n_basis + k), 1.0);
}
f
}
fn moments_over(
&self,
cells: &CellPlacement,
h: impl Fn(f32) -> f64 + Sync,
) -> (Vec<f64>, Vec<f64>) {
first_two_moments(self.dynamic.len(), cells.n_cells(), |j, out| {
let b = cells.branch[j];
let phi = self.basis_at(cells.time[j]);
for (o, &g) in out.iter_mut().zip(&self.dynamic) {
*o = h(self.value_at(g, b, &phi));
}
})
}
pub fn log_mean_exp(&self, cells: &CellPlacement, amp: f32) -> Vec<f32> {
let (m1, _) = self.moments_over(cells, |f| ((amp * f) as f64).exp());
let mut centre = vec![0.0_f32; self.n_genes()];
for (&g, m) in self.dynamic.iter().zip(m1) {
centre[g] = m.ln() as f32;
}
centre
}
}
#[cfg(test)]
mod tests;