use chematic::chem::{molecular_weight, sa_score};
use crate::chem_env::Molecule;
pub fn heuristic(unsolved_mols: &[&Molecule]) -> f64 {
unsolved_mols
.iter()
.map(|m| {
let sa = sa_score(m).clamp(1.0, 10.0);
1.0 + 0.5 * (sa - 1.0) / 9.0 })
.sum()
}
pub fn step_cost(precursors: &[&Molecule]) -> f64 {
let total_mw: f64 = precursors.iter().map(|m| molecular_weight(m)).sum();
1.0 + (total_mw / 2000.0).min(1.0)
}
pub fn template_bonus(weight: f64, max_weight: f64) -> f64 {
if max_weight <= 1.0 {
return 0.0;
}
0.2 * (weight - 1.0) / (max_weight - 1.0)
}
#[cfg(test)]
mod tests {
use super::*;
use chematic::smiles::parse;
#[test]
fn template_bonus_zero_when_all_weight_one() {
assert_eq!(template_bonus(1.0, 1.0), 0.0);
}
#[test]
fn template_bonus_range() {
let max_w = (1294_f64).ln();
let bonus_min = template_bonus(1.0, max_w); let bonus_max = template_bonus(max_w, max_w);
assert!(bonus_min >= 0.0);
assert!(
(bonus_max - 0.2).abs() < 1e-10,
"max bonus must be 0.2, got {bonus_max}"
);
}
fn mol(smi: &str) -> Molecule {
parse(smi).expect("valid SMILES")
}
#[test]
fn heuristic_empty_is_zero() {
assert_eq!(heuristic(&[]), 0.0);
}
#[test]
fn heuristic_single_simple_mol_in_range() {
let m = mol("C"); let h = heuristic(&[&m]);
assert!((1.0..=1.5).contains(&h), "h={h} out of [1.0, 1.5]");
}
#[test]
fn step_cost_single_small_mol() {
let m = mol("CC(=O)O"); let cost = step_cost(&[&m]);
assert!(cost > 1.0 && cost < 1.1, "step_cost={cost}");
}
#[test]
fn step_cost_heavy_mol_capped_at_two() {
let m = mol("CC(=O)Oc1ccccc1C(=O)O"); let cost = step_cost(&[&m]);
assert!(cost > 1.0 && cost <= 2.0, "step_cost={cost}");
}
}