molframe-pdb 0.6.0

Legacy PDB reading and writing for molframe
Documentation
use super::*;
use crate::read;
use molframe_core::io::{InputBuffer, ReadOptions};

fn parse(text: &str) -> Structure {
    let input = InputBuffer::from_bytes(text.as_bytes().to_vec());
    match read(&input, &ReadOptions::new()) {
        Ok((structure, _)) => structure,
        Err(findings) => panic!("read failed: {findings:?}"),
    }
}

#[test]
fn a_file_the_format_holds_exactly_reports_no_loss() {
    let source = "\
ATOM      1  N   GLY A   1      27.340  24.430   2.614  1.00 10.00           N
END
";
    assert!(projection_loss(&parse(source), &PdbOptions::new()).is_empty());
}

#[test]
fn a_value_is_rounded_exactly_when_the_columns_cannot_hold_it() {
    assert!(rounded(1.2345, 1e3));
    assert!(!rounded(1.234, 1e3));
    assert!(rounded(0.125, 1e2));
    assert!(!rounded(0.12, 1e2));
}

const ATOM: &str = "ATOM      1  N   GLY A   1      27.340  24.430   2.614  1.00 10.00           N";

fn mentions(findings: &[Diagnostic], needle: &str) -> bool {
    findings.iter().any(|f| f.message().contains(needle))
}

#[test]
fn a_header_the_columns_hold_exactly_reports_no_loss() {
    let source = format!(
        "REMARK   2 RESOLUTION.    1.80 ANGSTROMS.\n\
         CRYST1   10.000   20.000   30.000  90.00  90.00  90.00 P 1\n{ATOM}1+\nTER       2      GLY A   1\nEND\n"
    );
    assert!(projection_loss(&parse(&source), &PdbOptions::new()).is_empty());
}

#[test]
fn a_resolution_and_cell_are_reported_only_when_rounded() {
    let source = format!(
        "REMARK   2 RESOLUTION.    1.80 ANGSTROMS.\n\
         CRYST1   10.000   20.000   30.000  90.00  90.00  90.00 P 1\n{ATOM}\nEND\n"
    );
    let mut structure = parse(&source);
    let findings = projection_loss(&structure, &PdbOptions::new());
    assert!(!mentions(&findings, "resolution"));
    assert!(!mentions(&findings, "cell"));
    let mut data = structure.data().clone();
    data.entry.resolution = Some(1.8049);
    if let Some(cell) = data.cell.as_mut() {
        cell.lengths[0] = 10.0004;
    }
    structure = Structure::new(data);
    let findings = projection_loss(&structure, &PdbOptions::new());
    assert!(mentions(&findings, "resolution"));
    assert!(mentions(&findings, "cell"));
}

#[test]
fn a_serial_is_reported_only_when_the_writer_renumbers_it() {
    let kept = projection_loss(&parse(&format!("{ATOM}\nEND\n")), &PdbOptions::new());
    assert!(!mentions(&kept, "serial"));
    let moved = ATOM.replacen("ATOM      1", "ATOM      7", 1);
    let findings = projection_loss(&parse(&format!("{moved}\nEND\n")), &PdbOptions::new());
    assert!(mentions(&findings, "7 becomes 1"));
}

#[test]
fn an_explicit_zero_charge_is_reported_and_a_signed_one_is_not() {
    let zero = projection_loss(&parse(&format!("{ATOM}0+\nEND\n")), &PdbOptions::new());
    assert!(mentions(&zero, "explicit zero charge"));
    let signed = projection_loss(&parse(&format!("{ATOM}1+\nEND\n")), &PdbOptions::new());
    assert!(!mentions(&signed, "zero charge"));
    let blank = projection_loss(&parse(&format!("{ATOM}\nEND\n")), &PdbOptions::new());
    assert!(!mentions(&blank, "zero charge"));
}