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_chem::brics_fragments;
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")), 6);
}
#[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 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 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.25,
);
}
#[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.03,
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()
);
}