molframe-cif 0.3.0

PDBx/mmCIF reading and writing for molframe
Documentation
use super::*;
use crate::parse;
use molframe_core::io::InputBuffer;

const CIF: &str = "data_small\n_cell_length_a 10\n_cell_length_b 11\n_cell_length_c 12\n_cell_angle_alpha 90\n_cell_angle_beta 91\n_cell_angle_gamma 92\n_symmetry_space_group_name_H-M 'P 1'\nloop_\n_atom_site_label\n_atom_site_type_symbol\n_atom_site_fract_x\n_atom_site_fract_y\n_atom_site_fract_z\n_atom_site_occupancy\nC1 C 0.1 0.2 0.3 1\nO1 O 0.2 0.2 0.3 0.5\nloop_\n_geom_bond_atom_site_label_1\n_geom_bond_atom_site_label_2\n_geom_bond_distance\n_geom_bond_site_symmetry_2\nC1 O1 1.23 .\n";

fn parse_small(text: &str) -> Result<SmallCifStructure, SmallCifError> {
    let input = InputBuffer::from_bytes(text.as_bytes().to_vec());
    let (document, findings) = parse(&input).expect("CIF syntax should parse");
    assert!(findings.is_empty());
    lower_small_cif_with_options(&document, SmallCifOptions::ddl1())
}

#[test]
fn ddl1_atoms_cell_fractional_coordinates_and_bonds_are_lowered() {
    let structure = parse_small(CIF).expect("core CIF should lower");
    assert_eq!(structure.atoms.len(), 2);
    assert_eq!(structure.atoms[1].fractional, Some([0.2, 0.2, 0.3]));
    assert_eq!(structure.atoms[1].occupancy, Some(0.5));
    assert_eq!(structure.bonds[0].first, 0);
    assert_eq!(structure.bonds[0].second, 1);
    assert!(
        structure
            .cell
            .expect("cell should exist")
            .lengths
            .into_iter()
            .zip([10.0, 11.0, 12.0])
            .all(|(actual, expected)| (actual - expected).abs() < f64::EPSILON)
    );
}

#[test]
fn ddl1_names_require_the_explicit_dialect() {
    let input = InputBuffer::from_bytes(CIF.as_bytes().to_vec());
    let (document, _) = parse(&input).expect("CIF syntax should parse");
    assert_eq!(lower_small_cif(&document), Err(SmallCifError::MissingAtoms));
}

#[test]
fn missing_atom_chemistry_is_not_inferred_from_the_label() {
    let missing_type = CIF
        .replace("_atom_site_type_symbol\n", "")
        .replace("C1 C 0.1", "C1 0.1")
        .replace("O1 O 0.2", "O1 0.2");
    assert_eq!(
        parse_small(&missing_type),
        Err(SmallCifError::AtomTypeSymbol)
    );
}

#[test]
fn incomplete_fractional_triplets_are_refused() {
    let broken = CIF
        .replace("_atom_site_fract_z\n", "")
        .replace("C1 C 0.1 0.2 0.3 1", "C1 C 0.1 0.2 1")
        .replace("O1 O 0.2 0.2 0.3 0.5", "O1 O 0.2 0.2 0.5");
    assert_eq!(parse_small(&broken), Err(SmallCifError::Coordinates));
}

#[test]
fn external_cod_fixture_when_configured() {
    let Some(path) = std::env::var_os("MOLFRAME_SMALL_CIF_FIXTURE") else {
        return;
    };
    let bytes = std::fs::read(path).expect("configured COD fixture should be readable");
    let input = InputBuffer::from_bytes(bytes);
    let (document, _) = parse(&input).expect("COD CIF should parse");
    let structure = lower_small_cif_with_options(&document, SmallCifOptions::ddl1())
        .expect("COD CIF should lower");
    assert!(!structure.atoms.is_empty());
    assert!(structure.cell.is_some());
}