use super::*;
use crate::config::FeaturesConfig;
use crate::scoring::model::PEPTIDE;
#[test]
fn peptide_counts_scale_linearly() {
let (c, h, n, o, s) = PEPTIDE.counts(1500.0);
let scale = 1500.0 / PEPTIDE.residue_mass;
assert!((c as f64 - 4.9384 * scale).abs() <= 1.0);
assert!((h as f64 - 7.7583 * scale).abs() <= 1.0);
assert!((n as f64 - 1.3577 * scale).abs() <= 1.0);
assert!((o as f64 - 1.4773 * scale).abs() <= 1.0);
assert!((s as f64 - 0.0417 * scale).abs() <= 1.0);
}
#[test]
fn peptide_distribution_sums_to_one() {
for mass in [500.0, 1500.0, 3000.0, 5000.0] {
let d = PEPTIDE.distribution(mass);
let sum: f64 = d.iter().sum();
assert!((sum - 1.0).abs() < 1e-9, "{mass}: sum={sum}");
}
}
#[test]
fn well_aligned_chain_scores_high() {
let template: [f64; K_PATTERN] = [0.50, 0.30, 0.15, 0.04, 0.008, 0.001, 0.0005, 0.0, 0.0, 0.0];
let obs = [5.0e7, 3.0e7, 1.5e7, 4.0e6, 8.0e5];
let bc = bhattacharyya_score(&obs, &template);
assert!(
bc > 0.95,
"BC for well-aligned chain should be > 0.95, got {bc}"
);
}
#[test]
fn over_extended_chain_drops_score() {
let template: [f64; K_PATTERN] = [
0.50, 0.30, 0.15, 0.04, 0.008, 0.001, 0.0005, 0.0001, 0.0, 0.0,
];
let obs_real = [5.0e7, 3.0e7, 1.5e7, 4.0e6, 8.0e5];
let bc_real = bhattacharyya_score(&obs_real, &template);
let obs_overextended = [5.0e7, 3.0e7, 1.5e7, 4.0e6, 8.0e5, 5.0e6, 5.0e6, 5.0e6];
let bc_over = bhattacharyya_score(&obs_overextended, &template);
assert!(
bc_over < bc_real - 0.05,
"over-extended BC ({bc_over:.3}) should drop at least 0.05 below clean BC ({bc_real:.3})"
);
}
#[test]
fn empty_input_returns_zero() {
let template: [f64; K_PATTERN] = [0.5, 0.3, 0.2, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0];
assert_eq!(bhattacharyya_score(&[], &template), 0.0);
assert_eq!(bhattacharyya_score(&[0.0, 0.0], &template), 0.0);
}
#[test]
fn offsets_resolve_against_ceil_bands() {
let d = FeaturesConfig::default().sulfur_offsets;
for mass in [500.0, 1500.0, 2600.0] {
assert_eq!(PEPTIDE.sulfur_ceil(mass), 1, "mass {mass}");
assert_eq!(resolve_sulfur_counts(&PEPTIDE, mass, &d), vec![0, 1, 2]);
}
for mass in [2700.0, 4000.0, 5300.0] {
assert_eq!(PEPTIDE.sulfur_ceil(mass), 2, "mass {mass}");
assert_eq!(resolve_sulfur_counts(&PEPTIDE, mass, &d), vec![1, 2, 3]);
}
for mass in [5400.0, 7000.0, 7900.0] {
assert_eq!(PEPTIDE.sulfur_ceil(mass), 3, "mass {mass}");
assert_eq!(resolve_sulfur_counts(&PEPTIDE, mass, &d), vec![2, 3, 4]);
}
}
#[test]
fn offsets_saturate_and_dedup() {
assert_eq!(
resolve_sulfur_counts(&PEPTIDE, 1500.0, &[-3, -2, -1, 0, 1]),
vec![0, 1, 2]
);
assert_eq!(resolve_sulfur_counts(&PEPTIDE, 1500.0, &[0, 0, 0]), vec![1]);
assert_eq!(
resolve_sulfur_counts(&PEPTIDE, 1500.0, &[]),
Vec::<u32>::new()
);
assert_eq!(
resolve_sulfur_counts(&PEPTIDE, 4000.0, &[1, -1, 0]),
vec![3, 1, 2]
);
}
#[test]
fn default_offsets_reach_two_over_tryptic_band() {
let d = FeaturesConfig::default().sulfur_offsets;
for mass in [800.0, 1332.0, 2000.0, 3000.0, 3997.0] {
let v = resolve_sulfur_counts(&PEPTIDE, mass, &d);
assert!(v.contains(&2), "mass {mass} set {v:?} skips n_S = 2");
}
assert!(!resolve_sulfur_counts(&PEPTIDE, 3000.0, &d).contains(&0));
assert!(resolve_sulfur_counts(&PEPTIDE, 3000.0, &[-2, -1, 0, 1]).contains(&0));
}
#[test]
fn scoring_is_max_over_resolved_set() {
let obs = [4.0e7, 2.6e7, 1.6e7, 5.0e6, 1.0e6];
let d = FeaturesConfig::default().sulfur_offsets;
for mass in [900.0, 2000.0, 3000.0, 6000.0] {
let (c, h, n, o, _) = PEPTIDE.counts(mass);
let manual = resolve_sulfur_counts(&PEPTIDE, mass, &d)
.iter()
.map(|&s| bhattacharyya_score(&obs, &cache().distribution(c, h, n, o, s)))
.fold(0.0f64, f64::max);
let got = bhattacharyya_score_best_sulfur(&obs, mass, &d, &PEPTIDE).0;
assert!((got - manual).abs() < 1e-12, "{mass}: {got} vs {manual}");
}
}
#[test]
fn empty_offsets_match_plain_template() {
let obs = [5.0e7, 3.0e7, 1.5e7, 4.0e6, 8.0e5];
for mass in [900.0, 1500.0, 3000.0, 6000.0] {
let plain = bhattacharyya_score(&obs, &PEPTIDE.distribution(mass));
let got = bhattacharyya_score_best_sulfur(&obs, mass, &[], &PEPTIDE).0;
assert!((got - plain).abs() < 1e-12, "{mass}: {got} vs {plain}");
}
}
#[test]
fn shipped_default_offsets() {
assert_eq!(FeaturesConfig::default().sulfur_offsets, vec![-1, 0, 1]);
}
#[test]
fn sulfur_override_changes_pattern() {
let m = 1500.0;
let avg = PEPTIDE.distribution(m);
let high_s = PEPTIDE.distribution_with_sulfur(m, 3);
assert!(
(high_s[2] - avg[2]).abs() > 0.005,
"3-S override should noticeably shift M+2 (avg={}, hi-S={})",
avg[2],
high_s[2]
);
}