Push zunrplorkwkt #70

Merged
coissac merged 93 commits from push-zunrplorkwkt into main 2026-08-28 23:15:38 +00:00
6 changed files with 81 additions and 141 deletions
Showing only changes of commit 9abee87af3 - Show all commits
+2 -20
View File
@@ -1,6 +1,6 @@
use std::io;
use crate::colgroup::{ColGroup, MatrixGroupOps};
use crate::colgroup::{chunked_presence_count, ColGroup, MatrixGroupOps};
use crate::tempbitvec::{TempBitVec, TempBitVecBuilder};
use crate::tempintvec::{TempCompactIntVec, TempCompactIntVecBuilder};
@@ -11,25 +11,7 @@ use super::persistent::PersistentBitMatrix;
impl MatrixGroupOps for PersistentBitMatrix {
fn partial_group_presence_count(&self, g: &ColGroup, _threshold: u32) -> io::Result<TempCompactIntVec> {
// Bit matrices store 0/1 — threshold is structurally always 1.
let n = self.n();
if g.indices.len() < 255 {
let mut builder = TempCompactIntVecBuilder::new(n)?;
for &c in &g.indices {
builder.inc_present_fast(self.col_view(c));
}
builder.freeze()
} else {
let mut result = TempCompactIntVecBuilder::new(n)?;
for chunk in g.indices.chunks(254) {
let mut chunk_b = TempCompactIntVecBuilder::new(n)?;
for &c in chunk {
chunk_b.inc_present_fast(self.col_view(c));
}
let frozen = chunk_b.freeze()?;
result.add(frozen.view());
}
result.freeze()
}
chunked_presence_count(self.n(), &g.indices, |b, c| b.inc_present_fast(self.col_view(c)))
}
fn partial_group_sum(&self, g: &ColGroup) -> io::Result<TempCompactIntVec> {
@@ -111,11 +111,7 @@ impl PersistentBitMatrix {
match self {
Self::Columnar(m) => m.col(c).get(slot) as u32,
Self::Packed(m) => m.col_slice(c).get(slot) as u32,
Self::Sparse(m) => {
let mut buf = vec![0u32; m.n_cols()];
m.fill_row(slot, &mut buf);
buf[c]
}
Self::Sparse(m) => m.get(c, slot),
Self::Implicit { .. } => 1,
}
}
+12
View File
@@ -151,6 +151,18 @@ impl PersistentSparseBitMatrix {
(start, end)
}
/// Column-major point lookup: value at column `c`, slot `slot`, as 0/1.
///
/// Avoids `fill_row`'s `n_cols`-wide buffer: walks the row's own decode
/// — 1 entry for a singleton row, k̄ entries for a multi-genome row —
/// via [`for_each_genome_in_row`](Self::for_each_genome_in_row), instead
/// of allocating and scanning a full-width buffer for a single cell.
pub fn get(&self, c: usize, slot: usize) -> u32 {
let mut found = false;
self.for_each_genome_in_row(slot, |g| found |= g == c);
found as u32
}
pub fn row(&self, slot: usize) -> Box<[bool]> {
let mut out = vec![false; self.n_cols];
self.fill_row_bool(slot, &mut out);
+28 -1
View File
@@ -1,7 +1,7 @@
use std::io;
use crate::tempbitvec::{TempBitVec, TempBitVecBuilder};
use crate::tempintvec::TempCompactIntVec;
use crate::tempintvec::{TempCompactIntVec, TempCompactIntVecBuilder};
// ── ColGroup ──────────────────────────────────────────────────────────────────
@@ -21,6 +21,33 @@ impl ColGroup {
}
}
/// Shared chunked-accumulation body behind every `partial_group_presence_count`
/// impl: below 255 columns, accumulate directly (`u8` counters never
/// overflow); at or above, split into chunks of 254, accumulate each chunk
/// into its own builder, and merge chunks with `add`. `inc` supplies the
/// one thing that differs per matrix kind — how to bump the counter for
/// column `c` — so the chunking/merging logic itself is written once.
pub(crate) fn chunked_presence_count(
n: usize,
indices: &[usize],
mut inc: impl FnMut(&mut TempCompactIntVecBuilder, usize),
) -> io::Result<TempCompactIntVec> {
if indices.len() < 255 {
let mut builder = TempCompactIntVecBuilder::new(n)?;
for &c in indices { inc(&mut builder, c); }
builder.freeze()
} else {
let mut result = TempCompactIntVecBuilder::new(n)?;
for chunk in indices.chunks(254) {
let mut chunk_b = TempCompactIntVecBuilder::new(n)?;
for &c in chunk { inc(&mut chunk_b, c); }
let frozen = chunk_b.freeze()?;
result.add(frozen.view());
}
result.freeze()
}
}
// ── MatrixGroupOps ────────────────────────────────────────────────────────────
/// Per-matrix group aggregations.
+19 -55
View File
@@ -8,7 +8,7 @@ use rayon::prelude::*;
use crate::bitmatrix::{pairwise_matrix, pairwise2_matrix};
use crate::builder::PersistentCompactIntVecBuilder;
use crate::colgroup::{ColGroup, MatrixGroupOps};
use crate::colgroup::{chunked_presence_count, ColGroup, MatrixGroupOps};
use crate::format::{HEADER_SIZE, OVERFLOW_ENTRY_SIZE};
use crate::meta::MatrixMeta;
use crate::reader::PersistentCompactIntVec;
@@ -186,53 +186,33 @@ impl PackedCompactIntMatrix {
)
}
fn pair_partial_bray(&self, i: usize, j: usize) -> u64 {
self.col_view(i).iter().zip(self.col_view(j).iter()).map(|(a, b)| a.min(b) as u64).sum()
}
fn pair_partial_euclidean(&self, i: usize, j: usize) -> f64 {
self.col_view(i).iter().zip(self.col_view(j).iter())
.map(|(a, b)| { let d = a as f64 - b as f64; d * d }).sum()
}
fn pair_partial_threshold_jaccard(&self, i: usize, j: usize, t: u32) -> (u64, u64) {
self.col_view(i).iter().zip(self.col_view(j).iter())
.fold((0u64, 0u64), |(inter, uni), (a, b)| {
let ap = a >= t; let bp = b >= t;
(inter + (ap & bp) as u64, uni + (ap | bp) as u64)
})
}
fn pair_partial_relfreq_bray(&self, i: usize, j: usize, si: f64, sj: f64) -> f64 {
if si == 0.0 || sj == 0.0 { return 0.0; }
self.col_view(i).iter().zip(self.col_view(j).iter())
.map(|(a, b)| (a as f64 / si).min(b as f64 / sj)).sum()
}
fn pair_partial_relfreq_euclidean(&self, i: usize, j: usize, si: f64, sj: f64) -> f64 {
if si == 0.0 || sj == 0.0 { return 0.0; }
self.col_view(i).iter().zip(self.col_view(j).iter())
.map(|(a, b)| { let d = a as f64 / si - b as f64 / sj; d * d }).sum()
}
fn pair_partial_hellinger(&self, i: usize, j: usize, si: f64, sj: f64) -> f64 {
if si == 0.0 || sj == 0.0 { return 0.0; }
self.col_view(i).iter().zip(self.col_view(j).iter())
.map(|(a, b)| { let d = (a as f64 / si).sqrt() - (b as f64 / sj).sqrt(); d * d }).sum()
}
// Every pair formula below delegates to `IntSliceView` (`views.rs`) —
// the canonical implementation, also used by `ColumnarCompactIntMatrix`
// via `PersistentCompactIntVec`. No formula is re-derived here.
pub(crate) fn partial_bray_dist_matrix(&self) -> Array2<u64> {
pairwise_matrix(self.n_cols, |i, j| self.pair_partial_bray(i, j))
pairwise_matrix(self.n_cols, |i, j| self.col_view(i).partial_bray_dist(self.col_view(j)))
}
pub(crate) fn partial_euclidean_dist_matrix(&self) -> Array2<f64> {
pairwise_matrix(self.n_cols, |i, j| self.pair_partial_euclidean(i, j))
pairwise_matrix(self.n_cols, |i, j| self.col_view(i).partial_euclidean_dist(self.col_view(j)))
}
pub(crate) fn partial_threshold_jaccard_dist_matrix(&self, t: u32) -> (Array2<u64>, Array2<u64>) {
pairwise2_matrix(self.n_cols, |i, j| self.pair_partial_threshold_jaccard(i, j, t))
pairwise2_matrix(self.n_cols, |i, j| self.col_view(i).partial_threshold_jaccard_dist(self.col_view(j), t))
}
pub(crate) fn partial_relfreq_bray_dist_matrix(&self, col_sums: &Array1<u64>) -> Array2<f64> {
pairwise_matrix(self.n_cols, |i, j| self.pair_partial_relfreq_bray(i, j, col_sums[i] as f64, col_sums[j] as f64))
pairwise_matrix(self.n_cols, |i, j| {
self.col_view(i).partial_relfreq_bray_dist(self.col_view(j), col_sums[i] as f64, col_sums[j] as f64)
})
}
pub(crate) fn partial_relfreq_euclidean_dist_matrix(&self, col_sums: &Array1<u64>) -> Array2<f64> {
pairwise_matrix(self.n_cols, |i, j| self.pair_partial_relfreq_euclidean(i, j, col_sums[i] as f64, col_sums[j] as f64))
pairwise_matrix(self.n_cols, |i, j| {
self.col_view(i).partial_relfreq_euclidean_dist(self.col_view(j), col_sums[i] as f64, col_sums[j] as f64)
})
}
pub(crate) fn partial_hellinger_euclidean_dist_matrix(&self, col_sums: &Array1<u64>) -> Array2<f64> {
pairwise_matrix(self.n_cols, |i, j| self.pair_partial_hellinger(i, j, col_sums[i] as f64, col_sums[j] as f64))
pairwise_matrix(self.n_cols, |i, j| {
self.col_view(i).partial_hellinger_euclidean_dist(self.col_view(j), col_sums[i] as f64, col_sums[j] as f64)
})
}
}
@@ -531,25 +511,9 @@ impl PersistentCompactIntMatrixBuilder {
impl MatrixGroupOps for PersistentCompactIntMatrix {
fn partial_group_presence_count(&self, g: &ColGroup, threshold: u32) -> io::Result<TempCompactIntVec> {
let n = self.n();
if g.indices.len() < 255 {
let mut builder = TempCompactIntVecBuilder::new(n)?;
for &c in &g.indices {
builder.inc_predicate_fast(self.col_view(c), |v| v >= threshold);
}
builder.freeze()
} else {
let mut result = TempCompactIntVecBuilder::new(n)?;
for chunk in g.indices.chunks(254) {
let mut chunk_b = TempCompactIntVecBuilder::new(n)?;
for &c in chunk {
chunk_b.inc_predicate_fast(self.col_view(c), |v| v >= threshold);
}
let frozen = chunk_b.freeze()?;
result.add(frozen.view());
}
result.freeze()
}
chunked_presence_count(self.n(), &g.indices, |b, c| {
b.inc_predicate_fast(self.col_view(c), |v| v >= threshold)
})
}
fn partial_group_sum(&self, g: &ColGroup) -> io::Result<TempCompactIntVec> {
+19 -60
View File
@@ -138,107 +138,66 @@ impl PersistentCompactIntVec {
}
// ── Distance methods ──────────────────────────────────────────────────────
//
// Thin delegates to `IntSliceView` (see `views.rs`) — the canonical,
// zero-copy implementation of every formula below. Kept here only for
// API convenience (`&PersistentCompactIntVec` in, no `.view()` needed at
// call sites); no formula is re-derived.
pub fn bray_dist(&self, other: &PersistentCompactIntVec) -> f64 {
let sum_min = self.partial_bray_dist(other);
let denom = self.sum() + other.sum();
if denom == 0 { 0.0 } else { 1.0 - 2.0 * sum_min as f64 / denom as f64 }
self.view().bray_dist(other.view())
}
pub fn partial_bray_dist(&self, other: &PersistentCompactIntVec) -> u64 {
assert_eq!(self.n, other.len(), "length mismatch");
self.iter().zip(other.iter()).map(|(a, b)| a.min(b) as u64).sum()
self.view().partial_bray_dist(other.view())
}
pub fn relfreq_bray_dist(&self, other: &PersistentCompactIntVec) -> f64 {
assert_eq!(self.n, other.len(), "length mismatch");
let sa = self.sum() as f64;
let sb = other.sum() as f64;
if sa == 0.0 && sb == 0.0 { return 0.0; }
1.0 - self.partial_relfreq_bray_dist(other, sa, sb)
self.view().relfreq_bray_dist(other.view())
}
pub fn partial_relfreq_bray_dist(&self, other: &PersistentCompactIntVec, sum_a: f64, sum_b: f64) -> f64 {
assert_eq!(self.n, other.len(), "length mismatch");
self.iter().zip(other.iter())
.map(|(a, b)| {
let pa = if sum_a > 0.0 { a as f64 / sum_a } else { 0.0 };
let pb = if sum_b > 0.0 { b as f64 / sum_b } else { 0.0 };
pa.min(pb)
})
.sum()
self.view().partial_relfreq_bray_dist(other.view(), sum_a, sum_b)
}
pub fn euclidean_dist(&self, other: &PersistentCompactIntVec) -> f64 {
self.partial_euclidean_dist(other).sqrt()
self.view().euclidean_dist(other.view())
}
pub fn partial_euclidean_dist(&self, other: &PersistentCompactIntVec) -> f64 {
assert_eq!(self.n, other.len(), "length mismatch");
self.iter().zip(other.iter())
.map(|(a, b)| { let d = a as f64 - b as f64; d * d })
.sum()
self.view().partial_euclidean_dist(other.view())
}
pub fn relfreq_euclidean_dist(&self, other: &PersistentCompactIntVec) -> f64 {
let sa = self.sum() as f64;
let sb = other.sum() as f64;
if sa == 0.0 && sb == 0.0 { return 0.0; }
self.partial_relfreq_euclidean_dist(other, sa, sb).sqrt()
self.view().relfreq_euclidean_dist(other.view())
}
pub fn partial_relfreq_euclidean_dist(&self, other: &PersistentCompactIntVec, sum_a: f64, sum_b: f64) -> f64 {
assert_eq!(self.n, other.len(), "length mismatch");
self.iter().zip(other.iter())
.map(|(a, b)| {
let pa = if sum_a > 0.0 { a as f64 / sum_a } else { 0.0 };
let pb = if sum_b > 0.0 { b as f64 / sum_b } else { 0.0 };
let d = pa - pb;
d * d
})
.sum()
self.view().partial_relfreq_euclidean_dist(other.view(), sum_a, sum_b)
}
pub fn hellinger_euclidean_dist(&self, other: &PersistentCompactIntVec) -> f64 {
let sa = self.sum() as f64;
let sb = other.sum() as f64;
if sa == 0.0 && sb == 0.0 { return 0.0; }
self.partial_hellinger_euclidean_dist(other, sa, sb).sqrt()
self.view().hellinger_euclidean_dist(other.view())
}
pub fn partial_hellinger_euclidean_dist(&self, other: &PersistentCompactIntVec, sum_a: f64, sum_b: f64) -> f64 {
assert_eq!(self.n, other.len(), "length mismatch");
self.iter().zip(other.iter())
.map(|(a, b)| {
let pa = if sum_a > 0.0 { (a as f64 / sum_a).sqrt() } else { 0.0 };
let pb = if sum_b > 0.0 { (b as f64 / sum_b).sqrt() } else { 0.0 };
let d = pa - pb;
d * d
})
.sum()
self.view().partial_hellinger_euclidean_dist(other.view(), sum_a, sum_b)
}
pub fn hellinger_dist(&self, other: &PersistentCompactIntVec) -> f64 {
self.hellinger_euclidean_dist(other) / std::f64::consts::SQRT_2
self.view().hellinger_dist(other.view())
}
pub fn threshold_jaccard_dist(&self, other: &PersistentCompactIntVec, threshold: u32) -> f64 {
let (intersection, union) = self.partial_threshold_jaccard_dist(other, threshold);
if union == 0 { 0.0 } else { 1.0 - intersection as f64 / union as f64 }
self.view().threshold_jaccard_dist(other.view(), threshold)
}
pub fn partial_threshold_jaccard_dist(&self, other: &PersistentCompactIntVec, threshold: u32) -> (u64, u64) {
assert_eq!(self.n, other.len(), "length mismatch");
self.iter().zip(other.iter())
.fold((0u64, 0u64), |(inter, uni), (a, b)| {
let ap = a >= threshold;
let bp = b >= threshold;
(inter + (ap & bp) as u64, uni + (ap | bp) as u64)
})
self.view().partial_threshold_jaccard_dist(other.view(), threshold)
}
pub fn jaccard_dist(&self, other: &PersistentCompactIntVec) -> f64 {
self.threshold_jaccard_dist(other, 1)
self.view().jaccard_dist(other.view())
}
}