use chematic_chem::brics_fragments;
use chematic_chem::descriptors::{
aromatic_ring_count, exact_mass, formal_charge_sum, fsp3, hba_count, hbd_count,
heavy_atom_count, lipinski_passes, logp_crippen, molar_refractivity, molecular_weight, mqn,
num_aliphatic_heterocycles, num_aromatic_heterocycles, num_bridgehead_atoms,
num_saturated_heterocycles, num_spiro_atoms, rotatable_bond_count, tpsa,
};
use chematic_smiles::parse;
fn mol(smi: &str) -> chematic_core::molecule::Molecule {
parse(smi).unwrap_or_else(|e| panic!("parse({smi:?}) failed: {e}"))
}
fn assert_approx(label: &str, got: f64, expected: f64, tol: f64) {
assert!(
(got - expected).abs() <= tol,
"{label}: got {got:.4}, expected {expected:.4} ±{tol}"
);
}
#[test]
fn mw_aspirin() {
assert_approx(
"MW aspirin",
molecular_weight(&mol("CC(=O)Oc1ccccc1C(=O)O")),
180.159,
0.01,
);
}
#[test]
fn mw_caffeine() {
assert_approx(
"MW caffeine",
molecular_weight(&mol("Cn1cnc2c1c(=O)n(c(=O)n2C)C")),
194.194,
0.01,
);
}
#[test]
fn mw_indole() {
assert_approx(
"MW indole",
molecular_weight(&mol("c1ccc2[nH]ccc2c1")),
117.151,
0.01,
);
}
#[test]
fn mw_glucose() {
assert_approx(
"MW glucose",
molecular_weight(&mol("OC[C@H]1O[C@@H](O)[C@H](O)[C@@H](O)[C@@H]1O")),
180.156,
0.01,
);
}
#[test]
fn mw_lidocaine() {
assert_approx(
"MW lidocaine",
molecular_weight(&mol("CCN(CC)CC(=O)Nc1c(C)cccc1C")),
234.338,
0.01,
);
}
#[test]
fn mw_warfarin() {
assert_approx(
"MW warfarin",
molecular_weight(&mol("CC(=O)CC(c1ccccc1)C1C(=O)c2ccccc2OC1=O")),
308.333,
0.01,
);
}
#[test]
fn mw_naproxen() {
assert_approx(
"MW naproxen",
molecular_weight(&mol("CC(C(=O)O)c1ccc2cc(OC)ccc2c1")),
230.259,
0.01,
);
}
#[test]
fn mw_methotrexate() {
assert_approx(
"MW methotrexate",
molecular_weight(&mol(
"CN(Cc1cnc2nc(N)nc(N)c2n1)c1ccc(cc1)C(=O)NC(CCC(=O)O)C(=O)O",
)),
454.447,
0.01,
);
}
#[test]
fn mw_paracetamol() {
assert_approx(
"MW paracetamol",
molecular_weight(&mol("CC(=O)Nc1ccc(O)cc1")),
151.163,
0.01,
);
}
#[test]
fn mw_ibuprofen() {
assert_approx(
"MW ibuprofen",
molecular_weight(&mol("CC(C)Cc1ccc(cc1)C(C)C(=O)O")),
206.285,
0.01,
);
}
#[test]
fn mw_glycine() {
assert_approx(
"MW glycine",
molecular_weight(&mol("NCC(=O)O")),
75.067,
0.01,
);
}
#[test]
fn mw_benzene() {
assert_approx(
"MW benzene",
molecular_weight(&mol("c1ccccc1")),
78.114,
0.01,
);
}
#[test]
fn mw_naphthalene() {
assert_approx(
"MW naphthalene",
molecular_weight(&mol("c1ccc2ccccc2c1")),
128.174,
0.01,
);
}
#[test]
fn tpsa_aspirin() {
assert_approx(
"TPSA aspirin",
tpsa(&mol("CC(=O)Oc1ccccc1C(=O)O")),
63.60,
1.0,
);
}
#[test]
fn tpsa_caffeine() {
assert_approx(
"TPSA caffeine",
tpsa(&mol("Cn1cnc2c1c(=O)n(c(=O)n2C)C")),
61.82,
1.0,
);
}
#[test]
fn tpsa_paracetamol() {
assert_approx(
"TPSA paracetamol",
tpsa(&mol("CC(=O)Nc1ccc(O)cc1")),
49.33,
5.0,
);
}
#[test]
fn tpsa_lidocaine() {
assert_approx(
"TPSA lidocaine",
tpsa(&mol("CCN(CC)CC(=O)Nc1c(C)cccc1C")),
32.34,
10.0,
);
}
#[test]
fn tpsa_glucose() {
assert_approx(
"TPSA glucose",
tpsa(&mol("OC[C@H]1O[C@@H](O)[C@H](O)[C@@H](O)[C@@H]1O")),
110.38,
1.0,
);
}
#[test]
fn tpsa_pyridine() {
assert_approx("TPSA pyridine", tpsa(&mol("c1ccncc1")), 12.89, 0.1);
}
#[test]
fn tpsa_aniline() {
assert_approx("TPSA aniline", tpsa(&mol("Nc1ccccc1")), 26.02, 0.1);
}
#[test]
fn tpsa_benzene() {
assert_approx("TPSA benzene", tpsa(&mol("c1ccccc1")), 0.00, 0.01);
}
#[test]
fn tpsa_thiophene() {
assert_approx("TPSA thiophene", tpsa(&mol("c1ccsc1")), 28.24, 0.1);
}
#[test]
fn tpsa_thiazole() {
assert_approx("TPSA thiazole", tpsa(&mol("c1csc(n1)")), 41.13, 0.1);
}
#[test]
fn tpsa_dimethyl_sulfoxide() {
assert_approx("TPSA DMSO", tpsa(&mol("CS(C)=O")), 36.28, 0.1);
}
#[test]
fn tpsa_dimethyl_sulfone() {
assert_approx(
"TPSA dimethylsulfone",
tpsa(&mol("CS(C)(=O)=O")),
42.52,
0.1,
);
}
#[test]
fn tpsa_methanesulfonic_acid() {
assert_approx(
"TPSA methanesulfonic acid",
tpsa(&mol("CS(=O)(=O)O")),
62.75,
0.1,
);
}
#[test]
fn tpsa_furan() {
assert_approx("TPSA furan", tpsa(&mol("c1ccco1")), 13.14, 0.1);
}
#[test]
fn tpsa_indole() {
assert_approx("TPSA indole", tpsa(&mol("c1ccc2[nH]ccc2c1")), 15.79, 0.1);
}
#[test]
fn tpsa_methotrexate() {
assert_approx(
"TPSA methotrexate",
tpsa(&mol(
"CN(Cc1cnc2nc(N)nc(N)c2n1)c1ccc(cc1)C(=O)NC(CCC(=O)O)C(=O)O",
)),
210.54,
2.0,
);
}
#[test]
fn tpsa_aspirin_no_ester_overcounting() {
let t = tpsa(&mol("CC(=O)Oc1ccccc1C(=O)O"));
assert_approx("TPSA aspirin (ester O)", t, 63.60, 0.1);
}
#[test]
fn hac_ethanol() {
assert_eq!(heavy_atom_count(&mol("CCO")), 3);
}
#[test]
fn hac_benzene() {
assert_eq!(heavy_atom_count(&mol("c1ccccc1")), 6);
}
#[test]
fn hac_aspirin() {
assert_eq!(heavy_atom_count(&mol("CC(=O)Oc1ccccc1C(=O)O")), 13);
}
#[test]
fn hac_caffeine() {
assert_eq!(heavy_atom_count(&mol("Cn1cnc2c1c(=O)n(c(=O)n2C)C")), 14);
}
#[test]
fn hbd_aspirin() {
assert_eq!(hbd_count(&mol("CC(=O)Oc1ccccc1C(=O)O")), 1);
}
#[test]
fn hbd_glucose() {
assert_eq!(
hbd_count(&mol("OC[C@H]1O[C@@H](O)[C@H](O)[C@@H](O)[C@@H]1O")),
5
);
}
#[test]
fn hbd_caffeine() {
assert_eq!(hbd_count(&mol("Cn1cnc2c1c(=O)n(c(=O)n2C)C")), 0);
}
#[test]
fn hbd_paracetamol() {
assert_eq!(hbd_count(&mol("CC(=O)Nc1ccc(O)cc1")), 2);
}
#[test]
fn lipinski_aspirin_passes() {
assert!(lipinski_passes(&mol("CC(=O)Oc1ccccc1C(=O)O")));
}
#[test]
fn lipinski_caffeine_passes() {
assert!(lipinski_passes(&mol("Cn1cnc2c1c(=O)n(c(=O)n2C)C")));
}
#[test]
fn lipinski_cyclosporine_fails() {
assert!(!lipinski_passes(&mol(
"CCC1NC(=O)C(C(=O)N(C)C(CC(C)C)C(=O)NC(CC(C)C)C(=O)N(C)C(CC(C)C)C(=O)NC(C(C)C)C(=O)N(C)C1CC(C)C)"
)));
}
#[test]
fn hba_aspirin() {
assert_eq!(hba_count(&mol("CC(=O)Oc1ccccc1C(=O)O")), 3);
}
#[test]
fn hba_paracetamol() {
assert_eq!(hba_count(&mol("CC(=O)Nc1ccc(O)cc1")), 2);
}
#[test]
fn hba_caffeine() {
assert_eq!(hba_count(&mol("Cn1cnc2c1c(=O)n(c(=O)n2C)C")), 3);
}
#[test]
fn hba_indole() {
assert_eq!(hba_count(&mol("c1ccc2[nH]ccc2c1")), 0);
}
#[test]
fn hba_pyridine() {
assert_eq!(hba_count(&mol("c1ccncc1")), 1);
}
#[test]
fn hba_urea() {
assert_eq!(hba_count(&mol("NC(N)=O")), 1);
}
#[test]
fn hba_acetic_acid() {
assert_eq!(hba_count(&mol("CC(=O)O")), 1);
}
#[test]
fn hba_water_zero() {
assert_eq!(hba_count(&mol("O")), 0);
}
#[test]
fn hba_thioamide_n_excluded() {
assert_eq!(hba_count(&mol("NC(=S)N")), 1);
}
#[test]
fn hba_sulfonamide_n_excluded() {
assert_eq!(hba_count(&mol("CS(=O)(=O)N")), 2);
}
#[test]
fn hba_ring_guanidine_counts_all_n() {
assert_eq!(hba_count(&mol("NC(=N)N")), 1); assert_eq!(hba_count(&mol("NC1=NCCN1")), 3); }
#[test]
fn hba_nitroso_n_is_hba() {
assert_eq!(hba_count(&mol("c1ccc(N=O)cc1")), 2);
}
#[test]
fn hba_carbon_disulfide() {
assert_eq!(hba_count(&mol("S=C=S")), 2);
}
#[test]
fn hba_radical_n_excluded() {
assert_eq!(hba_count(&mol("C[N]C")), 0);
}
#[test]
fn logp_benzene() {
assert_approx("LogP benzene", logp_crippen(&mol("c1ccccc1")), 1.6866, 0.02);
}
#[test]
fn logp_pyridine() {
assert_approx(
"LogP pyridine",
logp_crippen(&mol("c1ccncc1")),
1.0816,
0.02,
);
}
#[test]
fn logp_thiophene() {
assert_approx(
"LogP thiophene",
logp_crippen(&mol("c1ccsc1")),
1.7481,
0.02,
);
}
#[test]
fn logp_methanol() {
assert_approx("LogP methanol", logp_crippen(&mol("CO")), -0.3915, 0.02);
}
#[test]
fn logp_ethanol() {
assert_approx("LogP ethanol", logp_crippen(&mol("CCO")), -0.0014, 0.02);
}
#[test]
fn logp_acetone() {
assert_approx("LogP acetone", logp_crippen(&mol("CC(C)=O")), 0.5953, 0.02);
}
#[test]
fn logp_acetic_acid() {
assert_approx(
"LogP acetic acid",
logp_crippen(&mol("CC(=O)O")),
0.0909,
0.05,
);
}
#[test]
fn logp_aspirin() {
assert_approx(
"LogP aspirin",
logp_crippen(&mol("CC(=O)Oc1ccccc1C(=O)O")),
1.3101,
0.35,
);
}
#[test]
fn logp_caffeine() {
assert_approx(
"LogP caffeine",
logp_crippen(&mol("Cn1cnc2c1c(=O)n(c(=O)n2C)C")),
-1.0293,
0.20,
);
}
#[test]
fn logp_pyrimidine() {
assert_approx(
"LogP pyrimidine",
logp_crippen(&mol("c1ccncn1")),
0.4766,
0.02,
);
}
#[test]
fn logp_tetrahydrofuran() {
assert_approx("LogP THF", logp_crippen(&mol("C1CCOC1")), 0.7968, 0.02);
}
#[test]
fn logp_dimethyl_sulfide() {
assert_approx(
"LogP dimethyl sulfide",
logp_crippen(&mol("CSC")),
0.9792,
0.02,
);
}
#[test]
fn logp_chlorobenzene() {
assert_approx(
"LogP chlorobenzene",
logp_crippen(&mol("Clc1ccccc1")),
2.3400,
0.02,
);
}
#[test]
fn logp_dichloromethane() {
assert_approx("LogP DCM", logp_crippen(&mol("ClCCl")), 1.4215, 0.02);
}
#[test]
fn logp_phenol() {
assert_approx(
"LogP phenol",
logp_crippen(&mol("c1ccccc1O")),
1.3922,
0.005,
);
}
#[test]
fn logp_catechol() {
assert_approx(
"LogP catechol",
logp_crippen(&mol("Oc1ccccc1O")),
1.0978,
0.005,
);
}
#[test]
fn logp_salicylic_acid() {
assert_approx(
"LogP salicylic acid",
logp_crippen(&mol("OC(=O)c1ccccc1O")),
1.0904,
0.01,
);
}
#[test]
fn logp_toluene() {
assert_approx(
"LogP toluene",
logp_crippen(&mol("Cc1ccccc1")),
1.9950,
0.01,
);
}
#[test]
fn logp_ethylbenzene() {
assert_approx(
"LogP ethylbenzene",
logp_crippen(&mol("CCc1ccccc1")),
2.2490,
0.01,
);
}
#[test]
fn logp_aniline() {
assert_approx(
"LogP aniline",
logp_crippen(&mol("Nc1ccccc1")),
1.2688,
0.01,
);
}
#[test]
fn logp_thiophenol() {
assert_approx(
"LogP thiophenol",
logp_crippen(&mol("Sc1ccccc1")),
1.9753,
0.02,
);
}
#[test]
fn tpsa_metformin() {
assert_approx(
"TPSA metformin",
tpsa(&mol("CN(C)C(=N)NC(=N)N")),
88.99,
0.5,
);
}
#[test]
fn tpsa_arginine() {
assert_approx(
"TPSA arginine",
tpsa(&mol("N[C@@H](CCCNC(=N)N)C(=O)O")),
125.22,
0.5,
);
}
#[test]
fn tpsa_trimethyl_phosphate() {
assert_approx(
"TPSA trimethyl phosphate",
tpsa(&mol("COP(=O)(OC)OC")),
54.57,
0.5,
);
}
#[test]
fn tpsa_nitrobenzene() {
assert_approx(
"TPSA nitrobenzene",
tpsa(&mol("c1ccc([N+](=O)[O-])cc1")),
43.14,
0.1,
);
}
#[test]
fn tpsa_4_nitrophenol() {
assert_approx(
"TPSA 4-nitrophenol",
tpsa(&mol("Oc1ccc([N+](=O)[O-])cc1")),
63.37,
0.1,
);
}
#[test]
fn logp_trimethyl_phosphate() {
assert_approx(
"LogP trimethyl phosphate",
logp_crippen(&mol("COP(=O)(OC)OC")),
1.0337,
0.15,
);
}
#[test]
fn ring_descs_pyridine() {
let m = mol("c1ccncc1");
assert_eq!(
num_aromatic_heterocycles(&m),
1,
"pyridine aromatic hetero rings"
);
assert_eq!(
num_aliphatic_heterocycles(&m),
0,
"pyridine aliphatic hetero rings"
);
assert_eq!(
num_saturated_heterocycles(&m),
0,
"pyridine saturated hetero rings"
);
assert_eq!(num_spiro_atoms(&m), 0, "pyridine spiro");
assert_eq!(num_bridgehead_atoms(&m), 0, "pyridine bridgehead");
}
#[test]
fn ring_descs_benzene() {
let m = mol("c1ccccc1");
assert_eq!(
num_aromatic_heterocycles(&m),
0,
"benzene has no hetero ring"
);
assert_eq!(num_spiro_atoms(&m), 0);
assert_eq!(num_bridgehead_atoms(&m), 0);
}
#[test]
fn ring_descs_piperidine() {
let m = mol("C1CCNCC1");
assert_eq!(num_aliphatic_heterocycles(&m), 1, "piperidine aliphatic");
assert_eq!(num_saturated_heterocycles(&m), 1, "piperidine saturated");
assert_eq!(num_aromatic_heterocycles(&m), 0, "piperidine not aromatic");
}
#[test]
fn ring_descs_morpholine() {
let m = mol("C1COCCN1");
assert_eq!(num_aliphatic_heterocycles(&m), 1, "morpholine aliphatic");
assert_eq!(num_saturated_heterocycles(&m), 1, "morpholine saturated");
}
#[test]
fn ring_descs_spiro_decane() {
let m = mol("C1CCCCC11CCCC1");
assert_eq!(num_spiro_atoms(&m), 1, "spiro[4.5]decane has 1 spiro atom");
assert_eq!(num_bridgehead_atoms(&m), 0, "spiro is not bridgehead");
}
#[test]
fn ring_descs_norbornane() {
let m = mol("C1CC2CCC1C2");
assert_eq!(num_bridgehead_atoms(&m), 2, "norbornane has 2 bridgeheads");
assert_eq!(num_spiro_atoms(&m), 0, "norbornane has no spiro atoms");
}
#[test]
fn ring_descs_naphthalene_no_spiro() {
let m = mol("c1ccc2ccccc2c1");
assert_eq!(num_spiro_atoms(&m), 0, "naphthalene has no spiro atoms");
assert_eq!(num_bridgehead_atoms(&m), 0, "naphthalene bridgehead");
}
#[test]
fn logp_metformin() {
assert_approx(
"LogP metformin",
logp_crippen(&mol("CN(C)C(=N)NC(=N)N")),
-1.034,
0.05,
);
}
#[test]
fn logp_arginine_guanidinium() {
assert_approx(
"LogP arginine",
logp_crippen(&mol("N[C@@H](CCCNC(=N)N)C(=O)O")),
-1.01,
0.40,
);
}
#[test]
fn exact_mass_aspirin() {
assert_approx(
"exact_mass aspirin",
exact_mass(&mol("CC(=O)Oc1ccccc1C(=O)O")),
180.042259,
0.001,
);
}
#[test]
fn fsp3_benzene() {
assert_approx("fsp3 benzene", fsp3(&mol("c1ccccc1")), 0.0, 0.01);
}
#[test]
fn fsp3_cyclohexane() {
assert_approx("fsp3 cyclohexane", fsp3(&mol("C1CCCCC1")), 1.0, 0.01);
}
#[test]
fn molar_refractivity_aspirin() {
assert_approx(
"molar_refractivity aspirin",
molar_refractivity(&mol("CC(=O)Oc1ccccc1C(=O)O")),
44.71,
0.1,
);
}
#[test]
fn rotatable_bond_count_ethanol() {
assert_approx(
"rotatable_bond_count ethanol",
rotatable_bond_count(&mol("CCO")) as f64,
0.0,
0.1,
);
}
#[test]
fn rotatable_bond_count_aspirin() {
assert_approx(
"rotatable_bond_count aspirin",
rotatable_bond_count(&mol("CC(=O)Oc1ccccc1C(=O)O")) as f64,
3.0,
0.1,
);
}
#[test]
fn aromatic_ring_count_benzene() {
assert_approx(
"aromatic_ring_count benzene",
aromatic_ring_count(&mol("c1ccccc1")) as f64,
1.0,
0.1,
);
}
#[test]
fn aromatic_ring_count_naphthalene() {
assert_approx(
"aromatic_ring_count naphthalene",
aromatic_ring_count(&mol("c1ccc2ccccc2c1")) as f64,
2.0,
0.1,
);
}
#[test]
fn formal_charge_sum_aspirin() {
assert_approx(
"formal_charge_sum aspirin",
formal_charge_sum(&mol("CC(=O)Oc1ccccc1C(=O)O")) as f64,
0.0,
0.1,
);
}
#[test]
fn formal_charge_sum_chloride() {
assert_approx(
"formal_charge_sum chloride",
formal_charge_sum(&mol("[Cl-]")) as f64,
-1.0,
0.1,
);
}
#[test]
fn mqn_benzene() {
let m = &mol("c1ccccc1");
let result = mqn(m);
assert_eq!(result.len(), 42, "MQN must return 42 descriptors");
}
#[test]
fn mqn_aspirin() {
let m = &mol("CC(=O)Oc1ccccc1C(=O)O");
let result = mqn(m);
assert_eq!(result.len(), 42, "MQN must return 42 descriptors");
}
#[test]
fn mqn_alanine() {
let m = &mol("CC(N)C(=O)O");
let result = mqn(m);
assert_eq!(result.len(), 42, "MQN must return 42 descriptors");
}
#[test]
fn mqn_caffeine() {
let m = &mol("Cn1cnc2c1c(=O)n(c(=O)n2C)C");
let result = mqn(m);
assert_eq!(result.len(), 42, "MQN must return 42 descriptors");
}
#[test]
fn brics_fragments_aspirin() {
let frags = brics_fragments(&mol("CC(=O)Oc1ccccc1C(=O)O"));
assert!(
frags.len() >= 2,
"aspirin BRICS should yield ≥2 fragments, got {}",
frags.len()
);
for (i, frag) in frags.iter().enumerate() {
assert!(frag.atom_count() > 0, "fragment {} has zero atoms", i);
}
}
#[test]
fn brics_fragments_acetanilide() {
let frags = brics_fragments(&mol("CC(=O)Nc1ccccc1"));
assert!(
frags.len() >= 2,
"acetanilide BRICS should yield ≥2 fragments, got {}",
frags.len()
);
}
#[test]
fn brics_fragments_ibuprofen() {
let frags = brics_fragments(&mol("CC(C)Cc1ccc(cc1)C(C)C(=O)O"));
assert!(
frags.len() >= 2,
"ibuprofen BRICS should yield ≥2 fragments, got {}",
frags.len()
);
}
#[test]
fn tpsa_all_tsv_reference() {
use std::path::Path;
let tsv = Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.unwrap()
.parent()
.unwrap()
.join("scripts/rdkit_ref_properties.tsv");
let content = std::fs::read_to_string(&tsv)
.unwrap_or_else(|e| panic!("cannot read {}: {}", tsv.display(), e));
const TOL: f64 = 1.0;
let mut failures: Vec<String> = Vec::new();
for line in content.lines().skip(1) {
let cols: Vec<&str> = line.split('\t').collect();
if cols.len() < 5 {
continue;
}
let (name, smi, expected_str) = (cols[0], cols[1], cols[4]);
let expected: f64 = expected_str.parse().unwrap_or(f64::NAN);
let m = match parse(smi) {
Ok(m) => m,
Err(_) => {
failures.push(format!(" {name}: SMILES parse failed ({smi})"));
continue;
}
};
let got = tpsa(&m);
if (got - expected).abs() > TOL {
failures.push(format!(
" {name}: expected={expected:.2} got={got:.2} delta={:+.2} {smi}",
got - expected
));
}
}
assert!(
failures.is_empty(),
"tpsa_all_tsv_reference: {} failure(s) (tol ±{TOL} Ų):\n{}",
failures.len(),
failures.join("\n")
);
}
#[test]
fn logp_all_tsv_reference() {
use std::path::Path;
let tsv = Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.unwrap()
.parent()
.unwrap()
.join("scripts/rdkit_ref_properties.tsv");
let content = std::fs::read_to_string(&tsv)
.unwrap_or_else(|e| panic!("cannot read {}: {}", tsv.display(), e));
const TOL: f64 = 0.3;
let mut failures: Vec<String> = Vec::new();
for line in content.lines().skip(1) {
let cols: Vec<&str> = line.split('\t').collect();
if cols.len() < 4 {
continue;
}
let (name, smi, expected_str) = (cols[0], cols[1], cols[3]);
let expected: f64 = expected_str.parse().unwrap_or(f64::NAN);
let m = match parse(smi) {
Ok(m) => m,
Err(_) => {
failures.push(format!(" {name}: SMILES parse failed ({smi})"));
continue;
}
};
let got = logp_crippen(&m);
if (got - expected).abs() > TOL {
failures.push(format!(
" {name}: expected={expected:.4} got={got:.4} delta={:+.4} {smi}",
got - expected
));
}
}
assert!(
failures.is_empty(),
"logp_all_tsv_reference: {} failure(s) (tol ±{TOL}):\n{}",
failures.len(),
failures.join("\n")
);
}
fn read_tsv() -> String {
let tsv = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.unwrap()
.parent()
.unwrap()
.join("scripts/rdkit_ref_properties.tsv");
std::fs::read_to_string(&tsv)
.unwrap_or_else(|e| panic!("cannot read {}: {}", tsv.display(), e))
}
#[test]
fn mw_all_tsv_reference() {
const TOL: f64 = 0.02;
let mut failures: Vec<String> = Vec::new();
for line in read_tsv().lines().skip(1) {
let cols: Vec<&str> = line.split('\t').collect();
if cols.len() < 3 { continue; }
let (name, smi, expected_str) = (cols[0], cols[1], cols[2]);
let expected: f64 = expected_str.parse().unwrap_or(f64::NAN);
let m = match parse(smi) {
Ok(m) => m,
Err(_) => { failures.push(format!(" {name}: parse failed")); continue; }
};
let got = molecular_weight(&m);
if (got - expected).abs() > TOL {
failures.push(format!(
" {name}: expected={expected:.4} got={got:.4} delta={:+.4} {smi}",
got - expected
));
}
}
assert!(
failures.is_empty(),
"mw_all_tsv_reference: {} failure(s) (tol ±{TOL} Da):\n{}",
failures.len(), failures.join("\n")
);
}
#[test]
fn hac_all_tsv_reference() {
let mut failures: Vec<String> = Vec::new();
for line in read_tsv().lines().skip(1) {
let cols: Vec<&str> = line.split('\t').collect();
if cols.len() < 6 { continue; }
let (name, smi, expected_str) = (cols[0], cols[1], cols[5]);
let expected: usize = expected_str.parse().unwrap_or(0);
let m = match parse(smi) {
Ok(m) => m,
Err(_) => { failures.push(format!(" {name}: parse failed")); continue; }
};
let got = heavy_atom_count(&m);
if got != expected {
failures.push(format!(
" {name}: expected={expected} got={got} {smi}"
));
}
}
assert!(
failures.is_empty(),
"hac_all_tsv_reference: {} failure(s):\n{}",
failures.len(), failures.join("\n")
);
}
#[test]
fn hbd_all_tsv_reference() {
let mut failures: Vec<String> = Vec::new();
for line in read_tsv().lines().skip(1) {
let cols: Vec<&str> = line.split('\t').collect();
if cols.len() < 7 { continue; }
let (name, smi, expected_str) = (cols[0], cols[1], cols[6]);
let expected: usize = expected_str.parse().unwrap_or(0);
let m = match parse(smi) {
Ok(m) => m,
Err(_) => { failures.push(format!(" {name}: parse failed")); continue; }
};
let got = hbd_count(&m);
if got != expected {
failures.push(format!(
" {name}: expected={expected} got={got} {smi}"
));
}
}
assert!(
failures.is_empty(),
"hbd_all_tsv_reference: {} failure(s):\n{}",
failures.len(), failures.join("\n")
);
}