11 Commits

Author SHA1 Message Date
daniel
c4483f8a94 travis clone depth 2020-11-19 10:26:18 +01:00
daniel
faaa335e37 test appending 2020-11-19 10:24:11 +01:00
daniel
ee9158555e array-like frame indexing 2020-11-17 09:17:39 +01:00
daniel
a29faf9d63 Merge branch 'clippy' 2020-11-17 08:41:19 +01:00
daniel
3b89309b8b test filter coords 2020-11-17 08:41:11 +01:00
Josh Mitchell
daccb110db ErrorCode::is_eof() now takes self by value (8 bits) rather than reference (64 bits) 2020-11-17 00:21:11 +11:00
Josh Mitchell
8ce523cb68 Explicit returns for error cases 2020-11-17 00:19:33 +11:00
Daniel Bauer
b1d92ab150 Update README.md 2020-11-16 14:17:22 +01:00
Josh Mitchell
49f77c672a Deref rather than clone in Frame::filter_coords() 2020-11-17 00:17:04 +11:00
Daniel Bauer
53c668631d Update README.md 2020-11-16 14:14:27 +01:00
daniel
5b7247962e add repo 2020-11-16 14:01:33 +01:00
7 changed files with 173 additions and 42 deletions

View File

@@ -4,6 +4,6 @@ rust:
- beta - beta
- nightly - nightly
git: git:
depth: 10 depth: 50
after_success: after_success:
./.travis_bench.sh ./.travis_bench.sh

View File

@@ -4,6 +4,7 @@ version = "0.3.0"
authors = ["Daniel Bauer <bauer@cbs.tu-darmstadt.de>"] authors = ["Daniel Bauer <bauer@cbs.tu-darmstadt.de>"]
license = "LGPL-3.0-only" license = "LGPL-3.0-only"
edition = "2018" edition = "2018"
repository = "https://github.com/danijoo/xdrfile"
description = "Wrapper around the gromacs libxdrfile library. Can be used to read and write gromacs trajectories in xtc and trr format." description = "Wrapper around the gromacs libxdrfile library. Can be used to read and write gromacs trajectories in xtc and trr format."
readme = "README.md" readme = "README.md"
categories = ["external-ffi-bindings", "encoding", "science"] categories = ["external-ffi-bindings", "encoding", "science"]

View File

