use std::fs::File;
use std::io::{BufRead, BufReader, BufWriter, Write};
use std::path::Path;
use hashbrown::HashMap;
use crate::auxiliary::{PDB_MAX_COORDINATE, PDB_MIN_COORDINATE};
use crate::errors::{ParsePdbConnectivityError, ParsePdbError, WritePdbError};
use crate::structures::{atom::Atom, simbox::SimBox};
use crate::system::System;
use super::check_coordinate_sizes;
pub fn read_pdb(filename: impl AsRef<Path>) -> Result<System, ParsePdbError> {
let file = match File::open(filename.as_ref()) {
Ok(x) => x,
Err(_) => return Err(ParsePdbError::FileNotFound(Box::from(filename.as_ref()))),
};
let reader = BufReader::new(file);
let mut atoms: Vec<Atom> = Vec::new();
let mut title = "Unknown".to_string();
let mut simbox = None;
for raw_line in reader.lines() {
let line = match raw_line {
Ok(x) => x,
Err(_) => return Err(ParsePdbError::LineNotFound(Box::from(filename.as_ref()))),
};
if (line.len() >= 4 && line[0..4] == *"ATOM")
|| (line.len() >= 6 && line[0..6] == *"HETATM")
{
atoms.push(line_as_atom(&line)?);
}
else if line.len() >= 5 && line[0..5] == *"TITLE" {
title = line_as_title(&line);
}
else if line.len() >= 6 && line[0..6] == *"CRYST1" {
simbox = Some(line_as_box(&line)?);
}
else if line.len() >= 3 && line[0..3] == *"END" {
break;
}
}
Ok(System::new(&title, atoms, simbox))
}
impl System {
pub fn add_bonds_from_pdb(
&mut self,
filename: impl AsRef<Path>,
) -> Result<(), ParsePdbConnectivityError> {
if self.has_duplicate_atom_numbers() {
return Err(ParsePdbConnectivityError::DuplicateAtomNumbers);
}
let file = match File::open(filename.as_ref()) {
Ok(x) => x,
Err(_) => {
return Err(ParsePdbConnectivityError::FileNotFound(Box::from(
filename.as_ref(),
)))
}
};
let reader = BufReader::new(file);
let atom_number_to_index: HashMap<usize, usize> = self
.atoms_iter()
.map(|atom| (atom.get_atom_number(), atom.get_index()))
.collect();
let mut temp_bonded = vec![Vec::new(); self.get_n_atoms()];
for raw_line in reader.lines() {
let line = match raw_line {
Ok(x) => x,
Err(_) => {
return Err(ParsePdbConnectivityError::LineNotFound(Box::from(
filename.as_ref(),
)))
}
};
if line.len() >= 6 && line[0..6] == *"CONECT" {
line_as_conect(&mut temp_bonded, &line, &atom_number_to_index)?;
} else if line.trim().len() == 3 && line[0..3] == *"END" {
break;
}
}
let mut empty = true;
for (bonded, atom) in temp_bonded.into_iter().zip(self.atoms_iter_mut()) {
unsafe {
if !bonded.is_empty() {
empty = false;
}
atom.set_bonded(bonded);
}
}
self.reset_mol_references();
if empty {
Err(ParsePdbConnectivityError::NoBondsWarning(Box::from(
filename.as_ref(),
)))
} else {
Ok(())
}
}
}
impl System {
pub fn write_pdb(
&self,
filename: impl AsRef<Path>,
write_connectivity: bool,
) -> Result<(), WritePdbError> {
match self.group_write_pdb("all", filename, write_connectivity) {
Ok(_) => Ok(()),
Err(WritePdbError::GroupNotFound(_)) => {
panic!(
"FATAL GROAN ERROR | System::write_pdb | Default group 'all' does not exist."
)
}
Err(e) => Err(e),
}
}
pub fn group_write_pdb(
&self,
group_name: &str,
filename: impl AsRef<Path>,
write_connectivity: bool,
) -> Result<(), WritePdbError> {
if !self.group_exists(group_name) {
return Err(WritePdbError::GroupNotFound(group_name.to_string()));
}
if !check_coordinate_sizes(
self.group_iter(group_name).unwrap(),
PDB_MIN_COORDINATE,
PDB_MAX_COORDINATE,
) {
return Err(WritePdbError::CoordinateTooLarge);
}
if write_connectivity {
if self.get_n_atoms() > 99_999 {
return Err(WritePdbError::ConectTooLarge(self.get_n_atoms()));
}
if self.has_duplicate_atom_numbers() {
return Err(WritePdbError::ConectDuplicateAtomNumbers);
}
}
let output = File::create(&filename)
.map_err(|_| WritePdbError::CouldNotCreate(Box::from(filename.as_ref())))?;
let mut writer = BufWriter::new(output);
let title = match group_name {
"all" => self.get_name().to_owned(),
_ => format!("Group `{}` from {}", group_name, self.get_name()),
};
write_header(&mut writer, &title, self.get_box())?;
for atom in self.group_iter(group_name).expect(
"FATAL GROAN ERROR | System::group_write_pdb | Group should exist but it does not.",
) {
atom.write_pdb(&mut writer)?;
}
write_line(&mut writer, "TER\nENDMDL")?;
if write_connectivity {
write_connectivity_section(self, &mut writer, group_name)?;
}
write_line(&mut writer, "END")?;
writer.flush().map_err(|_| WritePdbError::CouldNotWrite)?;
Ok(())
}
}
fn line_as_atom(line: &str) -> Result<Atom, ParsePdbError> {
if line.len() < 54 {
return Err(ParsePdbError::ParseAtomLineErr(line.to_string()));
}
let atom_number = line[6..11]
.trim()
.parse::<usize>()
.map_err(|_| ParsePdbError::ParseAtomLineErr(line.to_string()))?;
let atom_name = line[12..16].trim().to_string();
if atom_name.is_empty() {
return Err(ParsePdbError::ParseAtomLineErr(line.to_string()));
}
let residue_name = line[17..21].trim().to_string();
if residue_name.is_empty() {
return Err(ParsePdbError::ParseAtomLineErr(line.to_string()));
}
let chain = line.chars().nth(21).filter(|&x| !x.is_whitespace());
let residue_number = line[22..26]
.trim()
.parse::<usize>()
.map_err(|_| ParsePdbError::ParseAtomLineErr(line.to_string()))?;
let mut curr = 30usize;
let mut position = [0.0, 0.0, 0.0];
for pos in &mut position {
*pos = line[curr..curr + 8]
.trim()
.parse::<f32>()
.map(|x| x / 10.0)
.map_err(|_| ParsePdbError::ParseAtomLineErr(line.to_string()))?;
if !pos.is_finite() {
return Err(ParsePdbError::InvalidFloat(line.to_string()));
}
curr += 8;
}
let atom = Atom::new(residue_number, &residue_name, atom_number, &atom_name)
.with_position(position.into());
match chain {
Some(x) => Ok(atom.with_chain(x)),
None => Ok(atom),
}
}
pub(super) fn line_as_box(line: &str) -> Result<SimBox, ParsePdbError> {
if line.len() < 54 {
return Err(ParsePdbError::ParseBoxLineErr(line.to_string()));
}
let mut boxsize = [0.0, 0.0, 0.0];
let mut curr = 6usize;
for dim in &mut boxsize {
*dim = line[curr..curr + 9]
.trim()
.parse::<f32>()
.map(|x| x / 10.0)
.map_err(|_| ParsePdbError::ParseBoxLineErr(line.to_string()))?;
curr += 9;
}
let mut angles = [0.0, 0.0, 0.0];
for ang in &mut angles {
*ang = line[curr..curr + 7]
.trim()
.parse::<f32>()
.map_err(|_| ParsePdbError::ParseBoxLineErr(line.to_string()))?;
curr += 7;
}
Ok(SimBox::from_lengths_angles(boxsize.into(), angles.into()))
}
pub(super) fn line_as_title(line: &str) -> String {
let title = line[5..].trim().to_string();
if title.is_empty() {
return "Unknown".to_string();
}
title
}
fn line_as_conect(
temp_bonded: &mut [Vec<usize>],
line: &str,
number2index: &HashMap<usize, usize>,
) -> Result<(), ParsePdbConnectivityError> {
if line.len() < 11 {
return Err(ParsePdbConnectivityError::ParseConectLineErr(
line.to_string(),
));
}
let atom_number = line[6..11]
.trim()
.parse::<usize>()
.map_err(|_| ParsePdbConnectivityError::ParseConectLineErr(line.to_string()))?;
let atom_index = match number2index.get(&atom_number) {
Some(i) => i,
None => {
return Err(ParsePdbConnectivityError::AtomNotFound(
atom_number,
line.to_string(),
))
}
};
let mut iterator = 11usize;
while iterator + 4 < line.len() {
let trimmed = line[iterator..(iterator + 5)].trim();
if !trimmed.is_empty() {
let number = trimmed
.parse::<usize>()
.map_err(|_| ParsePdbConnectivityError::ParseConectLineErr(line.to_string()))?;
let index = match number2index.get(&number) {
Some(i) => i,
None => {
return Err(ParsePdbConnectivityError::AtomNotFound(
number,
line.to_string(),
))
}
};
if atom_index == index {
return Err(ParsePdbConnectivityError::SelfBonding(atom_number));
}
temp_bonded[*atom_index].push(*index);
temp_bonded[*index].push(*atom_index);
}
iterator += 5;
}
Ok(())
}
fn write_line<W: Write>(writer: &mut W, line: &str) -> Result<(), WritePdbError> {
writeln!(writer, "{}", line).map_err(|_| WritePdbError::CouldNotWrite)
}
fn write<W: Write>(writer: &mut W, string: &str) -> Result<(), WritePdbError> {
write!(writer, "{}", string).map_err(|_| WritePdbError::CouldNotWrite)
}
fn write_header(
writer: &mut BufWriter<File>,
title: &str,
simbox: Option<&SimBox>,
) -> Result<(), WritePdbError> {
write_line(writer, &format!("TITLE {}", title))?;
write_line(writer, "REMARK THIS IS A SIMULATION BOX")?;
if let Some(simbox) = simbox {
let (lengths, angles) = simbox.to_lengths_angles();
write_line(
writer,
&format!(
"CRYST1{:>9.3}{:>9.3}{:>9.3}{:>7.2}{:>7.2}{:>7.2} P 1 1",
lengths.x * 10.0,
lengths.y * 10.0,
lengths.z * 10.0,
angles.x,
angles.y,
angles.z,
),
)?;
}
write_line(writer, "MODEL 1")?;
Ok(())
}
fn write_connectivity_section(
system: &System,
writer: &mut BufWriter<File>,
group_name: &str,
) -> Result<(), WritePdbError> {
for atom in system
.group_iter(group_name)
.expect("FATAL GROAN ERROR | pdb_io::write_connectivity_section (1) | Group should exist but it does not.")
{
let bonded_in_group = atom
.get_bonded()
.iter()
.filter(|index| system
.group_isin(group_name, *index)
.expect("FATAL GROAN ERROR | pdb_io::write_connectivity_section (2) | Group should exist but it does not.")
)
.collect::<Vec<usize>>();
let n_bonded = bonded_in_group.len();
if n_bonded == 0 {
continue;
}
let atom_number = atom.get_atom_number();
if atom_number > 99_999 {
return Err(WritePdbError::ConectInvalidNumber(atom_number));
}
for (i, bonded_index) in bonded_in_group.into_iter().enumerate() {
if i % 4 == 0 {
write(writer, &format!("CONECT{:>5}", atom_number))?;
}
let bonded_number = system
.get_atom(bonded_index)
.expect("FATAL GROAN ERROR | pdb_io::write_connectivity_section | Invalid atom index.")
.get_atom_number();
if bonded_number > 99_999 {
return Err(WritePdbError::ConectInvalidNumber(bonded_number));
}
write(writer, &format!("{:>5}", bonded_number))?;
if i % 4 == 3 || i == n_bonded - 1 {
write_line(writer, "")?;
}
}
}
Ok(())
}
#[cfg(test)]
mod tests_read {
use super::*;
use crate::io::gro_io::read_gro;
use crate::test_utilities::utilities::compare_atoms;
use float_cmp::assert_approx_eq;
#[test]
fn read_simple() {
let system = read_pdb("test_files/example.pdb").unwrap();
assert_eq!(system.get_name(), "Buforin II peptide P11L");
assert_eq!(system.get_n_atoms(), 50);
let simbox = system.get_box().unwrap();
assert_approx_eq!(f32, simbox.x, 6.0861);
assert_approx_eq!(f32, simbox.y, 6.0861);
assert_approx_eq!(f32, simbox.z, 6.0861);
assert_eq!(simbox.v1y, 0.0f32);
assert_eq!(simbox.v1z, 0.0f32);
assert_eq!(simbox.v2x, 0.0f32);
assert_eq!(simbox.v2z, 0.0f32);
assert_eq!(simbox.v3x, 0.0f32);
assert_eq!(simbox.v3y, 0.0f32);
let atoms = system.get_atoms();
let first = &atoms[0];
assert_eq!(first.get_residue_number(), 1);
assert_eq!(first.get_residue_name(), "THR");
assert_eq!(first.get_atom_name(), "BB");
assert_eq!(first.get_atom_number(), 1);
assert_eq!(first.get_chain().unwrap(), 'A');
assert_approx_eq!(f32, first.get_position().unwrap().x, 1.660);
assert_approx_eq!(f32, first.get_position().unwrap().y, 2.061);
assert_approx_eq!(f32, first.get_position().unwrap().z, 3.153);
let middle = &atoms[24];
assert_eq!(middle.get_residue_number(), 11);
assert_eq!(middle.get_residue_name(), "LEU");
assert_eq!(middle.get_atom_name(), "SC1");
assert_eq!(middle.get_atom_number(), 25);
assert_eq!(middle.get_chain().unwrap(), 'B');
assert_approx_eq!(f32, middle.get_position().unwrap().x, 3.161);
assert_approx_eq!(f32, middle.get_position().unwrap().y, 2.868);
assert_approx_eq!(f32, middle.get_position().unwrap().z, 2.797);
let last = &atoms[49];
assert_eq!(last.get_residue_number(), 21);
assert_eq!(last.get_residue_name(), "LYS");
assert_eq!(last.get_atom_name(), "SC2");
assert_eq!(last.get_atom_number(), 50);
assert_eq!(last.get_chain().unwrap(), 'C');
assert_approx_eq!(f32, last.get_position().unwrap().x, 4.706);
assert_approx_eq!(f32, last.get_position().unwrap().y, 4.447);
assert_approx_eq!(f32, last.get_position().unwrap().z, 2.813);
for atom in atoms.iter() {
assert_eq!(atom.get_velocity(), None);
assert_eq!(atom.get_force(), None);
}
}
#[test]
fn read_endmdl() {
let system = read_pdb("test_files/example_endmdl.pdb").unwrap();
assert_eq!(system.get_name(), "Buforin II peptide P11L");
assert_eq!(system.get_n_atoms(), 17);
assert_eq!(system.atoms_iter().next().unwrap().get_atom_number(), 1);
assert_eq!(system.atoms_iter().nth(16).unwrap().get_atom_number(), 17);
}
#[test]
fn read_end() {
let system = read_pdb("test_files/example_end.pdb").unwrap();
assert_eq!(system.get_name(), "Buforin II peptide P11L");
assert_eq!(system.get_n_atoms(), 17);
assert_eq!(system.atoms_iter().next().unwrap().get_atom_number(), 1);
assert_eq!(system.atoms_iter().nth(16).unwrap().get_atom_number(), 17);
}
#[test]
fn read_nochain() {
let system_chain = read_pdb("test_files/example.pdb").unwrap();
let system_nochain = read_pdb("test_files/example_nochain.pdb").unwrap();
assert_eq!(system_chain.get_name(), system_nochain.get_name());
assert_eq!(
system_chain.get_box().unwrap().x,
system_nochain.get_box().unwrap().x
);
assert_eq!(
system_chain.get_box().unwrap().y,
system_nochain.get_box().unwrap().y
);
assert_eq!(
system_chain.get_box().unwrap().z,
system_nochain.get_box().unwrap().z
);
for (ac, anc) in system_chain.atoms_iter().zip(system_nochain.atoms_iter()) {
assert_eq!(ac.get_residue_number(), anc.get_residue_number());
assert_eq!(ac.get_residue_name(), anc.get_residue_name());
assert_eq!(ac.get_atom_number(), anc.get_atom_number());
assert_eq!(ac.get_atom_name(), anc.get_atom_name());
assert_eq!(ac.get_position().unwrap().x, anc.get_position().unwrap().x);
assert_eq!(ac.get_position().unwrap().y, anc.get_position().unwrap().y);
assert_eq!(ac.get_position().unwrap().z, anc.get_position().unwrap().z);
assert_eq!(ac.get_velocity(), anc.get_velocity());
assert_eq!(ac.get_force(), anc.get_force());
assert_eq!(anc.get_chain(), None);
}
}
#[test]
fn read_hetatm() {
let system = read_pdb("test_files/example_hetatm.pdb").unwrap();
assert_eq!(system.get_name(), "Buforin II peptide P11L");
assert_eq!(system.get_n_atoms(), 50);
let simbox = system.get_box().unwrap();
assert_approx_eq!(f32, simbox.x, 6.0861);
assert_approx_eq!(f32, simbox.y, 6.0861);
assert_approx_eq!(f32, simbox.z, 6.0861);
assert_eq!(simbox.v1y, 0.0f32);
assert_eq!(simbox.v1z, 0.0f32);
assert_eq!(simbox.v2x, 0.0f32);
assert_eq!(simbox.v2z, 0.0f32);
assert_eq!(simbox.v3x, 0.0f32);
assert_eq!(simbox.v3y, 0.0f32);
let atoms = system.get_atoms();
let first = &atoms[0];
assert_eq!(first.get_residue_number(), 1);
assert_eq!(first.get_residue_name(), "THR");
assert_eq!(first.get_atom_name(), "BB");
assert_eq!(first.get_atom_number(), 1);
assert_eq!(first.get_chain().unwrap(), 'A');
assert_approx_eq!(f32, first.get_position().unwrap().x, 1.660);
assert_approx_eq!(f32, first.get_position().unwrap().y, 2.061);
assert_approx_eq!(f32, first.get_position().unwrap().z, 3.153);
let middle = &atoms[24];
assert_eq!(middle.get_residue_number(), 11);
assert_eq!(middle.get_residue_name(), "LEU");
assert_eq!(middle.get_atom_name(), "SC1");
assert_eq!(middle.get_atom_number(), 25);
assert_eq!(middle.get_chain().unwrap(), 'A');
assert_approx_eq!(f32, middle.get_position().unwrap().x, 3.161);
assert_approx_eq!(f32, middle.get_position().unwrap().y, 2.868);
assert_approx_eq!(f32, middle.get_position().unwrap().z, 2.797);
let last = &atoms[49];
assert_eq!(last.get_residue_number(), 21);
assert_eq!(last.get_residue_name(), "LYS");
assert_eq!(last.get_atom_name(), "SC2");
assert_eq!(last.get_atom_number(), 50);
assert_eq!(last.get_chain().unwrap(), 'A');
assert_approx_eq!(f32, last.get_position().unwrap().x, 4.706);
assert_approx_eq!(f32, last.get_position().unwrap().y, 4.447);
assert_approx_eq!(f32, last.get_position().unwrap().z, 2.813);
for atom in atoms.iter() {
assert_eq!(atom.get_velocity(), None);
assert_eq!(atom.get_force(), None);
}
}
#[test]
fn read_no_title() {
let system = read_pdb("test_files/example_notitle.pdb").unwrap();
assert_eq!(system.get_name(), "Unknown");
assert_eq!(system.get_n_atoms(), 50);
let simbox = system.get_box().unwrap();
assert_approx_eq!(f32, simbox.x, 6.0861);
assert_approx_eq!(f32, simbox.y, 6.0861);
assert_approx_eq!(f32, simbox.z, 6.0861);
}
#[test]
fn read_empty_title() {
let system = read_pdb("test_files/example_empty_title.pdb").unwrap();
assert_eq!(system.get_name(), "Unknown");
assert_eq!(system.get_n_atoms(), 50);
let simbox = system.get_box().unwrap();
assert_approx_eq!(f32, simbox.x, 6.0861);
assert_approx_eq!(f32, simbox.y, 6.0861);
assert_approx_eq!(f32, simbox.z, 6.0861);
}
#[test]
fn read_no_box() {
let system = read_pdb("test_files/example_nobox.pdb").unwrap();
assert_eq!(system.get_name(), "Buforin II peptide P11L");
assert_eq!(system.get_n_atoms(), 50);
assert!(!system.has_box());
}
#[test]
fn read_multiple_titles() {
let system = read_pdb("test_files/example_multiple_titles.pdb").unwrap();
assert_eq!(system.get_name(), "Third title");
assert_eq!(system.get_n_atoms(), 50);
let simbox = system.get_box().unwrap();
assert_approx_eq!(f32, simbox.x, 6.0861);
assert_approx_eq!(f32, simbox.y, 6.0861);
assert_approx_eq!(f32, simbox.z, 6.0861);
}
#[test]
fn read_multiple_boxes() {
let system = read_pdb("test_files/example_multiple_boxes.pdb").unwrap();
assert_eq!(system.get_name(), "Buforin II peptide P11L");
assert_eq!(system.get_n_atoms(), 50);
let simbox = system.get_box().unwrap();
assert_approx_eq!(f32, simbox.x, 5.0861);
assert_approx_eq!(f32, simbox.y, 5.0861);
assert_approx_eq!(f32, simbox.z, 5.0861);
}
#[test]
fn add_bonds_from_pdb() {
let mut system = read_pdb("test_files/conect.pdb").unwrap();
system.add_bonds_from_pdb("test_files/conect.pdb").unwrap();
let expected_bonded: Vec<Vec<usize>> = vec![
vec![3, 2],
vec![1],
vec![4, 1, 6],
vec![3, 5],
vec![4],
vec![7, 3, 8],
vec![6],
vec![9, 6, 10],
vec![8],
vec![10, 12],
vec![11],
vec![10, 15, 14],
vec![13],
vec![13, 16],
vec![17, 15, 18],
vec![16],
vec![16, 20, 19],
vec![18],
vec![21, 18, 24],
vec![20, 22, 23],
vec![21, 23],
vec![21, 22],
vec![25, 20, 26],
vec![24],
vec![24, 28, 27],
vec![26],
vec![26, 29],
vec![11, 8, 13],
vec![30, 28, 32, 36, 38, 42, 48],
vec![29, 31],
vec![30],
vec![29, 34, 33],
vec![32],
vec![35, 32, 38],
vec![34, 36, 37],
vec![35, 37, 29],
vec![35, 36],
vec![39, 34, 41, 29],
vec![38, 40],
vec![39],
vec![42, 38, 43],
vec![41, 29],
vec![44, 41, 45],
vec![43],
vec![46, 43, 48],
vec![45, 47],
vec![46],
vec![49, 45, 29],
vec![48],
vec![],
];
for atom in system.atoms_iter() {
let expected = expected_bonded.get(atom.get_index()).unwrap();
for index in atom.get_bonded().iter() {
let bonded = system.get_atoms().get(index).unwrap().get_atom_number();
assert!(expected.contains(&bonded));
}
}
assert_eq!(system.get_atom(49).unwrap().get_n_bonded(), 0);
}
#[test]
fn add_bonds_from_pdb_2() {
let mut system1 = read_pdb("test_files/conect.pdb").unwrap();
system1.add_bonds_from_pdb("test_files/conect.pdb").unwrap();
let mut system2 = read_pdb("test_files/conect.pdb").unwrap();
system2
.add_bonds_from_pdb("test_files/bonds_for_example.pdb")
.unwrap();
for (atom1, atom2) in system1.atoms_iter().zip(system2.atoms_iter()) {
assert_eq!(atom1.get_bonded(), atom2.get_bonded());
}
}
#[test]
fn add_bonds_from_pdb_3() {
let mut system = read_pdb("test_files/conect.pdb").unwrap();
system.add_bonds_from_pdb("test_files/conect.pdb").unwrap();
system.make_molecules_whole().unwrap();
assert!(system.get_mol_references().is_some());
system.add_bonds_from_pdb("test_files/conect.pdb").unwrap();
assert!(system.get_mol_references().is_none());
}
#[test]
fn add_bonds_empty_pdb() {
let mut system = read_pdb("test_files/example.pdb").unwrap();
match system.add_bonds_from_pdb("test_files/example.pdb") {
Err(ParsePdbConnectivityError::NoBondsWarning(path)) => {
assert_eq!(path, Box::from(Path::new("test_files/example.pdb")));
assert!(!system.has_bonds());
}
Ok(_) => {
panic!("Parsing should have returned a warning but it succeeded without warning.")
}
Err(e) => panic!(
"Parsing failed with an error `{:?}` instead of a warning.",
e
),
}
}
#[test]
fn add_bonds_end() {
let mut system = read_pdb("test_files/conect.pdb").unwrap();
match system.add_bonds_from_pdb("test_files/conect_end.pdb") {
Err(ParsePdbConnectivityError::NoBondsWarning(path)) => {
assert_eq!(path, Box::from(Path::new("test_files/conect_end.pdb")));
assert!(!system.has_bonds());
}
Ok(_) => {
panic!("Parsing should have returned a warning but it succeeded without warning.")
}
Err(e) => panic!(
"Parsing failed with an error `{:?}` instead of a warning.",
e
),
}
}
#[test]
fn add_bonds_inconsistency() {
let mut system1 = read_pdb("test_files/conect.pdb").unwrap();
system1
.add_bonds_from_pdb("test_files/bonds_inconsistency.pdb")
.unwrap();
let mut system2 = read_pdb("test_files/conect.pdb").unwrap();
system2
.add_bonds_from_pdb("test_files/bonds_for_example.pdb")
.unwrap();
for (atom1, atom2) in system1.atoms_iter().zip(system2.atoms_iter()) {
assert_eq!(atom1.get_bonded(), atom2.get_bonded());
}
}
macro_rules! read_pdb_fails {
($name:ident, $file:expr, $variant:path, $expected:expr) => {
#[test]
fn $name() {
let file = $file;
match read_pdb(file) {
Err($variant(e)) => assert_eq!(e, $expected),
Ok(_) => panic!("Parsing should have failed, but it succeeded."),
Err(e) => panic!("Parsing successfully failed but incorrect error type `{:?}` was returned.", e),
}
}
};
}
read_pdb_fails!(
read_invalid_box,
"test_files/example_invalid_box.pdb",
ParsePdbError::ParseBoxLineErr,
"CRYST1 60.861 60.f61 60.861 90.00 90.00 90.00 P 1 1"
);
read_pdb_fails!(
read_invalid_box2,
"test_files/example_invalid_box2.pdb",
ParsePdbError::ParseBoxLineErr,
"CRYST1 60.861 60.861 60.861 90.00 90.00 90.0O P 1 1"
);
read_pdb_fails!(
read_short_box,
"test_files/example_short_box.pdb",
ParsePdbError::ParseBoxLineErr,
"CRYST1 60.861 60.861 60.861 90.00 90.00 90.0"
);
read_pdb_fails!(
read_short_atom,
"test_files/example_short_atom.pdb",
ParsePdbError::ParseAtomLineErr,
"ATOM 35 SC1 HIS A 16 34.580 36.530 27.8"
);
read_pdb_fails!(
read_invalid_atom,
"test_files/example_invalid_atom.pdb",
ParsePdbError::ParseAtomLineErr,
"ATOM 30 SC1 ARG A 14 32.540 35.200 34.040 1.00 0.00 "
);
read_pdb_fails!(
read_nan_position,
"test_files/nan_error.pdb",
ParsePdbError::InvalidFloat,
"ATOM 4 SC2 LYS 2 98.640 26.920 NaN 1.00 0.00 "
);
macro_rules! read_bonds_fails {
($name:ident, $file_struct:expr, $file_bonds:expr, $variant:path, $expected:expr) => {
#[test]
fn $name() {
let mut system = read_pdb($file_struct).unwrap();
match system.add_bonds_from_pdb($file_bonds) {
Err($variant(e)) => {
assert_eq!(e, $expected);
for atom in system.get_atoms() {
assert_eq!(atom.get_n_bonded(), 0);
}
}
Ok(_) => panic!("Parsing should have failed, but it succeeded."),
Err(e) => panic!("Parsing successfully failed but incorrect error type `{:?}` was returned.", e),
}
}
};
}
read_bonds_fails!(
pdb_bonds_nonexistent,
"test_files/example.pdb",
"test_files/nonexistent.pdb",
ParsePdbConnectivityError::FileNotFound,
Box::from(Path::new("test_files/nonexistent.pdb"))
);
read_bonds_fails!(
pdb_bonds_parse_error_1,
"test_files/example.pdb",
"test_files/bonds_parse_error_1.pdb",
ParsePdbConnectivityError::ParseConectLineErr,
"CONECT"
);
read_bonds_fails!(
pdb_bonds_parse_error_2,
"test_files/example.pdb",
"test_files/bonds_parse_error_2.pdb",
ParsePdbConnectivityError::ParseConectLineErr,
"CONECT 43 4A 41 45 "
);
#[test]
fn pdb_bonds_invalid_index_1() {
let mut system = read_pdb("test_files/example.pdb").unwrap();
match system.add_bonds_from_pdb("test_files/bonds_invalid_index_1.pdb") {
Err(ParsePdbConnectivityError::AtomNotFound(index, string)) => {
assert_eq!(index, 51);
assert_eq!(string, "CONECT 3 4 1 51");
}
Ok(_) => panic!("Parsing should have failed, but it succeeded."),
Err(e) => panic!(
"Parsing successfully failed but incorrect error type `{:?}` was returned.",
e
),
}
}
#[test]
fn pdb_bonds_invalid_index_2() {
let mut system = read_pdb("test_files/example.pdb").unwrap();
match system.add_bonds_from_pdb("test_files/bonds_invalid_index_2.pdb") {
Err(ParsePdbConnectivityError::AtomNotFound(index, string)) => {
assert_eq!(index, 55);
assert_eq!(
string,
"CONECT 55 35 37 29 "
);
}
Ok(_) => panic!("Parsing should have failed, but it succeeded."),
Err(e) => panic!(
"Parsing successfully failed but incorrect error type `{:?}` was returned.",
e
),
}
}
read_bonds_fails!(
pdb_bonds_selfbonding,
"test_files/example.pdb",
"test_files/bonds_selfbonding.pdb",
ParsePdbConnectivityError::SelfBonding,
44
);
#[test]
fn pdb_bonds_duplicate_numbers() {
let mut system = read_pdb("test_files/example.pdb").unwrap();
system.get_atom_mut(10).unwrap().set_atom_number(25);
match system.add_bonds_from_pdb("test_files/bonds_inconsistency.pdb") {
Err(ParsePdbConnectivityError::DuplicateAtomNumbers) => (),
Ok(_) => panic!("Parsing should have failed, but it succeeded."),
Err(e) => panic!(
"Parsing successfully failed but incorrect error type `{:?}` was returned.",
e
),
}
}
#[test]
fn pdb_read_triclinic() {
let system_pdb = read_pdb("test_files/triclinic.pdb").unwrap();
let system_gro = read_gro("test_files/triclinic.gro").unwrap();
let box_pdb = system_pdb.get_box().unwrap();
let box_gro = system_gro.get_box().unwrap();
assert_approx_eq!(f32, box_pdb.v1x, box_gro.v1x, epsilon = 0.001);
assert_approx_eq!(f32, box_pdb.v1y, box_gro.v1y, epsilon = 0.001);
assert_approx_eq!(f32, box_pdb.v1z, box_gro.v1z, epsilon = 0.001);
assert_approx_eq!(f32, box_pdb.v2x, box_gro.v2x, epsilon = 0.001);
assert_approx_eq!(f32, box_pdb.v2y, box_gro.v2y, epsilon = 0.001);
assert_approx_eq!(f32, box_pdb.v2z, box_gro.v2z, epsilon = 0.001);
assert_approx_eq!(f32, box_pdb.v3x, box_gro.v3x, epsilon = 0.001);
assert_approx_eq!(f32, box_pdb.v3y, box_gro.v3y, epsilon = 0.001);
assert_approx_eq!(f32, box_pdb.v3z, box_gro.v3z, epsilon = 0.001);
for (atom_pdb, atom_gro) in system_pdb.atoms_iter().zip(system_gro.atoms_iter()) {
compare_atoms(atom_pdb, atom_gro);
}
}
#[test]
fn pdb_read_dodecahedron() {
let system_pdb = read_pdb("test_files/dodecahedron.pdb").unwrap();
let system_gro = read_gro("test_files/dodecahedron.gro").unwrap();
let box_pdb = system_pdb.get_box().unwrap();
let box_gro = system_gro.get_box().unwrap();
assert_approx_eq!(f32, box_pdb.v1x, box_gro.v1x, epsilon = 0.001);
assert_approx_eq!(f32, box_pdb.v1y, box_gro.v1y, epsilon = 0.001);
assert_approx_eq!(f32, box_pdb.v1z, box_gro.v1z, epsilon = 0.001);
assert_approx_eq!(f32, box_pdb.v2x, box_gro.v2x, epsilon = 0.001);
assert_approx_eq!(f32, box_pdb.v2y, box_gro.v2y, epsilon = 0.001);
assert_approx_eq!(f32, box_pdb.v2z, box_gro.v2z, epsilon = 0.001);
assert_approx_eq!(f32, box_pdb.v3x, box_gro.v3x, epsilon = 0.001);
assert_approx_eq!(f32, box_pdb.v3y, box_gro.v3y, epsilon = 0.001);
assert_approx_eq!(f32, box_pdb.v3z, box_gro.v3z, epsilon = 0.001);
for (atom_pdb, atom_gro) in system_pdb.atoms_iter().zip(system_gro.atoms_iter()) {
compare_atoms(atom_pdb, atom_gro);
}
}
#[test]
fn pdb_read_octahedron() {
let system_pdb = read_pdb("test_files/octahedron.pdb").unwrap();
let system_gro = read_gro("test_files/octahedron.gro").unwrap();
let box_pdb = system_pdb.get_box().unwrap();
let box_gro = system_gro.get_box().unwrap();
assert_approx_eq!(f32, box_pdb.v1x, box_gro.v1x, epsilon = 0.001);
assert_approx_eq!(f32, box_pdb.v1y, box_gro.v1y, epsilon = 0.001);
assert_approx_eq!(f32, box_pdb.v1z, box_gro.v1z, epsilon = 0.001);
assert_approx_eq!(f32, box_pdb.v2x, box_gro.v2x, epsilon = 0.001);
assert_approx_eq!(f32, box_pdb.v2y, box_gro.v2y, epsilon = 0.001);
assert_approx_eq!(f32, box_pdb.v2z, box_gro.v2z, epsilon = 0.001);
assert_approx_eq!(f32, box_pdb.v3x, box_gro.v3x, epsilon = 0.001);
assert_approx_eq!(f32, box_pdb.v3y, box_gro.v3y, epsilon = 0.001);
assert_approx_eq!(f32, box_pdb.v3z, box_gro.v3z, epsilon = 0.001);
for (atom_pdb, atom_gro) in system_pdb.atoms_iter().zip(system_gro.atoms_iter()) {
compare_atoms(atom_pdb, atom_gro);
}
}
}
#[cfg(test)]
mod tests_write {
use super::*;
use file_diff;
use tempfile::NamedTempFile;
#[test]
fn write() {
let system = System::from_file("test_files/example_novelocities.gro").unwrap();
let pdb_output = NamedTempFile::new().unwrap();
let path_to_output = pdb_output.path();
if system.write_pdb(path_to_output, false).is_err() {
panic!("Writing pdb file failed.");
}
let mut result = File::open(path_to_output).unwrap();
let mut expected = File::open("test_files/example_nochain.pdb").unwrap();
assert!(file_diff::diff_files(&mut result, &mut expected));
}
#[test]
fn write_fails() {
let system = System::from_file("test_files/example.gro").unwrap();
match system.write_pdb("Xhfguiaghqueiowhd/nonexistent.ndx", false) {
Err(WritePdbError::CouldNotCreate(e)) => {
assert_eq!(e, Box::from(Path::new("Xhfguiaghqueiowhd/nonexistent.ndx")))
}
Ok(_) => panic!("Writing should have failed, but it did not."),
Err(e) => panic!("Incorrect error type `{:?}` was returned.", e),
}
}
#[test]
fn write_with_chains() {
let system = System::from_file("test_files/example.pdb").unwrap();
let pdb_output = NamedTempFile::new().unwrap();
let path_to_output = pdb_output.path();
if system.write_pdb(path_to_output, false).is_err() {
panic!("Writing pdb file failed.");
}
let mut result = File::open(path_to_output).unwrap();
let mut expected = File::open("test_files/example.pdb").unwrap();
assert!(file_diff::diff_files(&mut result, &mut expected));
}
#[test]
fn write_wrap() {
let atom1 = Atom::new(158, "THR", 1, "BBBBT");
let atom2 = Atom::new(158, "THR", 99999, "SC1");
let atom3 = Atom::new(10003, "ARG", 100000, "BB");
let atom4 = Atom::new(10003, "ARGGT", 200001, "SC1");
let atom5 = Atom::new(10003, "ARG", 200005, "SC2");
let atoms = vec![atom1, atom2, atom3, atom4, atom5];
let simbox = SimBox::from([1.0, 1.0, 1.0]);
let system = System::new("Expected atom and residue wrapping", atoms, Some(simbox));
let pdb_output = NamedTempFile::new().unwrap();
let path_to_output = pdb_output.path();
if system.write_pdb(path_to_output, false).is_err() {
panic!("Writing pdb file failed.");
}
let mut result = File::open(path_to_output).unwrap();
let mut expected = File::open("test_files/wrapping_expected.pdb").unwrap();
assert!(file_diff::diff_files(&mut result, &mut expected));
}
#[test]
fn write_group() {
let mut system = System::from_file("test_files/example.gro").unwrap();
system.read_ndx("test_files/index.ndx").unwrap();
let pdb_output = NamedTempFile::new().unwrap();
let path_to_output = pdb_output.path();
if system
.group_write_pdb("Protein", path_to_output, false)
.is_err()
{
panic!("Writing pdb file failed.");
}
let mut result = File::open(path_to_output).unwrap();
let mut expected = File::open("test_files/protein.pdb").unwrap();
assert!(file_diff::diff_files(&mut result, &mut expected));
}
#[test]
fn write_group_fails() {
let system = System::from_file("test_files/example.gro").unwrap();
let pdb_output = NamedTempFile::new().unwrap();
let path_to_output = pdb_output.path();
match system.group_write_pdb("Protein", path_to_output, false) {
Err(WritePdbError::GroupNotFound(e)) => assert_eq!(e, "Protein"),
Ok(_) => panic!("Writing should have failed, but it did not."),
Err(e) => panic!("Incorrect error type `{:?}` was returned.", e),
}
}
#[test]
fn write_with_connectivity() {
let mut system = System::from_file("test_files/conect.pdb").unwrap();
system.add_bonds_from_pdb("test_files/conect.pdb").unwrap();
let pdb_output = NamedTempFile::new().unwrap();
let path_to_output = pdb_output.path();
system.write_pdb(path_to_output, true).unwrap();
let mut result = File::open(path_to_output).unwrap();
let mut expected = File::open("test_files/expected_bonds.pdb").unwrap();
assert!(file_diff::diff_files(&mut result, &mut expected));
}
#[test]
fn write_with_connectivity_no_bonds() {
let mut system = System::from_file("test_files/example.pdb").unwrap();
match system.add_bonds_from_pdb("test_files/example.pdb") {
Ok(_) | Err(ParsePdbConnectivityError::NoBondsWarning(_)) => (),
Err(e) => panic!("Could not read bonds from file: `{}`", e),
}
let pdb_output = NamedTempFile::new().unwrap();
let path_to_output = pdb_output.path();
system.write_pdb(path_to_output, true).unwrap();
let mut result = File::open(path_to_output).unwrap();
let mut expected = File::open("test_files/example.pdb").unwrap();
assert!(file_diff::diff_files(&mut result, &mut expected));
}
#[test]
fn write_group_with_connectivity() {
let mut system = System::from_file("test_files/conect.pdb").unwrap();
system.add_bonds_from_pdb("test_files/conect.pdb").unwrap();
system.group_create("Group", "serial 20 to 30").unwrap();
let pdb_output = NamedTempFile::new().unwrap();
let path_to_output = pdb_output.path();
system
.group_write_pdb("Group", path_to_output, true)
.unwrap();
let mut result = File::open(path_to_output).unwrap();
let mut expected = File::open("test_files/group_expected_bonds.pdb").unwrap();
assert!(file_diff::diff_files(&mut result, &mut expected));
}
#[test]
fn write_with_connectivity_fails_too_large() {
let mut atoms = Vec::with_capacity(100_000);
let atom = Atom::new(1, "RES", 1, "ATM");
for _ in 0..100_000 {
atoms.push(atom.clone());
}
let system = System::new("Test system", atoms, Some([10.0, 10.0, 10.0].into()));
let pdb_output = NamedTempFile::new().unwrap();
let path_to_output = pdb_output.path();
match system.write_pdb(path_to_output, true) {
Ok(_) => panic!("Writing should have failed but it succeeded."),
Err(WritePdbError::ConectTooLarge(e)) => assert_eq!(e, system.get_n_atoms()),
Err(e) => panic!(
"Writing successfully failed but incorrect error type `{:?}` was returned.",
e
),
}
}
#[test]
fn write_with_connectivity_fails_duplicate() {
let mut system = System::from_file("test_files/conect.pdb").unwrap();
system.add_bonds_from_pdb("test_files/conect.pdb").unwrap();
system.get_atom_mut(10).unwrap().set_atom_number(4);
let pdb_output = NamedTempFile::new().unwrap();
let path_to_output = pdb_output.path();
match system.write_pdb(path_to_output, true) {
Ok(_) => panic!("Writing should have failed but it succeeded."),
Err(WritePdbError::ConectDuplicateAtomNumbers) => (),
Err(e) => panic!(
"Writing successfully failed but incorrect error type `{:?}` was returned.",
e
),
}
}
#[test]
fn write_with_connectivity_fails_number_too_high() {
let mut system = System::from_file("test_files/conect.pdb").unwrap();
system.add_bonds_from_pdb("test_files/conect.pdb").unwrap();
system.get_atom_mut(10).unwrap().set_atom_number(100_000);
let pdb_output = NamedTempFile::new().unwrap();
let path_to_output = pdb_output.path();
match system.write_pdb(path_to_output, true) {
Ok(_) => panic!("Writing should have failed but it succeeded."),
Err(WritePdbError::ConectInvalidNumber(e)) => assert_eq!(e, 100_000),
Err(e) => panic!(
"Writing successfully failed but incorrect error type `{:?}` was returned.",
e
),
}
}
#[test]
fn write_pdb_triclinic() {
let system = System::from_file("test_files/triclinic.gro").unwrap();
let pdb_output = NamedTempFile::new().unwrap();
let path_to_output = pdb_output.path();
system.write_pdb(path_to_output, false).unwrap();
let mut result = File::open(path_to_output).unwrap();
let mut expected = File::open("test_files/triclinic.pdb").unwrap();
assert!(file_diff::diff_files(&mut result, &mut expected));
}
#[test]
fn write_pdb_dodecahedron() {
let system = System::from_file("test_files/dodecahedron.gro").unwrap();
let pdb_output = NamedTempFile::new().unwrap();
let path_to_output = pdb_output.path();
system.write_pdb(path_to_output, false).unwrap();
let mut result = File::open(path_to_output).unwrap();
let mut expected = File::open("test_files/dodecahedron.pdb").unwrap();
assert!(file_diff::diff_files(&mut result, &mut expected));
}
#[test]
fn write_pdb_octahedron() {
let system = System::from_file("test_files/octahedron.gro").unwrap();
let pdb_output = NamedTempFile::new().unwrap();
let path_to_output = pdb_output.path();
system.write_pdb(path_to_output, false).unwrap();
let mut result = File::open(path_to_output).unwrap();
let mut expected = File::open("test_files/octahedron.pdb").unwrap();
assert!(file_diff::diff_files(&mut result, &mut expected));
}
#[test]
fn write_pdb_nobox() {
let mut system = System::from_file("test_files/example.pdb").unwrap();
system.reset_box();
let pdb_output = NamedTempFile::new().unwrap();
let path_to_output = pdb_output.path();
system.write_pdb(path_to_output, false).unwrap();
let mut result = File::open(path_to_output).unwrap();
let mut expected = File::open("test_files/example_nobox.pdb").unwrap();
assert!(file_diff::diff_files(&mut result, &mut expected));
}
#[test]
fn write_too_large_coordinate_1() {
let mut system = System::from_file("test_files/example.gro").unwrap();
system.get_atom_mut(16).unwrap().set_position_x(1000.0);
match system.write_pdb("will_not_be_created_1.pdb", false) {
Err(WritePdbError::CoordinateTooLarge) => (),
Ok(_) => panic!("Writing should have failed, but it did not."),
Err(e) => panic!("Incorrect error type `{:?}` was returned.", e),
}
}
#[test]
fn write_too_large_coordinate_2() {
let mut system = System::from_file("test_files/example.gro").unwrap();
system.get_atom_mut(16).unwrap().set_position_y(-999.0);
match system.group_write_pdb("all", "will_not_be_created_2.pdb", false) {
Err(WritePdbError::CoordinateTooLarge) => (),
Ok(_) => panic!("Writing should have failed, but it did not."),
Err(e) => panic!("Incorrect error type `{:?}` was returned.", e),
}
}
}