use std::collections::HashSet;
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;
use std::sync::RwLock;
use lightmotif::abc::Dna;
use lightmotif::pli::dispatch::Dispatch;
use lightmotif::pli::Encode;
use lightmotif::pli::Pipeline;
use lightmotif::pli::Score;
use lightmotif::pli::Stripe;
use lightmotif::pli::Threshold;
use crate::errors::Error;
use crate::matrix::DokMatrix;
use crate::primer::Primer;
use crate::seq::count_ambiguous;
use crate::seq::reverse_complement;
use crate::seq::DisambiguationIterator;
use crate::utils::Interner;
use crate::utils::OrderedSet;
use crate::utils::Paired;
use crate::utils::Rc;
use super::Database;
use super::UnindexedRegion;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct Sketch {
pub name: Rc<str>,
pub kmer: Paired<Rc<str>>,
}
#[derive(Debug)]
pub struct Builder {
k: usize,
primer_mismatches: usize,
primers: Vec<Paired<Primer>>,
sketches: Vec<RwLock<HashSet<Sketch>>>,
interner: Interner<str>,
references: AtomicUsize,
pipeline: Pipeline<Dna, Dispatch>,
largest_primer: Option<usize>,
}
impl Builder {
pub fn new(mut primers: Vec<Paired<Primer>>) -> Self {
assert!(primers.len() < u8::MAX as usize);
let mut sketches = Vec::with_capacity(primers.len());
for _ in 0..primers.len() {
sketches.push(RwLock::new(HashSet::new()));
}
for pair in primers.iter_mut() {
pair.backward = pair.backward.reverse_complement();
}
let largest_primer = primers
.iter()
.flat_map(|pair| [pair.forward.profile(), pair.backward.profile()])
.map(|prof| prof.len())
.max();
Builder {
primers,
sketches,
interner: Default::default(),
k: 100,
primer_mismatches: 2,
references: AtomicUsize::new(0),
pipeline: Pipeline::dispatch(),
largest_primer,
}
}
pub fn with_primer_mismatches(mut self, primer_mismatches: usize) -> Self {
self.primer_mismatches = primer_mismatches;
self
}
pub fn add<I>(&self, name: I, sequence: &str) -> Result<usize, Error>
where
I: AsRef<str>,
{
let name_ = name.as_ref();
let mut n = 0;
if count_ambiguous(&sequence)? <= 3 {
for dna in DisambiguationIterator::new(&sequence).unwrap() {
n += self.add_unambiguous(name_, &dna)?;
}
}
Ok(n)
}
fn add_unambiguous<I>(&self, name: I, sequence: &str) -> Result<usize, Error>
where
I: AsRef<str>,
{
macro_rules! find_best {
(
$primer:expr,
$striped:ident,
$scores:ident,
$seq:expr,
$pos:ident,
$mm:ident
) => {{
self.pipeline
.score_into(&$primer.profile(), &$striped, &mut $scores);
$pos = 0;
$mm = usize::MAX;
let coordinates = self.pipeline.threshold(&$scores, 0.0);
for c in coordinates {
let i = $scores.offset(c);
let mm_i = $primer.mismatches(&$seq[i..i + $primer.len()]);
if mm_i < $mm || ((mm_i == $mm) && (i < $pos)) {
$mm = mm_i;
$pos = i;
}
}
if $mm > self.primer_mismatches {
continue;
}
}};
}
let mut scores = lightmotif::scores::StripedScores::<f32, _>::empty();
let mut striped = match self.pipeline.encode(&sequence[..sequence.len() - self.k]) {
Ok(encoded) => self.pipeline.stripe(encoded),
Err(_) => return Err(Error::InvalidDna),
};
if let Some(&n) = self.largest_primer.as_ref() {
striped.configure_wrap(n);
}
let mut name_rc: Option<Rc<str>> = None;
let mut amplified = 0;
for (region, primer) in self.primers.iter().enumerate() {
let mut fwd_pos;
let mut fwd_mm;
find_best!(primer.forward, striped, scores, sequence, fwd_pos, fwd_mm);
let mut bwd_pos;
let mut bwd_mm;
find_best!(primer.backward, striped, scores, sequence, bwd_pos, bwd_mm);
if fwd_pos >= bwd_pos {
continue;
}
if fwd_pos + primer.forward.len() + self.k > sequence.len() {
continue;
}
if bwd_pos < self.k {
continue;
}
let fwd_kmer = self.interner.intern(
&sequence[fwd_pos + primer.forward.len()..fwd_pos + primer.forward.len() + self.k],
);
let bwd_kmer = self
.interner
.intern(&reverse_complement(&sequence[bwd_pos - self.k..bwd_pos])?);
if self.sketches[region]
.write()
.expect("lock was poisoned")
.insert(Sketch {
kmer: Paired::new(fwd_kmer, bwd_kmer),
name: name_rc.get_or_insert_with(|| name.as_ref().into()).clone(),
})
{
amplified += 1;
}
}
if amplified > 0 {
self.references.fetch_add(1, Ordering::Relaxed);
}
Ok(amplified)
}
pub fn to_database(&self) -> Database {
let sketches_ref = self
.sketches
.iter()
.map(|s| s.read().expect("lock was poisoned"))
.collect::<Vec<_>>();
let names = sketches_ref
.iter()
.flat_map(|entries| entries.iter().map(|kmer| &kmer.name))
.cloned()
.collect::<OrderedSet<_>>();
let mut amplified = vec![0; names.len()];
for kmer in sketches_ref.iter().map(|v| v.iter()).flatten() {
amplified[names[&kmer.name]] += 1;
}
let mut regions = Vec::with_capacity(self.primers.len());
for (primer, sketches) in self.primers.iter().zip(sketches_ref.iter()) {
let unique = sketches
.iter()
.map(|sketch| &sketch.kmer)
.cloned()
.collect::<Paired<HashSet<_>>>()
.map(OrderedSet::from);
let unique_pairs: OrderedSet<Paired<usize>> = sketches
.iter()
.map(|sketch| &sketch.kmer)
.map(|kmer| {
Paired::new(
unique.forward[&kmer.forward],
unique.backward[&kmer.backward],
)
})
.collect::<HashSet<Paired<_>>>()
.into();
let mut matrix = DokMatrix::new(unique_pairs.len(), names.len());
for sketch in sketches.iter() {
let j = names[&sketch.name];
let h = unique_pairs[&Paired::new(
unique.forward[&sketch.kmer.forward],
unique.backward[&sketch.kmer.backward],
)];
if amplified[j] > 0 {
matrix.insert(h, j, 1.0 / amplified[j] as f32);
}
}
let mut region_primer = primer.clone();
region_primer.backward = region_primer.backward.reverse_complement();
regions.push(
UnindexedRegion {
primer: region_primer,
unique_pairs,
matrix: matrix.to_csr(),
unique_kmers: unique,
}
.into(),
)
}
Database {
k: self.k,
regions,
names,
amplified,
}
}
}
impl From<Builder> for Database {
fn from(builder: Builder) -> Self {
builder.to_database()
}
}
impl<'b> From<&'b Builder> for Database {
fn from(builder: &'b Builder) -> Self {
builder.to_database()
}
}