use chematic_core::Molecule;
use crate::transform::run_reactants;
#[derive(Clone, Debug)]
pub struct LibraryConfig {
pub skip_failures: bool,
pub max_size: Option<usize>,
}
impl Default for LibraryConfig {
fn default() -> Self {
Self {
skip_failures: true,
max_size: Some(1_000_000),
}
}
}
pub fn enumerate_library(
template: &str,
fragment_sets: Vec<Vec<Molecule>>,
config: &LibraryConfig,
) -> Result<Vec<Molecule>, LibraryError> {
if fragment_sets.is_empty() {
return Err(LibraryError::NoFragmentSets);
}
let total_combos: usize = fragment_sets
.iter()
.map(|set| set.len())
.product();
if let Some(max) = config.max_size
&& total_combos > max {
return Err(LibraryError::EnumerationTooLarge(total_combos, max));
}
let mut products: Vec<Molecule> = Vec::new();
let mut indices = vec![0usize; fragment_sets.len()];
loop {
let mut reactants = Vec::new();
for (set_idx, frag_set) in fragment_sets.iter().enumerate() {
reactants.push(&frag_set[indices[set_idx]]);
}
match run_reactants(template, &reactants) {
Ok(reaction_product_sets) => {
for product_set in reaction_product_sets {
products.extend(product_set);
}
}
Err(_) => {
if !config.skip_failures {
}
}
}
if let Some(max) = config.max_size
&& products.len() >= max {
return Err(LibraryError::EnumerationLimitExceeded);
}
let mut carry = 1usize;
for i in 0..indices.len() {
indices[i] += carry;
if indices[i] < fragment_sets[i].len() {
carry = 0;
break;
}
indices[i] = 0;
}
if carry == 1 {
break;
}
}
Ok(products)
}
pub fn enumerate_library_2way(
template: &str,
scaffolds: Vec<Molecule>,
building_blocks: Vec<Molecule>,
config: &LibraryConfig,
) -> Result<Vec<Molecule>, LibraryError> {
enumerate_library(template, vec![scaffolds, building_blocks], config)
}
pub fn enumerate_library_3way(
template: &str,
scaffolds: Vec<Molecule>,
r1_set: Vec<Molecule>,
r2_set: Vec<Molecule>,
config: &LibraryConfig,
) -> Result<Vec<Molecule>, LibraryError> {
enumerate_library(template, vec![scaffolds, r1_set, r2_set], config)
}
#[derive(Debug, Clone)]
pub enum LibraryError {
NoFragmentSets,
EnumerationTooLarge(usize, usize), EnumerationLimitExceeded,
}
impl std::fmt::Display for LibraryError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::NoFragmentSets => write!(f, "no fragment sets provided"),
Self::EnumerationTooLarge(actual, max) => {
write!(f, "enumeration too large: {} > {}", actual, max)
}
Self::EnumerationLimitExceeded => write!(f, "enumeration iteration limit exceeded"),
}
}
}
impl std::error::Error for LibraryError {}
#[cfg(test)]
mod tests {
use super::*;
use chematic_smiles::parse;
fn mol(smiles: &str) -> Molecule {
parse(smiles).unwrap_or_else(|e| panic!("failed to parse {smiles:?}: {e}"))
}
#[test]
fn test_enumerate_library_2way_simple() {
let template = "[C:1][OH].[C:2][NH2]>>[C:1]O[C:2]"; let scaffolds = vec![mol("c1ccccc1O"), mol("Cc1ccccc1O")]; let building_blocks = vec![mol("c1ccccc1N"), mol("CCN")];
let result = enumerate_library_2way(
template,
scaffolds,
building_blocks,
&LibraryConfig::default(),
);
match result {
Ok(products) => {
assert!(products.len() <= 4, "expected <= 4 products, got {}", products.len());
}
Err(_) => {
}
}
}
#[test]
fn test_enumerate_library_no_fragments() {
let template = "[C:1][Cl]>>[C:1]I";
let result = enumerate_library(template, vec![], &LibraryConfig::default());
assert!(matches!(result, Err(LibraryError::NoFragmentSets)));
}
#[test]
fn test_enumerate_library_size_check() {
let config = LibraryConfig {
skip_failures: true,
max_size: Some(3),
};
let template = "[C:1][Cl]>>[C:1]I";
let set1 = vec![mol("C"), mol("CC"), mol("CCC"), mol("CCCC")];
let set2 = vec![mol("C"), mol("CC"), mol("CCC"), mol("CCCC")];
let result = enumerate_library(template, vec![set1, set2], &config);
assert!(matches!(
result,
Err(LibraryError::EnumerationTooLarge(16, 3))
));
}
#[test]
fn test_enumerate_library_3way() {
let template = "[C:1][C:2][C:3]"; let set1 = vec![mol("C"), mol("CC")];
let set2 = vec![mol("CCC")];
let set3 = vec![mol("CCCC")];
let result = enumerate_library_3way(
template,
set1,
set2,
set3,
&LibraryConfig {
skip_failures: true,
max_size: Some(100),
},
);
match result {
Ok(products) => {
assert!(products.len() <= 2, "expected <= 2 products");
}
Err(_) => {
}
}
}
}