use chematic::core::Molecule;
use chematic::perception::stereo_validation::stereo_completeness;
use crate::components::has_negatively_charged_atom;
use crate::report::{
ComponentScore, Contribution, Finding, FindingCode, FindingEvidence, FindingRef,
ProbabilityLikeScore, Severity, finite_or_zero,
};
use crate::rules::{
CONFIDENCE_PENALTY_STEREO_UNCHECKABLE, STEREO_BURDEN_SCALE, STEREO_DENSITY_FINDING_THRESHOLD,
STEREO_WEIGHT_DENSITY, STEREO_WEIGHT_PER_CENTER,
};
pub(crate) struct StereochemicalBurdenOutcome {
pub(crate) score: ComponentScore,
pub(crate) findings: Vec<Finding>,
pub(crate) contributions: Vec<Contribution>,
}
pub(crate) fn compute(mol: &Molecule) -> StereochemicalBurdenOutcome {
let mut findings = Vec::new();
let mut contributions = Vec::new();
let uncheckable = has_negatively_charged_atom(mol);
let total_centers = if uncheckable {
push(
&mut findings,
&mut contributions,
FindingCode::StereoAnalysisSkipped,
Severity::Medium,
FindingEvidence::default(),
0.0,
);
0
} else {
stereo_completeness(mol).total_centers
};
let atom_count = mol.atom_count();
let density = if atom_count == 0 {
0.0
} else {
total_centers as f64 / atom_count as f64
};
let count_weight = finite_or_zero(STEREO_WEIGHT_PER_CENTER * total_centers as f64);
let density_weight = finite_or_zero(STEREO_WEIGHT_DENSITY * density);
if total_centers > 0 {
push(
&mut findings,
&mut contributions,
FindingCode::StereoCenterCount,
Severity::Low,
FindingEvidence {
value: Some(total_centers as f64),
threshold: None,
},
count_weight,
);
}
if density > STEREO_DENSITY_FINDING_THRESHOLD {
push(
&mut findings,
&mut contributions,
FindingCode::StereoDensityHigh,
Severity::Medium,
FindingEvidence {
value: Some(density),
threshold: Some(STEREO_DENSITY_FINDING_THRESHOLD),
},
density_weight,
);
}
let raw = finite_or_zero(count_weight + density_weight);
let normalized = ProbabilityLikeScore::new(1.0 - (-raw / STEREO_BURDEN_SCALE).exp());
let confidence = if uncheckable {
CONFIDENCE_PENALTY_STEREO_UNCHECKABLE
} else {
1.0
};
let score = ComponentScore {
raw,
normalized,
confidence: ProbabilityLikeScore::new(confidence),
contribution: normalized.value(),
findings: (0..findings.len()).map(FindingRef).collect(),
};
StereochemicalBurdenOutcome {
score,
findings,
contributions,
}
}
fn push(
findings: &mut Vec<Finding>,
contributions: &mut Vec<Contribution>,
code: FindingCode,
severity: Severity,
evidence: FindingEvidence,
weight: f64,
) {
let explanation = crate::explain::render(code, evidence, 0, None);
findings.push(Finding {
code,
severity,
confidence: ProbabilityLikeScore::new(1.0),
atoms: Vec::new(),
evidence,
explanation: explanation.clone(),
});
contributions.push(Contribution {
code,
name: explanation,
contribution: ProbabilityLikeScore::new(finite_or_zero(weight)),
});
}