@@ -1,4 +1,4 @@
[![Build Status](https://travis-ci.com/danijoo/xdrfile.svg?branch=master)](https://travis-ci.com/danijoo/xdrfile) [![crates.io](https://img.shields.io/badge/crates.io-orange.svg?longCache=true)](https://www.crates.io/crates/xdrfile) [![Build Status](https://travis-ci.org/danijoo/xdrfile.svg?branch=master)](https://travis-ci.org/danijoo/xdrfile) [![crates.io](https://img.shields.io/badge/crates.io-orange.svg?longCache=true)](https://www.crates.io/crates/xdrfile) [![Latest Documentation](https://docs.rs/xdrfile/badge.svg)](https://docs.rs/xdrfile)
# xdrfile # xdrfile
Read and write xdr trajectory files in .xtc and .trr file format Read and write xdr trajectory files in .xtc and .trr file format
@@ -29,7 +29,7 @@ fn main() -> Result<()> {
assert_eq!(frame.step, 1); assert_eq!(frame.step, 1);
assert_eq!(frame.len(), num_atoms); assert_eq!(frame.len(), num_atoms);
let first_atom_coords = frame.coords[0]; let first_atom_coords = frame[0]; // shorthand for frame.coords[0]
assert_eq!(first_atom_coords, [-0.8901, 0.4127, -0.055499997]); assert_eq!(first_atom_coords, [-0.8901, 0.4127, -0.055499997]);
Ok(()) Ok(())

View File

@@ -64,7 +64,7 @@ impl std::error::Error for Error {
} else { } else {
None None
} }
}, }
Error::CouldNotCheckNAtoms(err) => Some(err.as_ref()), Error::CouldNotCheckNAtoms(err) => Some(err.as_ref()),
_ => None, _ => None,
} }
@@ -200,7 +200,7 @@ pub enum ErrorCode {
impl ErrorCode { impl ErrorCode {
/// True if the error is an end of file error, false otherwise /// True if the error is an end of file error, false otherwise
pub fn is_eof(&self) -> bool { pub fn is_eof(self) -> bool {
matches!(self, Self::ExdrEndOfFile) matches!(self, Self::ExdrEndOfFile)
} }
} }

View File

@@ -1,3 +1,5 @@
use std::ops::{Index, IndexMut};
/// A frame represents a single step in a trajectory. /// A frame represents a single step in a trajectory.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Frame { pub struct Frame {
@@ -40,14 +42,13 @@ impl Frame {
} }
/// Filters the frame by removing all atoms not matching the given indeces. /// Filters the frame by removing all atoms not matching the given indeces.
pub fn filter_coords(self: &mut Frame, indeces: &[usize]) { pub fn filter_coords(self: &mut Frame, indices: &[usize]) {
self.coords = self self.coords = self
.coords .coords
.iter() .iter()
.map(|elem| elem.clone())
.enumerate() .enumerate()
.filter(|&(i, _)| indeces.contains(&i)) .filter(|(i, _)| indices.contains(i))
.map(|(_, elem)| elem) .map(|(_, elem)| *elem)
.collect(); .collect();
} }
@@ -67,6 +68,20 @@ impl Frame {
} }
} }
impl Index<usize> for Frame {
type Output = [f32; 3];
fn index(&self, index: usize) -> &Self::Output {
&self.coords[index]
}
}
impl IndexMut<usize> for Frame {
fn index_mut(&mut self, index: usize) -> &mut Self::Output { {
&mut self.coords[index]
}}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@@ -81,15 +96,15 @@ mod tests {
#[test] #[test]
fn test_frame_filter_atoms() { fn test_frame_filter_atoms() {
let mut frame = Frame::with_len(3); let mut frame = Frame::with_len(3);
frame.coords[0] = [1.0, 2.0, 3.0]; frame[0] = [1.0, 2.0, 3.0];
frame.coords[1] = [4.0, 5.0, 6.0]; frame[1] = [4.0, 5.0, 6.0];
frame.coords[2] = [7.0, 8.0, 9.0]; frame[2] = [7.0, 8.0, 9.0];
let filter: Vec<usize> = vec![1, 2]; let filter: Vec<usize> = vec![1, 2];
let mut frame_new = frame.clone(); let mut frame_new = frame.clone();
frame_new.filter_coords(&filter); frame_new.filter_coords(&filter);
assert!(frame_new.len() == filter.len()); assert!(frame_new.len() == filter.len());
assert!(frame_new.coords[0] == frame.coords[1]); assert!(frame_new.coords[0] == frame[1]);
assert!(frame_new.coords[1] == frame.coords[2]); assert!(frame_new.coords[1] == frame[2]);
} }
#[test] #[test]
@@ -97,4 +112,60 @@ mod tests {
let frame = Frame::with_len(10); let frame = Frame::with_len(10);
assert_eq!(frame.len(), 10); assert_eq!(frame.len(), 10);
} }
#[test]
fn test_filter_coords() {
let mut frame = Frame {
step: 0,
time: 0.0,
box_vector: [[0.0; 3]; 3],
coords: vec![[0.0; 3], [1.0; 3], [2.0; 3]]
};
frame.filter_coords(&[1]);
for i in 0..3 {
assert_approx_eq!(frame[0][i], 1.0);
}
}
#[test]
#[allow(unused_mut)]
fn test_index() {
// test Index
let frame = Frame {
step: 0,
time: 0.0,
box_vector: [[0.0; 3]; 3],
coords: vec![[0.0; 3], [1.0; 3], [2.0; 3]]
};
for i in 0..frame.len() {
for j in 0..3 {
assert_approx_eq!(frame[i][j], frame[i][j]);
}
}
// test IndexMut
let mut frame = Frame {
step: 0,
time: 0.0,
box_vector: [[0.0; 3]; 3],
coords: vec![[0.0; 3], [1.0; 3], [2.0; 3]]
};
for i in 0..frame.len() {
for j in 0..3 {
assert_approx_eq!(frame[i][j], frame.coords[i][j]);
}
}
frame[0] = [123.0; 3];
for i in 0..frame.len() {
for j in 0..3 {
assert_approx_eq!(frame[i][j], frame.coords[i][j]);
if i == 0 {
assert_approx_eq!(frame[i][j], 123.0);
}
}
}
}
} }

