|
|
@@ -1,7 +1,10 @@
|
|
|
-//! Genome uniqueness index based on suffix arrays.
|
|
|
+//! Genome-wide uniqueness index based on suffix arrays.
|
|
|
//!
|
|
|
//! This module computes, stores, and queries minimal unique sequence lengths
|
|
|
-//! across a genome.
|
|
|
+//! across a genome. Multi-contig references are indexed by concatenating the
|
|
|
+//! selected contigs with reserved separator bytes, building one global suffix
|
|
|
+//! array, and constraining all reported intervals to a single contig. The
|
|
|
+//! coordinates exposed by [`GenomeIndex`] remain contig-local.
|
|
|
//!
|
|
|
//! For each genomic position `pos`, the index stores the shortest sequence
|
|
|
//! starting at `pos` that occurs exactly once in the genome:
|
|
|
@@ -67,58 +70,108 @@
|
|
|
//! - [`UniqueContext`] — independent left/right uniqueness lengths.
|
|
|
//! - [`GenomicPos`] — genomic coordinate used by [`GenomeIndex`].
|
|
|
|
|
|
-use log::warn;
|
|
|
+use log::{info, warn};
|
|
|
+use rayon::prelude::*;
|
|
|
use std::io::{self};
|
|
|
use std::path::Path;
|
|
|
+use std::time::Instant;
|
|
|
+
|
|
|
+pub type GenomeCoord = u32;
|
|
|
+pub type UniqueLen = u32;
|
|
|
+
|
|
|
+pub const NOT_UNIQUE: UniqueLen = u32::MAX;
|
|
|
|
|
|
mod cache;
|
|
|
mod genome_index;
|
|
|
mod suffix;
|
|
|
|
|
|
pub use cache::CacheKey;
|
|
|
+pub use genome_index::{ContigSpan, GenomeIndex, GenomicPos};
|
|
|
|
|
|
use cache::{load_from_cache, save_to_cache};
|
|
|
use suffix::{build_lcp_kasai, build_suffix_array};
|
|
|
|
|
|
/// Pure computation: genome bytes → min_unique_len vector.
|
|
|
-/// Separated so GenomeIndex can call it without going through build_with_cache.
|
|
|
-fn build_min_unique_len(genome: &[u8]) -> Vec<usize> {
|
|
|
+///
|
|
|
+/// `contig_end_at[p]` is the exclusive end coordinate of the contig containing
|
|
|
+/// `p`. Separator positions must use `contig_end_at[p] = p`, which makes every
|
|
|
+/// separator position non-queryable.
|
|
|
+fn build_min_unique_len(genome: &[u8], contig_end_at: &[GenomeCoord]) -> Vec<UniqueLen> {
|
|
|
let n = genome.len();
|
|
|
+ assert_eq!(n, contig_end_at.len());
|
|
|
+ assert!(
|
|
|
+ n <= u32::MAX as usize,
|
|
|
+ "uniqueness index only supports sequences up to u32::MAX bases"
|
|
|
+ );
|
|
|
+
|
|
|
+ info!(
|
|
|
+ "[uniqueness] computing minimal unique lengths ({:.1} Mbp)",
|
|
|
+ n as f64 / 1e6
|
|
|
+ );
|
|
|
+ let total_started = Instant::now();
|
|
|
+
|
|
|
let sa = build_suffix_array(genome);
|
|
|
let lcp = build_lcp_kasai(genome, &sa);
|
|
|
|
|
|
- let mut rank = vec![0usize; n];
|
|
|
+ info!("[uniqueness] building suffix rank array");
|
|
|
+ let started = Instant::now();
|
|
|
+ let mut rank = vec![0u32; n];
|
|
|
for (i, &s) in sa.iter().enumerate() {
|
|
|
- rank[s] = i;
|
|
|
- }
|
|
|
-
|
|
|
- let mut mul = vec![usize::MAX; n];
|
|
|
- for p in 0..n {
|
|
|
- let r = rank[p];
|
|
|
- let left = if r > 0 { lcp[r] } else { 0 };
|
|
|
- let right = if r + 1 < n { lcp[r + 1] } else { 0 };
|
|
|
- let k = left.max(right) + 1;
|
|
|
- mul[p] = if p + k <= n { k } else { usize::MAX };
|
|
|
+ rank[s as usize] = i as u32;
|
|
|
}
|
|
|
- mul
|
|
|
+ drop(sa);
|
|
|
+ info!(
|
|
|
+ "[uniqueness] suffix rank array built in {:.1}s",
|
|
|
+ started.elapsed().as_secs_f64()
|
|
|
+ );
|
|
|
+
|
|
|
+ info!("[uniqueness] computing boundary-aware unique length vector");
|
|
|
+ let started = Instant::now();
|
|
|
+ let min_unique_len = (0..n)
|
|
|
+ .into_par_iter()
|
|
|
+ .map(|p| {
|
|
|
+ let r = rank[p] as usize;
|
|
|
+ let left = if r > 0 { lcp[r] } else { 0 };
|
|
|
+ let right = if r + 1 < n { lcp[r + 1] } else { 0 };
|
|
|
+ let Some(k) = left.max(right).checked_add(1) else {
|
|
|
+ return NOT_UNIQUE;
|
|
|
+ };
|
|
|
+ let Some(end) = (p as GenomeCoord).checked_add(k) else {
|
|
|
+ return NOT_UNIQUE;
|
|
|
+ };
|
|
|
+ if end <= contig_end_at[p] {
|
|
|
+ k
|
|
|
+ } else {
|
|
|
+ NOT_UNIQUE
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .collect();
|
|
|
+ drop(rank);
|
|
|
+ drop(lcp);
|
|
|
+ info!(
|
|
|
+ "[uniqueness] unique length vector computed in {:.1}s (total {:.1}s)",
|
|
|
+ started.elapsed().as_secs_f64(),
|
|
|
+ total_started.elapsed().as_secs_f64()
|
|
|
+ );
|
|
|
+ min_unique_len
|
|
|
}
|
|
|
|
|
|
pub struct UniqueContext {
|
|
|
/// Bases required before `pos`.
|
|
|
- pub before: Option<usize>,
|
|
|
+ pub before: Option<UniqueLen>,
|
|
|
|
|
|
/// Bases required from `pos` onward.
|
|
|
- pub after: Option<usize>,
|
|
|
+ pub after: Option<UniqueLen>,
|
|
|
}
|
|
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
|
pub struct UniqueInterval {
|
|
|
- pub start: usize,
|
|
|
- pub end: usize,
|
|
|
+ pub start: GenomeCoord,
|
|
|
+ pub end: GenomeCoord,
|
|
|
}
|
|
|
|
|
|
impl UniqueInterval {
|
|
|
- pub fn len(&self) -> usize {
|
|
|
+ pub fn len(&self) -> UniqueLen {
|
|
|
self.end - self.start
|
|
|
}
|
|
|
}
|
|
|
@@ -131,45 +184,89 @@ pub enum AnchorBias {
|
|
|
}
|
|
|
|
|
|
pub struct UniquenessIndex {
|
|
|
- min_unique_len: Vec<usize>,
|
|
|
- rev_min_unique_len: Vec<usize>,
|
|
|
- genome_len: usize,
|
|
|
+ min_unique_len: Vec<UniqueLen>,
|
|
|
+ rev_min_unique_len: Vec<UniqueLen>,
|
|
|
+ genome_len: GenomeCoord,
|
|
|
}
|
|
|
|
|
|
impl UniquenessIndex {
|
|
|
/// Build from genome bytes, using a disk cache keyed on `cache_key`.
|
|
|
/// Cache lives in `cache_dir` (created if absent).
|
|
|
/// If the cache file exists and is valid, loading is O(n) read — no recomputation.
|
|
|
- pub fn build_with_cache(genome: &[u8], key: &CacheKey, cache_dir: &Path) -> io::Result<Self> {
|
|
|
+ pub fn build_with_cache(
|
|
|
+ genome: &[u8],
|
|
|
+ contig_end_at: &[GenomeCoord],
|
|
|
+ rev_contig_end_at: &[GenomeCoord],
|
|
|
+ key: &CacheKey,
|
|
|
+ cache_dir: &Path,
|
|
|
+ ) -> io::Result<Self> {
|
|
|
+ assert_eq!(genome.len(), contig_end_at.len());
|
|
|
+ assert_eq!(genome.len(), rev_contig_end_at.len());
|
|
|
std::fs::create_dir_all(cache_dir)?;
|
|
|
|
|
|
let fwd_path = cache_dir.join(key.file_name());
|
|
|
let rev_path = cache_dir.join(format!("rev.{}", key.file_name()));
|
|
|
|
|
|
let min_unique_len = match load_from_cache(&fwd_path, genome.len()) {
|
|
|
- Ok(mul) => mul,
|
|
|
+ Ok(mul) => {
|
|
|
+ info!(
|
|
|
+ "[cache] forward uniqueness cache hit: {}",
|
|
|
+ fwd_path.display()
|
|
|
+ );
|
|
|
+ mul
|
|
|
+ }
|
|
|
Err(e) => {
|
|
|
if fwd_path.exists() {
|
|
|
warn!("[cache] invalid forward ({e}), rebuilding...");
|
|
|
+ } else {
|
|
|
+ info!(
|
|
|
+ "[cache] forward uniqueness cache miss: {}",
|
|
|
+ fwd_path.display()
|
|
|
+ );
|
|
|
}
|
|
|
|
|
|
- let mul = build_min_unique_len(genome);
|
|
|
+ let mul = build_min_unique_len(genome, contig_end_at);
|
|
|
+ info!(
|
|
|
+ "[cache] saving forward uniqueness cache: {}",
|
|
|
+ fwd_path.display()
|
|
|
+ );
|
|
|
save_to_cache(&fwd_path, &mul)?;
|
|
|
mul
|
|
|
}
|
|
|
};
|
|
|
|
|
|
let rev_min_unique_len = match load_from_cache(&rev_path, genome.len()) {
|
|
|
- Ok(mul) => mul,
|
|
|
+ Ok(mul) => {
|
|
|
+ info!(
|
|
|
+ "[cache] reverse uniqueness cache hit: {}",
|
|
|
+ rev_path.display()
|
|
|
+ );
|
|
|
+ mul
|
|
|
+ }
|
|
|
Err(e) => {
|
|
|
if rev_path.exists() {
|
|
|
warn!("[cache] invalid reverse ({e}), rebuilding...");
|
|
|
+ } else {
|
|
|
+ info!(
|
|
|
+ "[cache] reverse uniqueness cache miss: {}",
|
|
|
+ rev_path.display()
|
|
|
+ );
|
|
|
}
|
|
|
|
|
|
+ info!("[uniqueness] reversing genome for reverse uniqueness pass");
|
|
|
+ let started = Instant::now();
|
|
|
let mut rev = genome.to_vec();
|
|
|
rev.reverse();
|
|
|
+ info!(
|
|
|
+ "[uniqueness] reversed genome prepared in {:.1}s",
|
|
|
+ started.elapsed().as_secs_f64()
|
|
|
+ );
|
|
|
|
|
|
- let mul = build_min_unique_len(&rev);
|
|
|
+ let mul = build_min_unique_len(&rev, rev_contig_end_at);
|
|
|
+ info!(
|
|
|
+ "[cache] saving reverse uniqueness cache: {}",
|
|
|
+ rev_path.display()
|
|
|
+ );
|
|
|
save_to_cache(&rev_path, &mul)?;
|
|
|
mul
|
|
|
}
|
|
|
@@ -180,10 +277,14 @@ impl UniquenessIndex {
|
|
|
|
|
|
/// Construct directly from a precomputed `min_unique_len` vector.
|
|
|
/// Used when loading from disk cache — skips SA/LCP computation entirely.
|
|
|
- pub fn from_raw(min_unique_len: Vec<usize>, rev_min_unique_len: Vec<usize>) -> Self {
|
|
|
+ pub fn from_raw(min_unique_len: Vec<UniqueLen>, rev_min_unique_len: Vec<UniqueLen>) -> Self {
|
|
|
assert_eq!(min_unique_len.len(), rev_min_unique_len.len());
|
|
|
+ assert!(
|
|
|
+ min_unique_len.len() <= u32::MAX as usize,
|
|
|
+ "uniqueness index only supports sequences up to u32::MAX bases"
|
|
|
+ );
|
|
|
|
|
|
- let genome_len = min_unique_len.len();
|
|
|
+ let genome_len = min_unique_len.len() as u32;
|
|
|
|
|
|
Self {
|
|
|
min_unique_len,
|
|
|
@@ -194,11 +295,14 @@ impl UniquenessIndex {
|
|
|
|
|
|
/// Build without cache (e.g. for tests or one-shot use).
|
|
|
pub fn build(genome: &[u8]) -> Self {
|
|
|
- let min_unique_len = build_min_unique_len(genome);
|
|
|
+ let n = genome.len() as GenomeCoord;
|
|
|
+ let contig_end_at = vec![n; genome.len()];
|
|
|
+ let min_unique_len = build_min_unique_len(genome, &contig_end_at);
|
|
|
|
|
|
let mut rev = genome.to_vec();
|
|
|
rev.reverse();
|
|
|
- let rev_min_unique_len = build_min_unique_len(&rev);
|
|
|
+ let rev_contig_end_at = vec![n; genome.len()];
|
|
|
+ let rev_min_unique_len = build_min_unique_len(&rev, &rev_contig_end_at);
|
|
|
|
|
|
Self::from_raw(min_unique_len, rev_min_unique_len)
|
|
|
}
|
|
|
@@ -220,15 +324,16 @@ impl UniquenessIndex {
|
|
|
/// Panics if `pos >= self.genome_len()`.
|
|
|
pub fn minimal_unique_interval_containing(
|
|
|
&self,
|
|
|
- pos: usize,
|
|
|
+ pos: GenomeCoord,
|
|
|
bias: AnchorBias,
|
|
|
) -> Option<UniqueInterval> {
|
|
|
assert!(pos < self.genome_len);
|
|
|
+ let pos_usize = pos as usize;
|
|
|
|
|
|
match bias {
|
|
|
AnchorBias::Left => {
|
|
|
- let k = self.min_unique_len[pos];
|
|
|
- if k == usize::MAX {
|
|
|
+ let k = self.min_unique_len[pos_usize];
|
|
|
+ if k == NOT_UNIQUE {
|
|
|
return None;
|
|
|
}
|
|
|
Some(UniqueInterval {
|
|
|
@@ -239,8 +344,8 @@ impl UniquenessIndex {
|
|
|
|
|
|
AnchorBias::Right => {
|
|
|
for a in (0..=pos).rev() {
|
|
|
- let k = self.min_unique_len[a];
|
|
|
- if k == usize::MAX {
|
|
|
+ let k = self.min_unique_len[a as usize];
|
|
|
+ if k == NOT_UNIQUE {
|
|
|
continue;
|
|
|
}
|
|
|
if a + k > pos {
|
|
|
@@ -261,8 +366,11 @@ impl UniquenessIndex {
|
|
|
let window = 500;
|
|
|
let mut best: Option<UniqueInterval> = None;
|
|
|
for a in pos.saturating_sub(window)..=pos {
|
|
|
- let k = self.min_unique_len[a];
|
|
|
- if k == usize::MAX || a + k <= pos {
|
|
|
+ let k = self.min_unique_len[a as usize];
|
|
|
+ if k == NOT_UNIQUE {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if a + k <= pos {
|
|
|
continue;
|
|
|
}
|
|
|
let c = UniqueInterval {
|
|
|
@@ -285,7 +393,7 @@ impl UniquenessIndex {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- pub fn genome_len(&self) -> usize {
|
|
|
+ pub fn genome_len(&self) -> GenomeCoord {
|
|
|
self.genome_len
|
|
|
}
|
|
|
|
|
|
@@ -304,13 +412,17 @@ impl UniquenessIndex {
|
|
|
///
|
|
|
/// # Panics
|
|
|
///
|
|
|
- /// Panics if `pos >= self.genome_len()`.
|
|
|
- pub fn unique_context_at(&self, pos: usize) -> UniqueContext {
|
|
|
- assert!(pos < self.genome_len);
|
|
|
+ /// Panics if `pos > self.genome_len()`.
|
|
|
+ pub fn unique_context_at(&self, pos: GenomeCoord) -> UniqueContext {
|
|
|
+ assert!(pos <= self.genome_len);
|
|
|
|
|
|
- let after = match self.min_unique_len[pos] {
|
|
|
- usize::MAX => None,
|
|
|
- k => Some(k),
|
|
|
+ let after = if pos == self.genome_len {
|
|
|
+ None
|
|
|
+ } else {
|
|
|
+ match self.min_unique_len[pos as usize] {
|
|
|
+ NOT_UNIQUE => None,
|
|
|
+ k => Some(k),
|
|
|
+ }
|
|
|
};
|
|
|
|
|
|
let before = if pos == 0 {
|
|
|
@@ -318,8 +430,8 @@ impl UniquenessIndex {
|
|
|
} else {
|
|
|
let rev_pos = self.genome_len - pos;
|
|
|
|
|
|
- match self.rev_min_unique_len[rev_pos] {
|
|
|
- usize::MAX => None,
|
|
|
+ match self.rev_min_unique_len[rev_pos as usize] {
|
|
|
+ NOT_UNIQUE => None,
|
|
|
k => Some(k),
|
|
|
}
|
|
|
};
|
|
|
@@ -341,6 +453,10 @@ mod tests {
|
|
|
b"ACGTACGTNNACGT"
|
|
|
}
|
|
|
|
|
|
+ fn single_contig_end_at(genome: &[u8]) -> Vec<GenomeCoord> {
|
|
|
+ vec![genome.len() as GenomeCoord; genome.len()]
|
|
|
+ }
|
|
|
+
|
|
|
#[test]
|
|
|
fn test_cache_roundtrip() {
|
|
|
let genome = toy_genome();
|
|
|
@@ -348,9 +464,14 @@ mod tests {
|
|
|
|
|
|
let key = CacheKey::from_genome("chr_test", genome);
|
|
|
|
|
|
- let idx1 = UniquenessIndex::build_with_cache(genome, &key, dir).unwrap();
|
|
|
+ let contig_end_at = single_contig_end_at(genome);
|
|
|
+ let idx1 =
|
|
|
+ UniquenessIndex::build_with_cache(genome, &contig_end_at, &contig_end_at, &key, dir)
|
|
|
+ .unwrap();
|
|
|
// Second call must hit cache
|
|
|
- let idx2 = UniquenessIndex::build_with_cache(genome, &key, dir).unwrap();
|
|
|
+ let idx2 =
|
|
|
+ UniquenessIndex::build_with_cache(genome, &contig_end_at, &contig_end_at, &key, dir)
|
|
|
+ .unwrap();
|
|
|
|
|
|
assert_eq!(idx1.min_unique_len, idx2.min_unique_len);
|
|
|
}
|
|
|
@@ -362,19 +483,38 @@ mod tests {
|
|
|
let dir = std::path::Path::new("/home/t_steimle/tmp/");
|
|
|
let key = CacheKey::from_genome("chr_test", genome1);
|
|
|
|
|
|
- UniquenessIndex::build_with_cache(genome1, &key, dir).unwrap();
|
|
|
+ let contig_end_at = single_contig_end_at(genome1);
|
|
|
+ UniquenessIndex::build_with_cache(genome1, &contig_end_at, &contig_end_at, &key, dir)
|
|
|
+ .unwrap();
|
|
|
// genome2 has different len → load_from_cache returns Err → rebuild
|
|
|
let key2 = CacheKey::from_genome("chr_test", genome2);
|
|
|
// different hash → different file, no collision
|
|
|
assert_ne!(key.file_name(), key2.file_name());
|
|
|
}
|
|
|
|
|
|
+ #[test]
|
|
|
+ fn old_cache_format_is_rejected() {
|
|
|
+ let path = std::env::temp_dir().join(format!(
|
|
|
+ "pandora_old_uniqueness_cache_{}.uniqidx",
|
|
|
+ std::process::id()
|
|
|
+ ));
|
|
|
+ let mut bytes = Vec::new();
|
|
|
+ bytes.extend_from_slice(b"UNIQIDX1");
|
|
|
+ bytes.extend_from_slice(&1u32.to_le_bytes());
|
|
|
+ bytes.extend_from_slice(&1u64.to_le_bytes());
|
|
|
+ bytes.extend_from_slice(&1u64.to_le_bytes());
|
|
|
+ std::fs::write(&path, bytes).unwrap();
|
|
|
+
|
|
|
+ assert!(cache::load_from_cache(&path, 1).is_err());
|
|
|
+ let _ = std::fs::remove_file(path);
|
|
|
+ }
|
|
|
+
|
|
|
// ── helpers ───────────────────────────────────────────────────────────────
|
|
|
|
|
|
/// Verify that a given interval is actually unique in the genome:
|
|
|
/// the subsequence appears exactly once.
|
|
|
fn assert_unique(genome: &[u8], iv: &UniqueInterval) {
|
|
|
- let needle = &genome[iv.start..iv.end];
|
|
|
+ let needle = &genome[iv.start as usize..iv.end as usize];
|
|
|
let count = genome
|
|
|
.windows(needle.len())
|
|
|
.filter(|w| *w == needle)
|
|
|
@@ -399,7 +539,7 @@ mod tests {
|
|
|
start: iv.start,
|
|
|
end: iv.end - 1,
|
|
|
};
|
|
|
- let needle = &genome[shorter.start..shorter.end];
|
|
|
+ let needle = &genome[shorter.start as usize..shorter.end as usize];
|
|
|
let count = genome
|
|
|
.windows(needle.len())
|
|
|
.filter(|w| *w == needle)
|
|
|
@@ -422,7 +562,7 @@ mod tests {
|
|
|
start: iv.start + 1,
|
|
|
end: iv.end,
|
|
|
};
|
|
|
- let needle = &genome[shorter.start..shorter.end];
|
|
|
+ let needle = &genome[shorter.start as usize..shorter.end as usize];
|
|
|
let count = genome
|
|
|
.windows(needle.len())
|
|
|
.filter(|w| *w == needle)
|
|
|
@@ -438,16 +578,16 @@ mod tests {
|
|
|
|
|
|
/// Assert that no interval of the same length covering pos is shorter
|
|
|
/// (Auto minimality: no other anchor yields a shorter unique window).
|
|
|
- fn assert_minimal_auto(genome: &[u8], iv: &UniqueInterval, pos: usize) {
|
|
|
+ fn assert_minimal_auto(genome: &[u8], iv: &UniqueInterval, pos: GenomeCoord) {
|
|
|
// Try all anchors in [pos - len + 1 .. pos] and verify none gives shorter
|
|
|
let window = iv.len().saturating_sub(1);
|
|
|
for a in pos.saturating_sub(window)..=pos {
|
|
|
- if a + 1 > genome.len() {
|
|
|
+ if a + 1 > genome.len() as GenomeCoord {
|
|
|
break;
|
|
|
}
|
|
|
- let max_end = (a + iv.len()).min(genome.len());
|
|
|
+ let max_end = (a + iv.len()).min(genome.len() as GenomeCoord);
|
|
|
for end in (a + 1)..max_end {
|
|
|
- let needle = &genome[a..end];
|
|
|
+ let needle = &genome[a as usize..end as usize];
|
|
|
let count = genome
|
|
|
.windows(needle.len())
|
|
|
.filter(|w| *w == needle)
|
|
|
@@ -565,7 +705,7 @@ mod tests {
|
|
|
let idx = UniquenessIndex::build(&g);
|
|
|
|
|
|
for pos in [5, 10, 15, 20] {
|
|
|
- if pos >= g.len() {
|
|
|
+ if pos >= g.len() as GenomeCoord {
|
|
|
continue;
|
|
|
}
|
|
|
if let Some(iv) = idx.minimal_unique_interval_containing(pos, AnchorBias::Right) {
|
|
|
@@ -610,7 +750,7 @@ mod tests {
|
|
|
let g = genome_biased();
|
|
|
let idx = UniquenessIndex::build(&g);
|
|
|
|
|
|
- for pos in 0..g.len() {
|
|
|
+ for pos in 0..g.len() as GenomeCoord {
|
|
|
let auto = idx.minimal_unique_interval_containing(pos, AnchorBias::Auto);
|
|
|
let left = idx.minimal_unique_interval_containing(pos, AnchorBias::Left);
|
|
|
let right = idx.minimal_unique_interval_containing(pos, AnchorBias::Right);
|
|
|
@@ -646,7 +786,7 @@ mod tests {
|
|
|
let g = genome_repeats();
|
|
|
let idx = UniquenessIndex::build(&g);
|
|
|
|
|
|
- for pos in 0..g.len() {
|
|
|
+ for pos in 0..g.len() as GenomeCoord {
|
|
|
if let Some(iv) = idx.minimal_unique_interval_containing(pos, AnchorBias::Auto) {
|
|
|
assert!(
|
|
|
iv.start <= pos && iv.end > pos,
|
|
|
@@ -666,12 +806,12 @@ mod tests {
|
|
|
let g = genome_simple();
|
|
|
let idx = UniquenessIndex::build(&g);
|
|
|
|
|
|
- for pos in 0..g.len() {
|
|
|
+ for pos in 0..g.len() as GenomeCoord {
|
|
|
for bias in [AnchorBias::Left, AnchorBias::Right, AnchorBias::Auto] {
|
|
|
if let Some(iv) = idx.minimal_unique_interval_containing(pos, bias) {
|
|
|
assert!(iv.start <= pos, "start must be ≤ pos");
|
|
|
assert!(iv.end > pos, "end must be > pos");
|
|
|
- assert!(iv.end <= g.len(), "end must be in bounds");
|
|
|
+ assert!(iv.end <= g.len() as GenomeCoord, "end must be in bounds");
|
|
|
assert_unique(&g, &iv);
|
|
|
}
|
|
|
}
|
|
|
@@ -683,7 +823,7 @@ mod tests {
|
|
|
let g = genome_repeats();
|
|
|
let idx = UniquenessIndex::build(&g);
|
|
|
|
|
|
- for pos in 0..g.len() {
|
|
|
+ for pos in 0..g.len() as GenomeCoord {
|
|
|
if let Some(iv) = idx.minimal_unique_interval_containing(pos, AnchorBias::Left) {
|
|
|
assert_eq!(
|
|
|
iv.start, pos,
|
|
|
@@ -702,10 +842,15 @@ mod tests {
|
|
|
let dir = Path::new("/home/t_steimle/tmp/");
|
|
|
let key = CacheKey::from_genome("test_contig", &g);
|
|
|
|
|
|
- let idx_built = UniquenessIndex::build_with_cache(&g, &key, dir).unwrap();
|
|
|
- let idx_cached = UniquenessIndex::build_with_cache(&g, &key, dir).unwrap();
|
|
|
+ let contig_end_at = single_contig_end_at(&g);
|
|
|
+ let idx_built =
|
|
|
+ UniquenessIndex::build_with_cache(&g, &contig_end_at, &contig_end_at, &key, dir)
|
|
|
+ .unwrap();
|
|
|
+ let idx_cached =
|
|
|
+ UniquenessIndex::build_with_cache(&g, &contig_end_at, &contig_end_at, &key, dir)
|
|
|
+ .unwrap();
|
|
|
|
|
|
- for pos in 0..g.len() {
|
|
|
+ for pos in 0..g.len() as GenomeCoord {
|
|
|
for bias in [AnchorBias::Left, AnchorBias::Right, AnchorBias::Auto] {
|
|
|
assert_eq!(
|
|
|
idx_built.minimal_unique_interval_containing(pos, bias),
|
|
|
@@ -723,7 +868,7 @@ mod tests {
|
|
|
let index = GenomeIndex::build(
|
|
|
"/home/t_steimle/ref/hs1/chm13v2.0.fa",
|
|
|
"/home/t_steimle/ref/hs1/hs1_uniqueness",
|
|
|
- Some(&["chr1"]), // None = tout indexer
|
|
|
+ None, // None = tout indexer
|
|
|
)?;
|
|
|
|
|
|
let pos = GenomicPos {
|