#[allow(unused_imports)]
use log::{debug, trace};
use std::collections::{BinaryHeap, HashMap};
use std::hash::{BuildHasher, BuildHasherDefault, Hash, Hasher};
use std::marker::PhantomData;
use std::mem;
use std::fmt::Debug;
pub use crate::base::{kmer::*, sequence::*};
use crate::hashed::*;
use probminhash::invhash::*;
pub struct MinHashDist(pub f64, pub f64, pub u64, pub u64);
pub struct MinHashCount<T: Hash + Clone + Copy + Debug, H: Hasher + Default> {
keep_item: bool,
hashes: BinaryHeap<HashedItem<T>>,
b_hasher: BuildHasherDefault<H>,
counts: HashMap<ItemHash, u16, BuildHasherDefault<H>>,
total_count: u64,
size: usize,
}
impl<T: Hash + Clone + Copy + Debug, H: Hasher + Default> MinHashCount<T, H> {
pub fn new(size: usize, keep_item: bool) -> Self {
MinHashCount {
keep_item,
b_hasher: BuildHasherDefault::<H>::default(),
hashes: BinaryHeap::with_capacity(size + 1),
counts: HashMap::with_capacity_and_hasher(size, BuildHasherDefault::<H>::default()),
total_count: 0,
size,
}
}
#[allow(clippy::map_entry)]
pub fn push(&mut self, item: &T) {
let new_hash: u64 = self.b_hasher.hash_one(&item);
let add_hash = match self.hashes.peek() {
None => true,
Some(old_max_hash) => {
(new_hash <= old_max_hash.hash) || (self.hashes.len() < self.size)
}
};
if add_hash {
self.total_count += 1;
if self.counts.contains_key(&new_hash) {
let count = self.counts.entry(new_hash).or_insert(0u16);
(*count) += 1;
} else {
self.hashes.push(HashedItem {
hash: new_hash,
item: if self.keep_item { Some(*item) } else { None },
});
self.counts.insert(new_hash, 1u16);
if self.hashes.len() > self.size {
let hashitem = self.hashes.pop().unwrap();
let _old_count = self.counts.remove(&hashitem.hash).unwrap();
}
}
} }
pub fn sketch_slice(&mut self, to_sketch: &[T]) {
trace!("sketching slice");
to_sketch.iter().for_each(|x| self.push(x));
}
pub fn get_sketchcount(&self) -> Vec<HashCount<T>> {
trace!("get_sketchcount got nb hashes : {} ", self.hashes.len());
let mut results = Vec::with_capacity(self.hashes.len());
for item in self.hashes.iter() {
trace!(" got hash : {:?}", item.hash);
let counts = *self.counts.get(&item.hash).unwrap();
let counted_item = HashCount {
hashed: *item,
count: counts,
};
results.push(counted_item);
}
results
}
pub fn get_signature(&self) -> Option<&BinaryHeap<HashedItem<T>>> {
if self.keep_item {
None
} else {
Some(&self.hashes)
}
} }
pub fn minhash_distance<T: Hash + Clone + Copy>(
sketch1: &[HashCount<T>],
sketch2: &[HashCount<T>],
) -> MinHashDist {
let mut i: usize = 0;
let mut j: usize = 0;
let mut common: u64 = 0;
let mut total: u64 = 0;
let sketch_size = sketch1.len();
trace!(
"sketch1 len : {}, sketch2 len : {}",
sketch1.len(),
sketch2.len()
);
let mut items1: Vec<HashedItem<T>> = sketch1.iter().map(|x| x.hashed).collect();
items1.sort_unstable();
let mut items2: Vec<HashedItem<T>> = sketch2.iter().map(|x| x.hashed).collect();
items2.sort_unstable();
while i < items1.len() && j < items2.len() {
if items1[i] < items2[j] {
i += 1;
} else if items2[j] < items1[i] {
j += 1;
} else {
i += 1;
j += 1;
common += 1;
}
total += 1;
if total >= sketch1.len() as u64 {
break;
}
} if total < items1.len() as u64 {
if i < items1.len() {
total += (items1.len() - i) as u64;
}
if j < items1.len() {
total += (items1.len() - j) as u64;
}
if total > sketch_size as u64 {
total = sketch_size as u64;
}
}
let containment: f64 = common as f64 / i as f64;
let jaccard: f64 = common as f64 / total as f64;
MinHashDist(containment, jaccard, common, total)
}
pub struct MinInvHashCountKmer<T: CompressedKmerT, H: Hasher + Default> {
hashes: BinaryHeap<InvHashedItem<T>>,
counts: HashMap<ItemHash, u8, BuildHasherDefault<H>>,
total_count: u64,
size: usize,
}
impl<T: CompressedKmerT, H: Hasher + Default> MinInvHashCountKmer<T, H> {
pub fn new(size: usize) -> Self {
MinInvHashCountKmer {
hashes: BinaryHeap::with_capacity(size + 1),
counts: HashMap::with_capacity_and_hasher(size, BuildHasherDefault::<H>::default()),
total_count: 0,
size,
}
}
#[allow(clippy::map_entry)]
fn push(&mut self, item: &T) {
let kmerval: T::Val = item.get_compressed_value();
let new_hash = match mem::size_of::<T::Val>() {
4 => {
let val_u = unsafe { mem::transmute_copy::<T::Val, u32>(&kmerval) };
int64_hash(val_u as u64)
}
8 => {
let val_u = unsafe { mem::transmute_copy::<T::Val, u64>(&kmerval) };
int64_hash(val_u) as u64
}
_ => panic!("bad size of kmer value"),
};
let add_hash = match self.hashes.peek() {
None => true,
Some(old_max_hash) => {
(new_hash <= old_max_hash.hash) || (self.hashes.len() < self.size)
}
};
if add_hash {
self.total_count += 1;
if self.counts.contains_key(&new_hash) {
let count = self.counts.entry(new_hash).or_insert(0u8);
(*count) += 1;
} else {
self.hashes.push(InvHashedItem {
hash: new_hash,
t_marker: PhantomData,
});
self.counts.insert(new_hash, 1u8);
if self.hashes.len() > self.size {
let hashitem = self.hashes.pop().unwrap();
let _old_count = self.counts.remove(&hashitem.hash).unwrap();
}
}
} }
pub fn sketch_kmer_slice(&mut self, to_sketch: &[T]) {
trace!("sketching slice");
to_sketch.iter().for_each(|x| self.push(x));
}
pub fn get_sketchcount(self) -> Vec<InvHashCount<T>> {
let mut vec = self.hashes.into_sorted_vec();
let mut results = Vec::with_capacity(vec.len());
for item in vec.drain(..) {
let counts = *self.counts.get(&item.hash).unwrap();
let counted_item = InvHashCount {
hashed: item,
count: counts,
};
results.push(counted_item);
}
results
} }
pub fn mininvhash_distance<T: CompressedKmerT>(
sketch1: &[InvHashCount<T>],
sketch2: &[InvHashCount<T>],
) -> MinHashDist {
let mut i: usize = 0;
let mut j: usize = 0;
let mut common: u64 = 0;
let mut total: u64 = 0;
let sketch_size = sketch1.len();
while i < sketch1.len() && j < sketch2.len() {
if sketch1[i].hashed < sketch2[j].hashed {
i += 1;
} else if sketch2[j].hashed < sketch1[i].hashed {
j += 1;
} else {
i += 1;
j += 1;
common += 1;
}
total += 1;
if total >= sketch1.len() as u64 {
break;
}
} if total < sketch1.len() as u64 {
if i < sketch1.len() {
total += (sketch1.len() - i) as u64;
}
if j < sketch1.len() {
total += (sketch1.len() - j) as u64;
}
if total > sketch_size as u64 {
total = sketch_size as u64;
}
}
let containment: f64 = common as f64 / i as f64;
let jaccard: f64 = common as f64 / total as f64;
MinHashDist(containment, jaccard, common, total)
}
#[cfg(test)]
mod tests {
use super::*;
extern crate fnv;
#[allow(unused_imports)]
use self::fnv::FnvHasher; use crate::base::kmergenerator::*;
#[allow(unused_imports)]
use crate::nohasher::NoHashHasher;
fn init_log_test() {
let _ = env_logger::builder().is_test(true).try_init();
}
#[test]
fn test_minhash_count_range_intersection_fnv() {
init_log_test();
let va: Vec<usize> = (0..100).collect();
let vb: Vec<usize> = (80..160).collect();
let _bh = BuildHasherDefault::<FnvHasher>::default();
let mut minhash_a: MinHashCount<usize, FnvHasher> = MinHashCount::new(500, true);
let mut minhash_b: MinHashCount<usize, FnvHasher> = MinHashCount::new(500, true);
println!("sketching a ");
minhash_a.sketch_slice(&va);
println!("\n \n sketching b ");
minhash_b.sketch_slice(&vb);
let sketch_a = minhash_a.get_sketchcount();
let sketch_b = minhash_b.get_sketchcount();
let resdist = minhash_distance(&sketch_a, &sketch_b);
log::info!(
"distance minhash (contain, dist, common, total): {:.3e} {:.3e} {:.3e} {:.3e} ",
resdist.0,
resdist.1,
resdist.2,
resdist.3
);
if let Some(opthashes) = minhash_a.get_signature() {
trace!(" nb objects {} ", opthashes.len());
} else {
trace!("minhash_a.get_signature() returned None");
}
assert!(resdist.2 > 0);
}
#[test]
fn test_mininvhash_count_range_intersection_fnv() {
init_log_test();
let str = String::from(
"TCAAAGGGAAACATTCAAAATCAGTATGCGCCCGTTCAGTTACGTATTGCTCTCGCTAATGAGATGGGCTGGGTACAGAG",
);
let seq_bytes = str.as_bytes();
let vkmer_a: Vec<Kmer16b32bit> =
KmerGenerator::new(16).generate_kmer(&Sequence::new(&seq_bytes[0..80], 2));
let vkmer_b: Vec<Kmer16b32bit> =
KmerGenerator::new(16).generate_kmer(&Sequence::new(&seq_bytes[60..], 2));
let mut minhash_a: MinInvHashCountKmer<Kmer16b32bit, FnvHasher> =
MinInvHashCountKmer::new(5);
let mut minhash_b: MinInvHashCountKmer<Kmer16b32bit, FnvHasher> =
MinInvHashCountKmer::new(5);
println!("sketching a ");
minhash_a.sketch_kmer_slice(&vkmer_a);
println!("\n \n sketching b ");
minhash_b.sketch_kmer_slice(&vkmer_b);
let sketch_a = minhash_a.get_sketchcount();
let sketch_b = minhash_b.get_sketchcount();
let resdist = mininvhash_distance(&sketch_a, &sketch_b);
trace!(
"distance minhash (contain, dist, common, total): {} {} {} {} ",
resdist.0,
resdist.1,
resdist.2,
resdist.3
);
assert!(resdist.2 > 0);
} }