use super::MolecularSource;
const TRIPEPTIDE: &str = "\
HELIX 1 1 ALA A 1 ALA A 3 1 3\n\
ATOM 1 N ALA A 1 0.000 0.000 0.000 1.00 0.00 N\n\
ATOM 2 CA ALA A 1 1.458 0.000 0.000 1.00 0.00 C\n\
ATOM 3 C ALA A 1 2.009 1.420 0.000 1.00 0.00 C\n\
ATOM 4 O ALA A 1 1.251 2.390 0.000 1.00 0.00 O\n\
ATOM 5 N ALA A 2 3.332 1.540 0.000 1.00 0.00 N\n\
ATOM 6 CA ALA A 2 3.966 2.850 0.000 1.00 0.00 C\n\
ATOM 7 C ALA A 2 5.480 2.750 0.000 1.00 0.00 C\n\
ATOM 8 O ALA A 2 6.020 1.650 0.000 1.00 0.00 O\n\
ATOM 9 N ALA A 3 6.150 3.890 0.000 1.00 0.00 N\n\
ATOM 10 CA ALA A 3 7.600 3.980 0.000 1.00 0.00 C\n\
ATOM 11 C ALA A 3 8.200 5.380 0.000 1.00 0.00 C\n\
ATOM 12 O ALA A 3 7.500 6.380 0.000 1.00 0.00 O\n\
CONECT 1 2\n\
END\n";
fn source() -> MolecularSource {
let result = molframe::read_bytes(
TRIPEPTIDE.as_bytes().to_vec(),
Some("tripeptide.pdb"),
&molframe::ReadOptions::new(),
);
let Ok((structure, _)) = result else {
panic!("fixture must parse")
};
MolecularSource::from_molframe(&structure)
}
const ANISO_PDB: &str = "\
ATOM 1 N ALA A 1 0.000 0.000 0.000 1.00 0.00 N\n\
ATOM 2 CA ALA A 1 1.458 0.000 0.000 1.00 0.00 C\n\
ANISOU 2 CA ALA A 1 100000 200000 300000 40000 50000 60000 C\n\
ATOM 3 C ALA A 1 2.009 1.420 0.000 1.00 0.00 C\n\
END\n";
fn aniso_source() -> MolecularSource {
let result = molframe::read_bytes(
ANISO_PDB.as_bytes().to_vec(),
Some("aniso.pdb"),
&molframe::ReadOptions::new(),
);
let Ok((structure, _)) = result else {
panic!("anisotropy fixture must parse")
};
MolecularSource::from_molframe(&structure)
}
#[test]
fn anisotropic_tensors_reach_the_atom_that_carries_them() {
let source = aniso_source();
let anisotropy = source.topology().anisotropy.as_ref();
assert_eq!(anisotropy, [(1, [10.0, 20.0, 30.0, 4.0, 5.0, 6.0])]);
}
#[test]
fn an_isotropic_structure_carries_no_anisotropy_rows() {
assert!(source().topology().anisotropy.is_empty());
}
fn has_bond(source: &MolecularSource, a: u32, b: u32) -> bool {
source
.topology()
.bonds
.iter()
.any(|bond| bond.atoms == [a, b] || bond.atoms == [b, a])
}
#[test]
fn file_secondary_structure_reaches_every_residue_row() {
assert_eq!(
source().topology().secondary_structure.as_ref(),
[molframe::SecondaryStructure::Helix; 3]
);
}
#[test]
fn peptide_links_join_the_carbonyl_carbon_to_the_next_nitrogen() {
let source = source();
assert!(has_bond(&source, 2, 4), "residue one C to residue two N");
assert!(has_bond(&source, 6, 8), "residue two C to residue three N");
}
#[test]
fn a_file_bond_is_kept_once_even_when_distance_inference_finds_it_too() {
let source = source();
let count = source
.topology()
.bonds
.iter()
.filter(|bond| bond.atoms == [0, 1] || bond.atoms == [1, 0])
.count();
assert_eq!(count, 1);
}
#[test]
fn every_bond_joins_two_distinct_atoms_inside_the_table() {
let source = source();
let atoms = u32::try_from(source.topology().atoms.len()).unwrap_or(u32::MAX);
for bond in source.topology().bonds.iter() {
assert_ne!(bond.atoms[0], bond.atoms[1]);
assert!(bond.atoms.iter().all(|&atom| atom < atoms));
}
}
const PROTEIN_PDB: &str = "\
ATOM 1 N GLY A 1 0.000 0.000 0.000 1.00 0.00 N\n\
ATOM 2 CA GLY A 1 1.458 0.000 0.000 1.00 0.00 C\n\
ATOM 3 C GLY A 1 2.009 1.420 0.000 1.00 0.00 C\n\
ATOM 4 O GLY A 1 1.251 2.390 0.000 1.00 0.00 O\n\
ATOM 5 N GLY A 2 3.332 1.540 0.000 1.00 0.00 N\n\
ATOM 6 CA GLY A 2 3.966 2.850 0.000 1.00 0.00 C\n\
ATOM 7 C GLY A 2 5.480 2.750 0.000 1.00 0.00 C\n\
ATOM 8 O GLY A 2 6.020 1.650 0.000 1.00 0.00 O\n\
END\n";
const NUCLEIC_PDB: &str = "\
ATOM 1 P A A 1 0.000 0.000 0.000 1.00 0.00 P\n\
ATOM 2 O3' A A 1 1.600 0.000 0.000 1.00 0.00 O\n\
ATOM 3 C4' A A 1 0.700 1.400 0.000 1.00 0.00 C\n\
ATOM 4 P A A 2 2.900 0.300 0.000 1.00 0.00 P\n\
ATOM 5 O3' A A 2 4.500 0.300 0.000 1.00 0.00 O\n\
ATOM 6 C4' A A 2 3.600 1.700 0.000 1.00 0.00 C\n\
END\n";
const LIGAND_PDB: &str = "\
HETATM 1 C1 LIG A 1 0.000 0.000 0.000 1.00 0.00 C\n\
HETATM 2 C2 LIG A 1 1.390 0.000 0.000 1.00 0.00 C\n\
HETATM 3 O1 LIG A 1 2.020 1.090 0.000 1.00 0.00 O\n\
CONECT 1 2 3\n\
CONECT 2 1\n\
CONECT 3 1\n\
END\n";
fn read(pdb: &str, name: &str) -> MolecularSource {
let Ok((structure, _)) = molframe::read_bytes(
pdb.as_bytes().to_vec(),
Some(name),
&molframe::ReadOptions::new(),
) else {
panic!("{name} must parse")
};
MolecularSource::from_molframe(&structure)
}
#[test]
fn file_and_inferred_bond_counts_are_deterministic_across_the_shared_fixtures() {
let protein = read(PROTEIN_PDB, "protein.pdb");
let protein_again = read(PROTEIN_PDB, "protein.pdb");
assert_eq!(
protein.topology().bonds.len(),
protein_again.topology().bonds.len()
);
assert!(
!protein.topology().bonds.is_empty(),
"a three-residue peptide has at least its backbone bonds"
);
assert_eq!(
protein.topology().secondary_structure.len(),
2,
"one secondary-structure row per residue"
);
let nucleic = read(NUCLEIC_PDB, "nucleic.pdb");
assert_eq!(nucleic.topology().atoms.len(), 6);
assert!(!nucleic.topology().bonds.is_empty());
let ligand = read(LIGAND_PDB, "ligand.pdb");
let stated = ligand
.topology()
.bonds
.iter()
.filter(|bond| bond.atoms == [0, 1] || bond.atoms == [1, 0])
.count();
assert_eq!(stated, 1, "one file edge, not a duplicate inference");
}