use std::collections::HashMap;
use std::sync::{Arc, Mutex};
#[derive(Clone, Debug, PartialEq, Default)]
pub struct DescriptorEntry {
pub mw: Option<f64>,
pub logp: Option<f64>,
pub tpsa: Option<f64>,
pub hba: Option<usize>,
pub hbd: Option<usize>,
pub rotb: Option<usize>,
}
#[derive(Clone, Debug)]
pub struct DescriptorCache {
cache: Arc<Mutex<HashMap<String, DescriptorEntry>>>,
max_size: usize,
}
impl DescriptorCache {
pub fn new(max_size: usize) -> Self {
Self {
cache: Arc::new(Mutex::new(HashMap::new())),
max_size,
}
}
pub fn get(&self, smiles: &str) -> Option<DescriptorEntry> {
self.cache.lock().ok().and_then(|c| c.get(smiles).cloned())
}
pub fn put(&self, smiles: String, entry: DescriptorEntry) {
if let Ok(mut cache) = self.cache.lock() {
if !cache.contains_key(&smiles) && cache.len() >= self.max_size {
if let Some(key) = cache.keys().next().cloned() {
cache.remove(&key);
}
}
cache.insert(smiles, entry);
}
}
pub fn clear(&self) {
if let Ok(mut cache) = self.cache.lock() {
cache.clear();
}
}
pub fn len(&self) -> usize {
self.cache.lock().map(|c| c.len()).unwrap_or(0)
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cache_put_get() {
let cache = DescriptorCache::new(100);
let entry = DescriptorEntry {
mw: Some(46.0),
..DescriptorEntry::default()
};
cache.put("CC".to_string(), entry.clone());
let retrieved = cache.get("CC").unwrap();
assert_eq!(retrieved.mw, Some(46.0));
}
#[test]
fn cache_miss() {
let cache = DescriptorCache::new(100);
assert_eq!(cache.get("CC"), None);
}
#[test]
fn cache_eviction() {
let cache = DescriptorCache::new(2);
let entry = DescriptorEntry::default();
cache.put("C1".to_string(), entry.clone());
cache.put("C2".to_string(), entry.clone());
cache.put("C3".to_string(), entry.clone());
assert!(cache.len() <= 2);
}
#[test]
fn cache_clear() {
let cache = DescriptorCache::new(100);
let entry = DescriptorEntry::default();
cache.put("CC".to_string(), entry);
assert!(!cache.is_empty());
cache.clear();
assert!(cache.is_empty());
}
}