use crate::bitvec::BitVec2048;
use crate::ecfp::ecfp4;
#[derive(Clone, Debug)]
pub struct ReactionFpConfig {
pub use_xor: bool,
}
impl Default for ReactionFpConfig {
fn default() -> Self {
ReactionFpConfig {
use_xor: true,
}
}
}
#[derive(Clone, Debug)]
pub struct ReactionFingerprint {
pub reactant_fp: BitVec2048,
pub product_fp: BitVec2048,
pub combined_fp: BitVec2048,
}
impl ReactionFingerprint {
pub fn tanimoto(&self, other: &ReactionFingerprint) -> f64 {
self.combined_fp.tanimoto(&other.combined_fp)
}
}
fn combine_fps_or(fps: &[BitVec2048]) -> BitVec2048 {
if fps.is_empty() {
return BitVec2048::new();
}
let mut result = fps[0].clone();
for fp in &fps[1..] {
result = result.or(fp);
}
result
}
fn compute_structural_difference(reactant_fp: &BitVec2048, product_fp: &BitVec2048) -> BitVec2048 {
reactant_fp.or(product_fp)
}
pub fn reaction_fp(rxn: &chematic_rxn::Reaction) -> ReactionFingerprint {
reaction_fp_with_config(rxn, &ReactionFpConfig::default())
}
pub fn reaction_fp_with_config(
rxn: &chematic_rxn::Reaction,
_config: &ReactionFpConfig,
) -> ReactionFingerprint {
let mut reactant_fps = Vec::new();
for mol in &rxn.reactants {
let fp = ecfp4(mol);
reactant_fps.push(fp);
}
let reactant_fp = combine_fps_or(&reactant_fps);
let mut product_fps = Vec::new();
for mol in &rxn.products {
let fp = ecfp4(mol);
product_fps.push(fp);
}
let product_fp = combine_fps_or(&product_fps);
let combined_fp = compute_structural_difference(&reactant_fp, &product_fp);
ReactionFingerprint {
reactant_fp,
product_fp,
combined_fp,
}
}
pub fn reaction_fp_ecfp4(rxn: &chematic_rxn::Reaction) -> ReactionFingerprint {
reaction_fp(rxn)
}
pub fn tanimoto_reaction_fp(rxn1: &chematic_rxn::Reaction, rxn2: &chematic_rxn::Reaction) -> f64 {
let fp1 = reaction_fp(rxn1);
let fp2 = reaction_fp(rxn2);
fp1.tanimoto(&fp2)
}
#[cfg(test)]
mod tests {
use super::*;
fn create_test_reaction(smiles: &str) -> chematic_rxn::Reaction {
chematic_rxn::reaction::parse_reaction(smiles).unwrap()
}
#[test]
fn test_reaction_fp_simple() {
let rxn = create_test_reaction("CC>>C");
let fp = reaction_fp(&rxn);
assert!(fp.combined_fp.popcount() > 0);
assert!(fp.reactant_fp.popcount() > 0);
assert!(fp.product_fp.popcount() > 0);
}
#[test]
fn test_reaction_fp_identical() {
let rxn = create_test_reaction("CC>>C");
let fp1 = reaction_fp(&rxn);
let fp2 = reaction_fp(&rxn);
assert!((fp1.tanimoto(&fp2) - 1.0).abs() < 1e-6);
}
#[test]
fn test_reaction_fp_different_products() {
let rxn1 = create_test_reaction("CC>>C");
let rxn2 = create_test_reaction("CC>>CC");
let fp1 = reaction_fp(&rxn1);
let fp2 = reaction_fp(&rxn2);
let similarity = fp1.tanimoto(&fp2);
assert!(similarity < 1.0);
assert!(similarity > 0.0);
}
#[test]
fn test_reaction_fp_different_reactants() {
let rxn1 = create_test_reaction("C>>CC");
let rxn2 = create_test_reaction("CC>>CCC");
let fp1 = reaction_fp(&rxn1);
let fp2 = reaction_fp(&rxn2);
let similarity = fp1.tanimoto(&fp2);
assert!(similarity < 1.0);
}
#[test]
fn test_reaction_fp_multi_molecule() {
let rxn = create_test_reaction("C.C>>CC");
let fp = reaction_fp(&rxn);
assert!(fp.combined_fp.popcount() > 0);
}
#[test]
fn test_reaction_tanimoto_symmetry() {
let rxn1 = create_test_reaction("CC>>C");
let rxn2 = create_test_reaction("CCC>>CC");
let sim12 = tanimoto_reaction_fp(&rxn1, &rxn2);
let sim21 = tanimoto_reaction_fp(&rxn2, &rxn1);
assert!((sim12 - sim21).abs() < 1e-6);
}
#[test]
fn test_reaction_fp_bounds() {
let rxn1 = create_test_reaction("CC>>C");
let rxn2 = create_test_reaction("CCCC>>CCC");
let fp1 = reaction_fp(&rxn1);
let fp2 = reaction_fp(&rxn2);
let similarity = fp1.tanimoto(&fp2);
assert!(similarity >= 0.0 && similarity <= 1.0);
}
#[test]
fn test_reaction_fp_config() {
let rxn = create_test_reaction("CC>>C");
let config = ReactionFpConfig {
use_xor: true,
};
let fp = reaction_fp_with_config(&rxn, &config);
assert!(fp.combined_fp.popcount() > 0);
}
#[test]
fn test_reaction_fp_structural_difference() {
let rxn = create_test_reaction("C.C>>CC");
let fp = reaction_fp(&rxn);
assert!(fp.combined_fp.popcount() > 0);
}
#[test]
fn test_reaction_fp_transformation_vs_composition() {
let rxn1 = create_test_reaction("CC>>C"); let rxn2 = create_test_reaction("C>>CC");
let fp1 = reaction_fp(&rxn1);
let fp2 = reaction_fp(&rxn2);
let similarity = fp1.tanimoto(&fp2);
assert!(similarity >= 0.0 && similarity <= 1.0);
}
}