Push zunrplorkwkt #70
Generated
+62
-6
@@ -300,7 +300,7 @@ version = "1.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "af737c6c59cb018ecbe6472cbdf86d39c59d78252febfe311953a991b6e4ed85"
|
checksum = "af737c6c59cb018ecbe6472cbdf86d39c59d78252febfe311953a991b6e4ed85"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"common_traits",
|
"common_traits 0.11.4",
|
||||||
"epserde",
|
"epserde",
|
||||||
"mem_dbg",
|
"mem_dbg",
|
||||||
]
|
]
|
||||||
@@ -475,7 +475,18 @@ checksum = "fda9ae1f26adcae83adb2e92f69cf59421f2a277a942f49f8e59f2fcbd7cf062"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"half",
|
"half",
|
||||||
"impl-tools",
|
"impl-tools 0.10.3",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "common_traits"
|
||||||
|
version = "0.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d8e0d7d0c5977b1e87f759d406740c38227125f374f54d81bc197e877f2c5f26"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"half",
|
||||||
|
"impl-tools 0.12.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -783,7 +794,7 @@ checksum = "c40d342ff20a2ce62d9a85ce406e672dfa137f902ac9670034533184f1533976"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bitflags 2.11.1",
|
"bitflags 2.11.1",
|
||||||
"common_traits",
|
"common_traits 0.11.4",
|
||||||
"epserde-derive",
|
"epserde-derive",
|
||||||
"maligned",
|
"maligned",
|
||||||
"mem_dbg",
|
"mem_dbg",
|
||||||
@@ -1083,11 +1094,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "0ae95c9095c2f1126d7db785955c73cdc5fc33e7c3fa911bd4a42931672029a7"
|
checksum = "0ae95c9095c2f1126d7db785955c73cdc5fc33e7c3fa911bd4a42931672029a7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
"impl-tools-lib",
|
"impl-tools-lib 0.11.4",
|
||||||
"proc-macro-error2",
|
"proc-macro-error2",
|
||||||
"syn 2.0.117",
|
"syn 2.0.117",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "impl-tools"
|
||||||
|
version = "0.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "06c8567c75b167cdda5ecee024da9cc9581d41bd52460e2e0e768967972aeef1"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"impl-tools-lib 0.12.0",
|
||||||
|
"proc-macro-error3",
|
||||||
|
"syn 2.0.117",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "impl-tools-lib"
|
name = "impl-tools-lib"
|
||||||
version = "0.11.4"
|
version = "0.11.4"
|
||||||
@@ -1100,6 +1123,18 @@ dependencies = [
|
|||||||
"syn 2.0.117",
|
"syn 2.0.117",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "impl-tools-lib"
|
||||||
|
version = "0.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c81eb99ba7c6145aeccb8857ddc0c611cd52440091dcfddd605b6a930c9afbf4"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro-error3",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.117",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "2.14.0"
|
version = "2.14.0"
|
||||||
@@ -1519,7 +1554,7 @@ dependencies = [
|
|||||||
name = "obicompactvec"
|
name = "obicompactvec"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"common_traits",
|
"common_traits 0.13.0",
|
||||||
"memmap2",
|
"memmap2",
|
||||||
"ndarray",
|
"ndarray",
|
||||||
"rayon",
|
"rayon",
|
||||||
@@ -2009,6 +2044,16 @@ dependencies = [
|
|||||||
"quote",
|
"quote",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro-error-attr3"
|
||||||
|
version = "3.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b0084e6206a967a2dad822180626b2f6b07a3b379325e8f1ec0438e33a469ba7"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro-error2"
|
name = "proc-macro-error2"
|
||||||
version = "2.0.1"
|
version = "2.0.1"
|
||||||
@@ -2020,6 +2065,17 @@ dependencies = [
|
|||||||
"quote",
|
"quote",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro-error3"
|
||||||
|
version = "3.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0cf066225f2373bc711684792b69bdeac0356019b007e721090c24d92d5d5a50"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro-error-attr3",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.106"
|
version = "1.0.106"
|
||||||
@@ -2093,7 +2149,7 @@ dependencies = [
|
|||||||
"cacheline-ef",
|
"cacheline-ef",
|
||||||
"clap",
|
"clap",
|
||||||
"colored",
|
"colored",
|
||||||
"common_traits",
|
"common_traits 0.11.4",
|
||||||
"epserde",
|
"epserde",
|
||||||
"epserde-derive",
|
"epserde-derive",
|
||||||
"fastrand",
|
"fastrand",
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ version = "0.1.0"
|
|||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
common_traits = "0.11"
|
common_traits = "0.13"
|
||||||
memmap2 = "0.9"
|
memmap2 = "0.9"
|
||||||
ndarray = "0.17"
|
ndarray = "0.17"
|
||||||
rayon = "1"
|
rayon = "1"
|
||||||
|
|||||||
@@ -200,7 +200,7 @@ impl PersistentBitMatrix {
|
|||||||
/// buffer, ever — the whole point, see
|
/// buffer, ever — the whole point, see
|
||||||
/// `PersistentSparseBitMatrix::nonzero_iter`); `Columnar`/`Packed`
|
/// `PersistentSparseBitMatrix::nonzero_iter`); `Columnar`/`Packed`
|
||||||
/// reuse the same sorted-slot batching `fill_sub_matrix` always used
|
/// reuse the same sorted-slot batching `fill_sub_matrix` always used
|
||||||
/// (`BitSliceView::nonzero_among_sorted`), just eagerly collected here
|
/// (`BitSliceView::enumerate_nonzero_slots`), just eagerly collected here
|
||||||
/// rather than filled into a dense buffer — same `get` calls, same
|
/// rather than filled into a dense buffer — same `get` calls, same
|
||||||
/// mmap locality, no per-format duplication of that logic elsewhere.
|
/// mmap locality, no per-format duplication of that logic elsewhere.
|
||||||
/// `Implicit` is trivial (one column, always present).
|
/// `Implicit` is trivial (one column, always present).
|
||||||
@@ -228,7 +228,7 @@ impl PersistentBitMatrix {
|
|||||||
for c in 0..n_cols {
|
for c in 0..n_cols {
|
||||||
let view = self.col_view(c);
|
let view = self.col_view(c);
|
||||||
hits.extend(
|
hits.extend(
|
||||||
view.nonzero_among_sorted(&sorted_slots)
|
view.enumerate_nonzero_slots(&sorted_slots)
|
||||||
.map(|pos| (perm[pos], c, 1u32)),
|
.map(|pos| (perm[pos], c, 1u32)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,36 +75,15 @@ impl PersistentBitVec {
|
|||||||
/// The slots are sorted internally before reading so that accesses to
|
/// The slots are sorted internally before reading so that accesses to
|
||||||
/// the underlying mmap are as sequential as possible, then the results
|
/// the underlying mmap are as sequential as possible, then the results
|
||||||
/// are reordered to match the input order.
|
/// are reordered to match the input order.
|
||||||
pub fn get_batch(&self, slots: &[usize]) -> Vec<bool> {
|
#[inline]
|
||||||
let mut out = vec![false; slots.len()];
|
pub fn collect_slots_values(&self, slots: &[usize]) -> Vec<bool> {
|
||||||
self.fill_batch(slots, &mut out);
|
self.view().collect_slots_values(slots)
|
||||||
out
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Like [`get_batch`](Self::get_batch), but fills a caller-provided buffer.
|
/// Like [`collect_slots_values`](Self::collect_slots_values), but fills a caller-provided buffer.
|
||||||
pub fn fill_batch(&self, slots: &[usize], out: &mut [bool]) {
|
#[inline]
|
||||||
assert_eq!(slots.len(), out.len());
|
pub fn fill_slots_values(&self, slots: &[usize], out: &mut [bool]) {
|
||||||
let n = slots.len();
|
self.view().fill_slots_values(slots, out)
|
||||||
if n == 0 {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let mut perm: Vec<usize> = (0..n).collect();
|
|
||||||
perm.sort_by_key(|&i| slots[i]);
|
|
||||||
let sorted: Vec<usize> = perm.iter().map(|&i| slots[i]).collect();
|
|
||||||
let mut tmp = vec![false; n];
|
|
||||||
self.fill_batch_sorted(&sorted, &mut tmp);
|
|
||||||
for (i, &orig_idx) in perm.iter().enumerate() {
|
|
||||||
out[orig_idx] = tmp[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Fill `out` assuming `sorted_slots` is already in ascending order.
|
|
||||||
/// Results are written in `sorted_slots` order (no reordering).
|
|
||||||
pub(crate) fn fill_batch_sorted(&self, sorted_slots: &[usize], out: &mut [bool]) {
|
|
||||||
assert_eq!(sorted_slots.len(), out.len());
|
|
||||||
for (i, &slot) in sorted_slots.iter().enumerate() {
|
|
||||||
out[i] = self.get(slot);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SAFETY: mmap is page-aligned, HEADER_SIZE=16 divisible by 8 → u64-aligned.
|
// SAFETY: mmap is page-aligned, HEADER_SIZE=16 divisible by 8 → u64-aligned.
|
||||||
|
|||||||
@@ -372,7 +372,7 @@ impl PersistentCompactIntMatrix {
|
|||||||
let mut out: Vec<Vec<u32>> = Vec::with_capacity(n_cols);
|
let mut out: Vec<Vec<u32>> = Vec::with_capacity(n_cols);
|
||||||
for c in 0..n_cols {
|
for c in 0..n_cols {
|
||||||
let mut col_buf = vec![0u32; slots.len()];
|
let mut col_buf = vec![0u32; slots.len()];
|
||||||
self.col_view(c).fill_batch(slots, &mut col_buf);
|
self.col_view(c).fill_slots_values(slots, &mut col_buf);
|
||||||
out.push(col_buf);
|
out.push(col_buf);
|
||||||
}
|
}
|
||||||
out
|
out
|
||||||
@@ -397,7 +397,7 @@ impl PersistentCompactIntMatrix {
|
|||||||
for (c, col) in out.iter_mut().enumerate() {
|
for (c, col) in out.iter_mut().enumerate() {
|
||||||
col.resize(n, 0);
|
col.resize(n, 0);
|
||||||
let mut tmp = vec![0u32; n];
|
let mut tmp = vec![0u32; n];
|
||||||
self.col_view(c).fill_batch_sorted(&sorted_slots, &mut tmp);
|
self.col_view(c).fill_slots_values_sorted(&sorted_slots, &mut tmp);
|
||||||
for (i, &orig_idx) in perm.iter().enumerate() {
|
for (i, &orig_idx) in perm.iter().enumerate() {
|
||||||
col[orig_idx] = tmp[i];
|
col[orig_idx] = tmp[i];
|
||||||
}
|
}
|
||||||
@@ -411,7 +411,7 @@ impl PersistentCompactIntMatrix {
|
|||||||
/// design even though no sparse count format exists yet). No native
|
/// design even though no sparse count format exists yet). No native
|
||||||
/// low-effort case here the way `PersistentSparseBitMatrix` has one —
|
/// low-effort case here the way `PersistentSparseBitMatrix` has one —
|
||||||
/// both variants reuse the same sorted-slot batching `fill_sub_matrix`
|
/// both variants reuse the same sorted-slot batching `fill_sub_matrix`
|
||||||
/// already used, via `IntSliceView::nonzero_among_sorted`.
|
/// already used, via `IntSliceView::enumerate_nonzero_slots`.
|
||||||
pub fn nonzero_iter<'a>(&'a self, slots: &'a [usize]) -> impl Iterator<Item = (usize, usize, u32)> + 'a {
|
pub fn nonzero_iter<'a>(&'a self, slots: &'a [usize]) -> impl Iterator<Item = (usize, usize, u32)> + 'a {
|
||||||
let n = slots.len();
|
let n = slots.len();
|
||||||
let mut perm: Vec<usize> = (0..n).collect();
|
let mut perm: Vec<usize> = (0..n).collect();
|
||||||
@@ -422,7 +422,7 @@ impl PersistentCompactIntMatrix {
|
|||||||
for c in 0..n_cols {
|
for c in 0..n_cols {
|
||||||
hits.extend(
|
hits.extend(
|
||||||
self.col_view(c)
|
self.col_view(c)
|
||||||
.nonzero_among_sorted(&sorted_slots)
|
.enumerate_nonzero_slots(&sorted_slots)
|
||||||
.map(|(pos, v)| (perm[pos], c, v)),
|
.map(|(pos, v)| (perm[pos], c, v)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,34 +67,15 @@ impl PersistentCompactIntVec {
|
|||||||
/// The slots are sorted internally before reading so that accesses to
|
/// The slots are sorted internally before reading so that accesses to
|
||||||
/// the underlying mmap are as sequential as possible, then the results
|
/// the underlying mmap are as sequential as possible, then the results
|
||||||
/// are reordered to match the input order.
|
/// are reordered to match the input order.
|
||||||
pub fn get_batch(&self, slots: &[usize]) -> Vec<u32> {
|
#[inline]
|
||||||
let mut out = vec![0u32; slots.len()];
|
pub fn collect_slots_values(&self, slots: &[usize]) -> Vec<u32> {
|
||||||
self.fill_batch(slots, &mut out);
|
self.view().collect_slots_values(slots)
|
||||||
out
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Like [`get_batch`](Self::get_batch), but fills a caller-provided buffer.
|
/// Like [`collect_slots_values`](Self::collect_slots_values), but fills a caller-provided buffer.
|
||||||
pub fn fill_batch(&self, slots: &[usize], out: &mut [u32]) {
|
#[inline]
|
||||||
assert_eq!(slots.len(), out.len());
|
pub fn fill_slots_values(&self, slots: &[usize], out: &mut [u32]) {
|
||||||
let n = slots.len();
|
self.view().fill_slots_values(slots, out)
|
||||||
if n == 0 { return; }
|
|
||||||
let mut perm: Vec<usize> = (0..n).collect();
|
|
||||||
perm.sort_by_key(|&i| slots[i]);
|
|
||||||
let sorted: Vec<usize> = perm.iter().map(|&i| slots[i]).collect();
|
|
||||||
let mut tmp = vec![0u32; n];
|
|
||||||
self.fill_batch_sorted(&sorted, &mut tmp);
|
|
||||||
for (i, &orig_idx) in perm.iter().enumerate() {
|
|
||||||
out[orig_idx] = tmp[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Fill `out` assuming `sorted_slots` is already in ascending order.
|
|
||||||
/// Results are written in `sorted_slots` order (no reordering).
|
|
||||||
pub(crate) fn fill_batch_sorted(&self, sorted_slots: &[usize], out: &mut [u32]) {
|
|
||||||
assert_eq!(sorted_slots.len(), out.len());
|
|
||||||
for (i, &slot) in sorted_slots.iter().enumerate() {
|
|
||||||
out[i] = self.get(slot);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn overflow_get(&self, slot: usize) -> u32 {
|
fn overflow_get(&self, slot: usize) -> u32 {
|
||||||
|
|||||||
@@ -215,65 +215,65 @@ fn hamming_dist_basic() {
|
|||||||
assert_eq!(ra.hamming_dist(&rb), 2);
|
assert_eq!(ra.hamming_dist(&rb), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── get_batch tests ────────────────────────────────────────────────────────────
|
// ── collect_slots_values tests ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn bitvec_get_batch_in_order() {
|
fn bitvec_collect_slots_values_in_order() {
|
||||||
let bits = vec![true, false, true, false, true];
|
let bits = vec![true, false, true, false, true];
|
||||||
let (_dir, r) = make_bv(&bits);
|
let (_dir, r) = make_bv(&bits);
|
||||||
let got = r.get_batch(&[0, 1, 2, 3, 4]);
|
let got = r.collect_slots_values(&[0, 1, 2, 3, 4]);
|
||||||
assert_eq!(got, bits);
|
assert_eq!(got, bits);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn bitvec_get_batch_out_of_order() {
|
fn bitvec_collect_slots_values_out_of_order() {
|
||||||
let bits = vec![true, false, true, false, true];
|
let bits = vec![true, false, true, false, true];
|
||||||
let (_dir, r) = make_bv(&bits);
|
let (_dir, r) = make_bv(&bits);
|
||||||
let got = r.get_batch(&[3, 0, 4, 1]);
|
let got = r.collect_slots_values(&[3, 0, 4, 1]);
|
||||||
assert_eq!(got, vec![false, true, true, false]);
|
assert_eq!(got, vec![false, true, true, false]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn bitvec_get_batch_with_duplicates() {
|
fn bitvec_collect_slots_values_with_duplicates() {
|
||||||
let bits = vec![true, false, true];
|
let bits = vec![true, false, true];
|
||||||
let (_dir, r) = make_bv(&bits);
|
let (_dir, r) = make_bv(&bits);
|
||||||
let got = r.get_batch(&[0, 2, 0, 1]);
|
let got = r.collect_slots_values(&[0, 2, 0, 1]);
|
||||||
assert_eq!(got, vec![true, true, true, false]);
|
assert_eq!(got, vec![true, true, true, false]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn bitvec_get_batch_empty() {
|
fn bitvec_collect_slots_values_empty() {
|
||||||
let (_dir, r) = make_bv(&[true, false]);
|
let (_dir, r) = make_bv(&[true, false]);
|
||||||
let got = r.get_batch(&[]);
|
let got = r.collect_slots_values(&[]);
|
||||||
assert!(got.is_empty());
|
assert!(got.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn bitvec_get_batch_out_of_bounds_returns_false() {
|
fn bitvec_collect_slots_values_out_of_bounds_returns_false() {
|
||||||
// PersistentBitVec::get does NOT bounds-check — out-of-range slots read
|
// PersistentBitVec::get does NOT bounds-check — out-of-range slots read
|
||||||
// whatever bit happens to be in the mmap (zero-initialised, so false).
|
// whatever bit happens to be in the mmap (zero-initialised, so false).
|
||||||
// This is a known gap; get_batch inherits it.
|
// This is a known gap; collect_slots_values inherits it.
|
||||||
let (_dir, r) = make_bv(&[true, false]);
|
let (_dir, r) = make_bv(&[true, false]);
|
||||||
let got = r.get_batch(&[0, 2]);
|
let got = r.collect_slots_values(&[0, 2]);
|
||||||
assert_eq!(got, vec![true, false]);
|
assert_eq!(got, vec![true, false]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// BitSliceView get_batch (same logic, exercised through the view)
|
// BitSliceView collect_slots_values (same logic, exercised through the view)
|
||||||
#[test]
|
#[test]
|
||||||
fn bitslice_view_get_batch() {
|
fn bitslice_view_collect_slots_values() {
|
||||||
let bits = vec![true, false, true, false, true];
|
let bits = vec![true, false, true, false, true];
|
||||||
let (_dir, r) = make_bv(&bits);
|
let (_dir, r) = make_bv(&bits);
|
||||||
let view = r.view();
|
let view = r.view();
|
||||||
assert_eq!(view.get_batch(&[0, 1, 2]), vec![true, false, true]);
|
assert_eq!(view.collect_slots_values(&[0, 1, 2]), vec![true, false, true]);
|
||||||
assert_eq!(view.get_batch(&[4, 3]), vec![true, false]);
|
assert_eq!(view.collect_slots_values(&[4, 3]), vec![true, false]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn bitslice_view_get_batch_out_of_bounds_returns_false() {
|
fn bitslice_view_collect_slots_values_out_of_bounds_returns_false() {
|
||||||
// BitSliceView::get does NOT bounds-check either.
|
// BitSliceView::get does NOT bounds-check either.
|
||||||
let bits = vec![true, false];
|
let bits = vec![true, false];
|
||||||
let (_dir, r) = make_bv(&bits);
|
let (_dir, r) = make_bv(&bits);
|
||||||
let view = r.view();
|
let view = r.view();
|
||||||
let got = view.get_batch(&[0, 2]);
|
let got = view.collect_slots_values(&[0, 2]);
|
||||||
assert_eq!(got, vec![true, false]);
|
assert_eq!(got, vec![true, false]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -400,7 +400,7 @@ fn partial_relfreq_bray_additive_across_split() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── get_batch tests ────────────────────────────────────────────────────────────
|
// ── collect_slots_values tests ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
fn make_pciv(counts: &[u32]) -> (tempfile::TempDir, PersistentCompactIntVec) {
|
fn make_pciv(counts: &[u32]) -> (tempfile::TempDir, PersistentCompactIntVec) {
|
||||||
let dir = tempdir().unwrap();
|
let dir = tempdir().unwrap();
|
||||||
@@ -413,49 +413,49 @@ fn make_pciv(counts: &[u32]) -> (tempfile::TempDir, PersistentCompactIntVec) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn pciv_get_batch_in_order() {
|
fn pciv_collect_slots_values_in_order() {
|
||||||
let counts = vec![10u32, 255, 300, 1000];
|
let counts = vec![10u32, 255, 300, 1000];
|
||||||
let (_dir, v) = make_pciv(&counts);
|
let (_dir, v) = make_pciv(&counts);
|
||||||
let got = v.get_batch(&[0, 1, 2, 3]);
|
let got = v.collect_slots_values(&[0, 1, 2, 3]);
|
||||||
assert_eq!(got, counts);
|
assert_eq!(got, counts);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn pciv_get_batch_out_of_order() {
|
fn pciv_collect_slots_values_out_of_order() {
|
||||||
let counts = vec![10u32, 255, 300, 1000];
|
let counts = vec![10u32, 255, 300, 1000];
|
||||||
let (_dir, v) = make_pciv(&counts);
|
let (_dir, v) = make_pciv(&counts);
|
||||||
let got = v.get_batch(&[3, 0, 2, 1]);
|
let got = v.collect_slots_values(&[3, 0, 2, 1]);
|
||||||
assert_eq!(got, vec![1000, 10, 300, 255]);
|
assert_eq!(got, vec![1000, 10, 300, 255]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn pciv_get_batch_with_duplicates() {
|
fn pciv_collect_slots_values_with_duplicates() {
|
||||||
let counts = vec![10u32, 255, 300];
|
let counts = vec![10u32, 255, 300];
|
||||||
let (_dir, v) = make_pciv(&counts);
|
let (_dir, v) = make_pciv(&counts);
|
||||||
let got = v.get_batch(&[0, 2, 0, 1]);
|
let got = v.collect_slots_values(&[0, 2, 0, 1]);
|
||||||
assert_eq!(got, vec![10, 300, 10, 255]);
|
assert_eq!(got, vec![10, 300, 10, 255]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn pciv_get_batch_empty() {
|
fn pciv_collect_slots_values_empty() {
|
||||||
let (_dir, v) = make_pciv(&[10u32, 20]);
|
let (_dir, v) = make_pciv(&[10u32, 20]);
|
||||||
let got: Vec<u32> = v.get_batch(&[]);
|
let got: Vec<u32> = v.collect_slots_values(&[]);
|
||||||
assert!(got.is_empty());
|
assert!(got.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn pciv_get_batch_out_of_bounds_panics() {
|
fn pciv_collect_slots_values_out_of_bounds_panics() {
|
||||||
let (_dir, v) = make_pciv(&[10u32, 20]);
|
let (_dir, v) = make_pciv(&[10u32, 20]);
|
||||||
let result = std::panic::catch_unwind(|| v.get_batch(&[0, 2]));
|
let result = std::panic::catch_unwind(|| v.collect_slots_values(&[0, 2]));
|
||||||
assert!(result.is_err(), "get_batch should panic on out-of-bounds slot");
|
assert!(result.is_err(), "collect_slots_values should panic on out-of-bounds slot");
|
||||||
}
|
}
|
||||||
|
|
||||||
// IntSliceView get_batch (same logic, exercised through the view)
|
// IntSliceView collect_slots_values (same logic, exercised through the view)
|
||||||
#[test]
|
#[test]
|
||||||
fn intslice_view_get_batch() {
|
fn intslice_view_collect_slots_values() {
|
||||||
let counts = vec![10u32, 255, 300, 1000];
|
let counts = vec![10u32, 255, 300, 1000];
|
||||||
let (_dir, v) = make_pciv(&counts);
|
let (_dir, v) = make_pciv(&counts);
|
||||||
let view = v.view();
|
let view = v.view();
|
||||||
assert_eq!(view.get_batch(&[0, 1, 2]), vec![10, 255, 300]);
|
assert_eq!(view.collect_slots_values(&[0, 1, 2]), vec![10, 255, 300]);
|
||||||
assert_eq!(view.get_batch(&[3, 1]), vec![1000, 255]);
|
assert_eq!(view.collect_slots_values(&[3, 1]), vec![1000, 255]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,13 +30,13 @@ impl<'a> BitSliceView<'a> {
|
|||||||
///
|
///
|
||||||
/// Slots are sorted internally before reading, then results are reordered
|
/// Slots are sorted internally before reading, then results are reordered
|
||||||
/// to match the input order.
|
/// to match the input order.
|
||||||
pub fn get_batch(&self, slots: &[usize]) -> Vec<bool> {
|
pub fn collect_slots_values(&self, slots: &[usize]) -> Vec<bool> {
|
||||||
let mut out = vec![false; slots.len()];
|
let mut out = vec![false; slots.len()];
|
||||||
self.fill_batch(slots, &mut out);
|
self.fill_slots_values(slots, &mut out);
|
||||||
out
|
out
|
||||||
}
|
}
|
||||||
/// Like [`get_batch`](Self::get_batch), but fills a caller-provided buffer.
|
/// Like [`collect_slots_values`](Self::collect_slots_values), but fills a caller-provided buffer.
|
||||||
pub fn fill_batch(&self, slots: &[usize], out: &mut [bool]) {
|
pub fn fill_slots_values(&self, slots: &[usize], out: &mut [bool]) {
|
||||||
assert_eq!(slots.len(), out.len());
|
assert_eq!(slots.len(), out.len());
|
||||||
let n = slots.len();
|
let n = slots.len();
|
||||||
if n == 0 { return; }
|
if n == 0 { return; }
|
||||||
@@ -44,32 +44,43 @@ impl<'a> BitSliceView<'a> {
|
|||||||
perm.sort_by_key(|&i| slots[i]);
|
perm.sort_by_key(|&i| slots[i]);
|
||||||
let sorted: Vec<usize> = perm.iter().map(|&i| slots[i]).collect();
|
let sorted: Vec<usize> = perm.iter().map(|&i| slots[i]).collect();
|
||||||
let mut tmp = vec![false; n];
|
let mut tmp = vec![false; n];
|
||||||
self.fill_batch_sorted(&sorted, &mut tmp);
|
self.fill_slots_values_sorted(&sorted, &mut tmp);
|
||||||
for (i, &orig_idx) in perm.iter().enumerate() {
|
for (i, &orig_idx) in perm.iter().enumerate() {
|
||||||
out[orig_idx] = tmp[i];
|
out[orig_idx] = tmp[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fill `out` assuming `sorted_slots` is already in ascending order.
|
/// `(position in slots, value)` for every slot. Plain `enumerate` +
|
||||||
/// Results are written in `sorted_slots` order (no reordering).
|
/// `get`: order never affects correctness here, only the caller's cache
|
||||||
pub(crate) fn fill_batch_sorted(&self, sorted_slots: &[usize], out: &mut [bool]) {
|
/// locality (see `enumerate_nonzero_slots`), so this takes `slots` as
|
||||||
assert_eq!(sorted_slots.len(), out.len());
|
/// given.
|
||||||
for (i, &slot) in sorted_slots.iter().enumerate() {
|
#[inline]
|
||||||
out[i] = self.get(slot);
|
pub fn enumerate_slots_values<'b>(self, slots: &'b [usize]) -> impl Iterator<Item = (usize, bool)> + 'b
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Positions in `sorted_slots` (not the slots themselves) whose bit is
|
|
||||||
/// set — a `filter` over `get`, not a new traversal. `sorted_slots`
|
|
||||||
/// need not actually be sorted for correctness, but callers batching
|
|
||||||
/// several columns over the same slot set sort once and reuse it for
|
|
||||||
/// cache-friendlier `get` access (see `PersistentBitMatrix::nonzero_iter`).
|
|
||||||
pub(crate) fn nonzero_among_sorted<'b>(self, sorted_slots: &'b [usize]) -> impl Iterator<Item = usize> + 'b
|
|
||||||
where
|
where
|
||||||
Self: 'b,
|
Self: 'b,
|
||||||
{
|
{
|
||||||
sorted_slots.iter().enumerate()
|
slots.iter().enumerate().map(move |(pos, &slot)| (pos, self.get(slot)))
|
||||||
.filter_map(move |(pos, &slot)| self.get(slot).then_some(pos))
|
}
|
||||||
|
|
||||||
|
/// Fill `out` assuming `sorted_slots` is already in ascending order.
|
||||||
|
/// Results are written in `sorted_slots` order (no reordering).
|
||||||
|
pub(crate) fn fill_slots_values_sorted(&self, sorted_slots: &[usize], out: &mut [bool]) {
|
||||||
|
assert_eq!(sorted_slots.len(), out.len());
|
||||||
|
for (pos, v) in self.enumerate_slots_values(sorted_slots) {
|
||||||
|
out[pos] = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Positions in `slots` (not the slots themselves) whose bit is set.
|
||||||
|
/// Order of `slots` never matters for correctness; callers batching
|
||||||
|
/// several columns over the same slot set sort it once beforehand
|
||||||
|
/// purely for cache-friendlier `get` access (see
|
||||||
|
/// `PersistentBitMatrix::nonzero_iter`).
|
||||||
|
pub fn enumerate_nonzero_slots<'b>(self, slots: &'b [usize]) -> impl Iterator<Item = usize> + 'b
|
||||||
|
where
|
||||||
|
Self: 'b,
|
||||||
|
{
|
||||||
|
self.enumerate_slots_values(slots).filter_map(|(pos, v)| v.then_some(pos))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn count_ones(&self) -> u64 {
|
pub fn count_ones(&self) -> u64 {
|
||||||
@@ -184,14 +195,14 @@ impl<'a> IntSliceView<'a> {
|
|||||||
///
|
///
|
||||||
/// Slots are sorted internally before reading, then results are reordered
|
/// Slots are sorted internally before reading, then results are reordered
|
||||||
/// to match the input order.
|
/// to match the input order.
|
||||||
pub fn get_batch(&self, slots: &[usize]) -> Vec<u32> {
|
pub fn collect_slots_values(&self, slots: &[usize]) -> Vec<u32> {
|
||||||
let mut out = vec![0u32; slots.len()];
|
let mut out = vec![0u32; slots.len()];
|
||||||
self.fill_batch(slots, &mut out);
|
self.fill_slots_values(slots, &mut out);
|
||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Like [`get_batch`](Self::get_batch), but fills a caller-provided buffer.
|
/// Like [`collect_slots_values`](Self::collect_slots_values), but fills a caller-provided buffer.
|
||||||
pub fn fill_batch(&self, slots: &[usize], out: &mut [u32]) {
|
pub fn fill_slots_values(&self, slots: &[usize], out: &mut [u32]) {
|
||||||
assert_eq!(slots.len(), out.len());
|
assert_eq!(slots.len(), out.len());
|
||||||
let n = slots.len();
|
let n = slots.len();
|
||||||
if n == 0 { return; }
|
if n == 0 { return; }
|
||||||
@@ -199,32 +210,40 @@ impl<'a> IntSliceView<'a> {
|
|||||||
perm.sort_by_key(|&i| slots[i]);
|
perm.sort_by_key(|&i| slots[i]);
|
||||||
let sorted: Vec<usize> = perm.iter().map(|&i| slots[i]).collect();
|
let sorted: Vec<usize> = perm.iter().map(|&i| slots[i]).collect();
|
||||||
let mut tmp = vec![0u32; n];
|
let mut tmp = vec![0u32; n];
|
||||||
self.fill_batch_sorted(&sorted, &mut tmp);
|
self.fill_slots_values_sorted(&sorted, &mut tmp);
|
||||||
for (i, &orig_idx) in perm.iter().enumerate() {
|
for (i, &orig_idx) in perm.iter().enumerate() {
|
||||||
out[orig_idx] = tmp[i];
|
out[orig_idx] = tmp[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fill `out` assuming `sorted_slots` is already in ascending order.
|
/// `(position in slots, value)` for every slot. See
|
||||||
/// Results are written in `sorted_slots` order (no reordering).
|
/// [`BitSliceView::enumerate_slots_values`]: order never affects
|
||||||
pub(crate) fn fill_batch_sorted(&self, sorted_slots: &[usize], out: &mut [u32]) {
|
/// correctness here.
|
||||||
assert_eq!(sorted_slots.len(), out.len());
|
#[inline]
|
||||||
for (i, &slot) in sorted_slots.iter().enumerate() {
|
pub fn enumerate_slots_values<'b>(self, slots: &'b [usize]) -> impl Iterator<Item = (usize, u32)> + 'b
|
||||||
out[i] = self.get(slot);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Like [`BitSliceView::nonzero_among_sorted`]: `(position in
|
|
||||||
/// sorted_slots, value)` for every nonzero value — a `filter_map` over
|
|
||||||
/// `get`, not a new traversal.
|
|
||||||
pub(crate) fn nonzero_among_sorted<'b>(self, sorted_slots: &'b [usize]) -> impl Iterator<Item = (usize, u32)> + 'b
|
|
||||||
where
|
where
|
||||||
Self: 'b,
|
Self: 'b,
|
||||||
{
|
{
|
||||||
sorted_slots.iter().enumerate().filter_map(move |(pos, &slot)| {
|
slots.iter().enumerate().map(move |(pos, &slot)| (pos, self.get(slot)))
|
||||||
let v = self.get(slot);
|
}
|
||||||
(v != 0).then_some((pos, v))
|
|
||||||
})
|
/// Fill `out` assuming `sorted_slots` is already in ascending order.
|
||||||
|
/// Results are written in `sorted_slots` order (no reordering).
|
||||||
|
pub(crate) fn fill_slots_values_sorted(&self, sorted_slots: &[usize], out: &mut [u32]) {
|
||||||
|
assert_eq!(sorted_slots.len(), out.len());
|
||||||
|
for (pos, v) in self.enumerate_slots_values(sorted_slots) {
|
||||||
|
out[pos] = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Like [`BitSliceView::enumerate_nonzero_slots`]: `(position in slots,
|
||||||
|
/// value)` for every nonzero value — a `filter_map` over `get`, not a
|
||||||
|
/// new traversal.
|
||||||
|
pub fn enumerate_nonzero_slots<'b>(self, slots: &'b [usize]) -> impl Iterator<Item = (usize, u32)> + 'b
|
||||||
|
where
|
||||||
|
Self: 'b,
|
||||||
|
{
|
||||||
|
self.enumerate_slots_values(slots).filter_map(|(pos, v)| (v != 0).then_some((pos, v)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sequential merge scan: yields all n values in slot order.
|
/// Sequential merge scan: yields all n values in slot order.
|
||||||
|
|||||||
Reference in New Issue
Block a user