View File

@@ -49,7 +49,7 @@ impl<T: Trajectory> TrajectoryIterator<T> {
// It's OK to do this every frame because the result is cached by Trajectory // It's OK to do this every frame because the result is cached by Trajectory
let num_atoms = match &self.trajectory.get_num_atoms() { let num_atoms = match &self.trajectory.get_num_atoms() {
&Ok(n) => n, &Ok(n) => n,
Err(e) => Err(Error::CouldNotCheckNAtoms(Box::new(e.clone())))?, Err(e) => return Err(Error::CouldNotCheckNAtoms(Box::new(e.clone()))),
}; };
// Reuse old frame // Reuse old frame

View File

@@ -26,7 +26,7 @@
//! assert_eq!(frame.step, 1); //! assert_eq!(frame.step, 1);
//! assert_eq!(frame.len(), num_atoms); //! assert_eq!(frame.len(), num_atoms);
//! //!
//! let first_atom_coords = frame.coords[0]; //! let first_atom_coords = frame[0];
//! assert_eq!(first_atom_coords, [-0.8901, 0.4127, -0.055499997]); //! assert_eq!(first_atom_coords, [-0.8901, 0.4127, -0.055499997]);
//! //!
//! Ok(()) //! Ok(())
@@ -175,7 +175,7 @@ impl XDRFile {
}) })
} else { } else {
// Something went wrong. But the C api does not tell us what // Something went wrong. But the C api does not tell us what
Err((path, filemode))? Err((path, filemode).into())
} }
} }
} }
@@ -232,6 +232,7 @@ pub trait Trajectory {
/// Get the number of atoms from the give trajectory /// Get the number of atoms from the give trajectory
fn get_num_atoms(&mut self) -> Result<usize>; fn get_num_atoms(&mut self) -> Result<usize>;
} }
/// Handle to Read/Write XTC Trajectories /// Handle to Read/Write XTC Trajectories
@@ -275,8 +276,8 @@ impl Trajectory for XTCTrajectory {
.get_num_atoms() .get_num_atoms()
.map_err(|e| Error::CouldNotCheckNAtoms(Box::new(e)))?; .map_err(|e| Error::CouldNotCheckNAtoms(Box::new(e)))?;
if num_atoms != frame.coords.len() { if num_atoms != frame.coords.len() {
Err((&*frame, num_atoms))?; return Err((&*frame, num_atoms).into());
}; }
unsafe { unsafe {
let code = xdrfile_xtc::read_xtc( let code = xdrfile_xtc::read_xtc(
@@ -402,7 +403,7 @@ impl Trajectory for TRRTrajectory {
.get_num_atoms() .get_num_atoms()
.map_err(|e| Error::CouldNotCheckNAtoms(Box::new(e)))?; .map_err(|e| Error::CouldNotCheckNAtoms(Box::new(e)))?;
if num_atoms != frame.coords.len() { if num_atoms != frame.coords.len() {
Err((&*frame, num_atoms))?; return Err((&*frame, num_atoms).into());
} }
unsafe { unsafe {
@@ -501,14 +502,15 @@ mod tests {
use tempfile::NamedTempFile; use tempfile::NamedTempFile;
#[test] #[test]
fn test_read_write_xtc() -> Result<()> { fn test_write_append_read_xtc() -> Result<()> {
let tempfile = NamedTempFile::new().expect("Could not create temporary file"); let tempfile = NamedTempFile::new().expect("Could not create temporary file");
let tmp_path = tempfile.path(); let tmp_path = tempfile.path();
let natoms = 2; let natoms = 2;
// write frame 1
let frame = Frame { let frame = Frame {
step: 5, step: 1,
time: 2.0, time: 1.0,
box_vector: [[1.0, 2.0, 3.0], [2.0, 1.0, 3.0], [3.0, 2.0, 1.0]], box_vector: [[1.0, 2.0, 3.0], [2.0, 1.0, 3.0], [3.0, 2.0, 1.0]],
coords: vec![[1.0, 1.0, 1.0], [1.0, 1.0, 1.0]], coords: vec![[1.0, 1.0, 1.0], [1.0, 1.0, 1.0]],
}; };
@@ -520,11 +522,28 @@ mod tests {
} }
f.flush()?; f.flush()?;
// append frame 2
let frame2 = Frame {
step: 2,
time: 2.0,
box_vector: [[1.0, 2.0, 3.0], [2.0, 1.0, 3.0], [3.0, 2.0, 1.0]],
coords: vec![[1.0, 1.0, 1.0], [1.0, 1.0, 1.0]],
};
let mut f = XTCTrajectory::open_append(&tmp_path)?;
let write_status = f.write(&frame2);
match write_status {
Err(_) => panic!("Failed"),
Ok(()) => {}
}
f.flush()?;
// open trj for read
let mut new_frame = Frame::with_len(natoms); let mut new_frame = Frame::with_len(natoms);
let mut f = XTCTrajectory::open_read(tmp_path)?; let mut f = XTCTrajectory::open_read(tmp_path)?;
let num_atoms = f.get_num_atoms()?; let num_atoms = f.get_num_atoms()?;
assert_eq!(num_atoms, natoms); assert_eq!(num_atoms, natoms);
// check frame 1 ...
let read_status = f.read(&mut new_frame); let read_status = f.read(&mut new_frame);
match read_status { match read_status {
Err(e) => assert!(false, "{:?}", e), Err(e) => assert!(false, "{:?}", e),
@@ -536,22 +555,36 @@ mod tests {
assert_approx_eq!(new_frame.time, frame.time); assert_approx_eq!(new_frame.time, frame.time);
assert_eq!(new_frame.box_vector, frame.box_vector); assert_eq!(new_frame.box_vector, frame.box_vector);
assert_eq!(new_frame.coords, frame.coords); assert_eq!(new_frame.coords, frame.coords);
// and check frame 1 ...
let read_status = f.read(&mut new_frame);
match read_status {
Err(e) => assert!(false, "{:?}", e),
Ok(()) => {}
}
assert_eq!(new_frame.len(), frame2.len());
assert_eq!(new_frame.step, frame2.step);
assert_approx_eq!(new_frame.time, frame2.time);
assert_eq!(new_frame.box_vector, frame2.box_vector);
assert_eq!(new_frame.coords, frame2.coords);
Ok(()) Ok(())
} }
#[test] #[test]
fn test_read_write_trr() -> Result<()> { fn test_write_append_read_trr() -> Result<()> {
let tempfile = NamedTempFile::new().expect("Could not create temporary file"); let tempfile = NamedTempFile::new().expect("Could not create temporary file");
let tmp_path = tempfile.path(); let tmp_path = tempfile.path();
let natoms = 2; let natoms = 2;
// write frame 1
let frame = Frame { let frame = Frame {
step: 5, step: 1,
time: 2.0, time: 1.0,
box_vector: [[1.0, 2.0, 3.0], [2.0, 1.0, 3.0], [3.0, 2.0, 1.0]], box_vector: [[1.0, 2.0, 3.0], [2.0, 1.0, 3.0], [3.0, 2.0, 1.0]],
coords: vec![[1.0, 1.0, 1.0], [1.0, 1.0, 1.0]], coords: vec![[1.0, 1.0, 1.0], [1.0, 1.0, 1.0]],
}; };
let mut f = TRRTrajectory::open_write(tmp_path)?; let mut f = TRRTrajectory::open_write(&tmp_path)?;
let write_status = f.write(&frame); let write_status = f.write(&frame);
match write_status { match write_status {
Err(_) => panic!("Failed"), Err(_) => panic!("Failed"),
@@ -559,11 +592,28 @@ mod tests {
} }
f.flush()?; f.flush()?;
// append frame 2
let frame2 = Frame {
step: 2,
time: 2.0,
box_vector: [[1.0, 2.0, 3.0], [2.0, 1.0, 3.0], [3.0, 2.0, 1.0]],
coords: vec![[1.0, 1.0, 1.0], [1.0, 1.0, 1.0]],
};
let mut f = TRRTrajectory::open_append(&tmp_path)?;
let write_status = f.write(&frame2);
match write_status {
Err(_) => panic!("Failed"),
Ok(()) => {}
}
f.flush()?;
// open trj for read
let mut new_frame = Frame::with_len(natoms); let mut new_frame = Frame::with_len(natoms);
let mut f = TRRTrajectory::open_read(tmp_path)?; let mut f = TRRTrajectory::open_read(tmp_path)?;
// let num_atoms = f.get_num_atoms()?; let num_atoms = f.get_num_atoms()?;
// assert_eq!(num_atoms, natoms); assert_eq!(num_atoms, natoms);
// check frame 1 ...
let read_status = f.read(&mut new_frame); let read_status = f.read(&mut new_frame);
match read_status { match read_status {
Err(e) => assert!(false, "{:?}", e), Err(e) => assert!(false, "{:?}", e),
@@ -572,9 +622,22 @@ mod tests {
assert_eq!(new_frame.len(), frame.len()); assert_eq!(new_frame.len(), frame.len());
assert_eq!(new_frame.step, frame.step); assert_eq!(new_frame.step, frame.step);
assert_eq!(new_frame.time, frame.time); assert_approx_eq!(new_frame.time, frame.time);
assert_eq!(new_frame.box_vector, frame.box_vector); assert_eq!(new_frame.box_vector, frame.box_vector);
assert_eq!(new_frame.coords, frame.coords); assert_eq!(new_frame.coords, frame.coords);
// and check frame 1 ...
let read_status = f.read(&mut new_frame);
match read_status {
Err(e) => assert!(false, "{:?}", e),
Ok(()) => {}
}
assert_eq!(new_frame.len(), frame2.len());
assert_eq!(new_frame.step, frame2.step);
assert_approx_eq!(new_frame.time, frame2.time);
assert_eq!(new_frame.box_vector, frame2.box_vector);
assert_eq!(new_frame.coords, frame2.coords);
Ok(()) Ok(())
} }
@@ -631,21 +694,17 @@ mod tests {
Ok(s) => { Ok(s) => {
assert_eq!(s, CString::new("test")?); assert_eq!(s, CString::new("test")?);
} }
Err(_) => panic!("Valid Path failed to convert to CString.") Err(_) => panic!("Valid Path failed to convert to CString."),
} }
// \0 in path should result in an InvalidOsStr(Some(NulError)) // \0 in path should result in an InvalidOsStr(Some(NulError))
let result = path_to_cstring(PathBuf::from("invalid/\0path")); let result = path_to_cstring(PathBuf::from("invalid/\0path"));
match result { match result {
Ok(_) => panic!("Cstring conversion did not fail"), Ok(_) => panic!("Cstring conversion did not fail"),
Err(e) => { Err(e) => match e {
match e { Error::InvalidOsStr(opt) => assert!(opt.is_some()),
Error::InvalidOsStr(opt) => { _ => panic!("Wrong error type. (This should never happend)."),
assert!(opt.is_some()) },
}
_ => panic!("Wrong error type. (This should never happend).")
}
}
} }
Ok(()) Ok(())
} }