use rayon::prelude::*;
use rustc_hash::{FxHashMap, FxHashSet};
use crate::prelude::*;
pub const CXDS_NTOP: usize = 500;
type CellGeneSet = Vec<u16>;
fn norm_cdf(x: f64) -> f64 {
if x < -8.0 {
return 0.0;
}
if x > 8.0 {
return 1.0;
}
let t = 1.0 / (1.0 + 0.231_641_9 * x.abs());
let poly = t
* (0.319_381_530
+ t * (-0.356_563_782
+ t * (1.781_477_937 + t * (-1.821_255_978 + t * 1.330_274_429))));
let pdf = (-x * x / 2.0).exp() * std::f64::consts::FRAC_2_SQRT_PI * 0.5;
let tail = pdf * poly;
if x >= 0.0 { 1.0 - tail } else { tail }
}
fn neg_log_binom_upper(k: u32, n: u32, p: f64) -> f32 {
let np = n as f64 * p;
let var = np * (1.0 - p);
if var < 1e-10 {
return 0.0;
}
let z = (k as f64 - 0.5 - np) / var.sqrt();
let tail_p = 1.0 - norm_cdf(z);
if tail_p < 1e-300 {
return 700.0; }
(-tail_p.ln()) as f32
}
#[allow(dead_code)]
pub struct CxdsModel {
pair_scores: Vec<f32>,
selected_genes: Vec<usize>,
ntop: usize,
gene_map: FxHashMap<usize, u16>,
}
impl CxdsModel {
pub fn fit<S: SingleCellReading>(
reader: &S,
cells_to_keep: &[usize],
hvg_genes: &[usize],
ntop: usize,
) -> Result<(Self, Vec<CellGeneSet>), BixverseErrors> {
let n_cells = cells_to_keep.len();
let hvg_set: FxHashSet<usize> = hvg_genes.iter().copied().collect();
let mut gene_counts: FxHashMap<usize, u32> = FxHashMap::default();
let mut cell_hvg_indices: Vec<Vec<usize>> = Vec::with_capacity(n_cells);
for &cell_idx in cells_to_keep {
let chunk = reader.read_cell(cell_idx)?;
let mut expressed_hvgs = Vec::new();
for (i, &gene_idx) in chunk.indices.iter().enumerate() {
let gi = gene_idx as usize;
if hvg_set.contains(&gi) && chunk.data_raw.get(i) > 0 {
*gene_counts.entry(gi).or_insert(0) += 1;
expressed_hvgs.push(gi);
}
}
cell_hvg_indices.push(expressed_hvgs);
}
let nf = n_cells as f64;
let mut gene_vars: Vec<(usize, f64)> = gene_counts
.iter()
.map(|(&gene, &count)| {
let p = count as f64 / nf;
(gene, p * (1.0 - p))
})
.collect();
gene_vars.sort_unstable_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
let actual_ntop = ntop.min(gene_vars.len());
let selected_genes: Vec<usize> = gene_vars[..actual_ntop].iter().map(|&(g, _)| g).collect();
let gene_map: FxHashMap<usize, u16> = selected_genes
.iter()
.enumerate()
.map(|(pos, &g)| (g, pos as u16))
.collect();
let marginals: Vec<f64> = selected_genes
.iter()
.map(|g| *gene_counts.get(g).unwrap_or(&0) as f64 / nf)
.collect();
let obs_gene_sets: Vec<CellGeneSet> = cell_hvg_indices
.par_iter()
.map(|hvgs| {
let mut expressed: CellGeneSet = hvgs
.iter()
.filter_map(|&gi| gene_map.get(&gi).copied())
.collect();
expressed.sort_unstable();
expressed
})
.collect();
drop(cell_hvg_indices);
let n_coexpress = obs_gene_sets
.par_iter()
.fold(
|| vec![0u32; actual_ntop * actual_ntop],
|mut local_coex, expressed| {
let ne = expressed.len();
for a in 0..ne {
let i = expressed[a] as usize;
let row = i * actual_ntop;
for b in (a + 1)..ne {
let j = expressed[b] as usize;
local_coex[row + j] += 1;
}
}
local_coex
},
)
.reduce(
|| vec![0u32; actual_ntop * actual_ntop],
|mut a, b| {
for i in 0..a.len() {
a[i] += b[i];
}
a
},
);
let n_express: Vec<u32> = selected_genes
.iter()
.map(|g| *gene_counts.get(g).unwrap_or(&0))
.collect();
let n = n_cells as u32;
let mut pair_scores = vec![0.0f32; actual_ntop * actual_ntop];
for i in 0..actual_ntop {
for j in (i + 1)..actual_ntop {
let pi = marginals[i];
let pj = marginals[j];
let p_excl = pi * (1.0 - pj) + (1.0 - pi) * pj;
let coex = n_coexpress[i * actual_ntop + j];
let k = n_express[i] + n_express[j] - 2 * coex;
let score = neg_log_binom_upper(k, n, p_excl);
pair_scores[i * actual_ntop + j] = score;
pair_scores[j * actual_ntop + i] = score;
}
}
let model = CxdsModel {
pair_scores,
selected_genes,
ntop: actual_ntop,
gene_map,
};
Ok((model, obs_gene_sets))
}
pub fn score(&self, gene_sets: &[CellGeneSet]) -> Vec<f32> {
gene_sets
.par_iter()
.map(|expressed| {
let mut total = 0.0f32;
let ne = expressed.len();
for a in 0..ne {
let i = expressed[a] as usize;
let row = i * self.ntop;
for b in (a + 1)..ne {
let j = expressed[b] as usize;
total += self.pair_scores[row + j];
}
}
total
})
.collect()
}
pub fn score_simulated_from_chunks(
&self,
sim_chunks: &[CsrCellChunk],
selected_genes_to_cxds_pos: &FxHashMap<usize, u16>,
bin_thresh: u32,
) -> Vec<f32> {
sim_chunks
.par_iter()
.map(|chunk| {
let mut expressed: Vec<u16> = Vec::new();
for i in 0..chunk.indices.len() {
let raw = chunk.data_raw.get(i);
if raw < bin_thresh {
continue;
}
let gene_pos = chunk.indices[i] as usize;
if let Some(&cxds_pos) = selected_genes_to_cxds_pos.get(&gene_pos) {
expressed.push(cxds_pos);
}
}
expressed.sort_unstable();
let mut total = 0.0f32;
let ne = expressed.len();
for a in 0..ne {
let i = expressed[a] as usize;
let row = i * self.ntop;
for b in (a + 1)..ne {
let j = expressed[b] as usize;
total += self.pair_scores[row + j];
}
}
total
})
.collect()
}
pub fn hvg_position_map(&self, selected_genes: &[usize]) -> FxHashMap<usize, u16> {
selected_genes
.iter()
.enumerate()
.filter_map(|(hvg_pos, orig_gene)| {
self.gene_map
.get(orig_gene)
.map(|&cxds_pos| (hvg_pos, cxds_pos))
})
.collect()
}
}