79 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
daniel
f10d3154f1 version up 2020-11-16 13:59:13 +01:00
Daniel Bauer
6da236c86e Merge pull request #7 from Yoshanuikabundi/no-fallible-casts
No fallible casts
2020-11-16 13:47:49 +01:00
daniel
58617dcf4d documentation 2020-11-16 13:26:03 +01:00
daniel
40714061f6 check_code should not be public 2020-11-16 13:07:08 +01:00
daniel
46d358fd7a test outofrange error 2020-11-16 12:55:54 +01:00
daniel
864ccf628d remove use statements in methods 2020-11-16 11:56:10 +01:00
daniel
ee7a266bb7 single error type for cstring conversion 2020-11-16 11:50:40 +01:00
daniel
abe7022233 Merge branch 'master' into no-fallible-casts 2020-11-15 13:38:12 +01:00
daniel
62b4429dfe travis benchmarks 2020-11-15 13:19:30 +01:00
Josh Mitchell
be6ac4202d Test for the error messages from to! 2020-11-15 18:48:13 +11:00
Josh Mitchell
43cc36de99 Corrected a few ErrorTasks 2020-11-15 18:36:54 +11:00
Josh Mitchell
62462ed8c3 Unified conversion errors and improved messages 2020-11-15 18:30:57 +11:00
Josh Mitchell
2221531f75 Improved out-of-range error messages 2020-11-15 17:44:49 +11:00
Josh Mitchell
4a71ed001c New NumAtomsOutOfRange error variant 2020-11-15 17:39:22 +11:00
Josh Mitchell
9a4f27aeb7 Removed pointer casts from lib.rs 2020-11-15 17:21:49 +11:00
Josh Mitchell
7acde7f7de Use c_int and c_float behind closed doors rather than i32/f32 2020-11-15 17:13:46 +11:00
Josh Mitchell
8f310f3fe7 Added from type info to cast errors 2020-11-15 16:54:42 +11:00
Josh Mitchell
8f34cdfcdf no more BindgenTy1 2020-11-15 16:48:52 +11:00
Josh Mitchell
ecccd6aca4 Fix a few casts after rebase 2020-11-14 13:06:53 +11:00
Josh Mitchell
1192188cb0 Test for to_i32 2020-11-14 13:02:19 +11:00
Josh Mitchell
58719dad5d Removed all fallible casts (except in tests) 2020-11-14 13:02:19 +11:00
Daniel Bauer
b0875b60a5 Merge pull request #6 from Yoshanuikabundi/frame-semantics
Frame semantics
2020-11-13 12:47:17 +01:00
daniel
bbc0250af4 frame semantics for seek/tell and benchs 2020-11-13 12:42:04 +01:00
Josh Mitchell
3c36d3040e Removed trivial casts and changed frame.step to usize 2020-11-13 12:37:23 +01:00
Josh Mitchell
734048d572 Refactor frame to have desired semantics 2020-11-13 12:37:23 +01:00
daniel
d559f9e6df disable default libtest bench 2020-11-13 12:33:09 +01:00
daniel
d2fff6cd85 iterator benchmarks 2020-11-13 12:28:40 +01:00
Daniel Bauer
e7ad99c061 Merge pull request #5 from Yoshanuikabundi/xdr-seek
XDR seek
2020-11-13 10:06:56 +01:00
daniel
dde60bff5c move imports out of functions 2020-11-12 15:45:27 +01:00
Josh Mitchell
d784be8be5 Improved seek errors and implemented for TRR and XTC 2020-11-12 15:38:57 +01:00
Josh Mitchell
95de96aa5b Implement std::io::Seek for XDRFile with unit tests 2020-11-12 15:38:57 +01:00
Daniel Bauer
a8c88caf34 Merge pull request #3 from Yoshanuikabundi/rusty-error-codes
Rusty error codes
2020-11-12 10:25:41 +01:00
daniel
80b6cafc66 reverted to single ReadNAtom error type 2020-11-12 09:59:59 +01:00
Josh Mitchell
90c4c73887 Used cached result of self.trajectory.get_num_atoms() rather than storing it in TrajectoryIterator 2020-11-11 19:19:44 +11:00
Josh Mitchell
53dd997129 Broke out next_inner 2020-11-11 17:11:55 +11:00
Josh Mitchell
6d911fae0b Clearer errors if get_num_atoms fails during into_iter 2020-11-11 16:40:37 +11:00
daniel
0d887fc515 tests 2020-11-10 17:46:11 +01:00
daniel
7a22031bee move check_code to lib 2020-11-10 17:22:43 +01:00
daniel
56a15b7a41 code cleanup 2020-11-10 17:02:34 +01:00
Josh Mitchell
7ee04620f6 Error::task() now checks sources 2020-11-10 20:11:15 +11:00
Josh Mitchell
323ba0b88c Clarified error message when read_num_atoms() fails during read() 2020-11-10 19:49:58 +11:00
Josh Mitchell
85b106f5d8 Returned Error to being an enum, as other fields were unused 2020-11-10 19:34:58 +11:00
Josh Mitchell
ce16b20b67 Slightly clearer docs 2020-11-10 18:52:16 +11:00
Josh Mitchell
51635f2583 Restricted ErrorTask to only being relevant to C API errors 2020-11-10 18:50:14 +11:00
Josh Mitchell
420af8b369 Reduced role of ErrorTask to simplify errors 2020-11-10 18:17:23 +11:00
Josh Mitchell
4a99b4e0b0 Cleaned up errors a bit 2020-11-10 17:51:41 +11:00
Josh Mitchell
65294abee8 Replaced frame resizing with a simple check 2020-11-10 17:41:11 +11:00
Josh Mitchell
1fbce21ff6 Corrected undefined behaviour when calling Trajectory.read() on improperly sized Frame 2020-11-10 00:59:10 +11:00
Josh Mitchell
d1b69d3fd4 Treat ToCString differently in Display based on whether it has a source 2020-11-09 22:42:50 +11:00
Josh Mitchell
9ac3c959d9 Added ToCString variant to Error.source() 2020-11-09 22:40:37 +11:00
Josh Mitchell
f757072523 Added documentation to the errors module 2020-11-09 22:14:16 +11:00
Josh Mitchell
95b1bdba57 Streamlined error generation 2020-11-09 22:12:07 +11:00
Josh Mitchell
36a722e721 Refactored ErrorCode as Error.source() 2020-11-09 22:06:03 +11:00
Josh Mitchell
18436aaaa3 Nicer error messages 2020-11-09 22:04:23 +11:00
Josh Mitchell
4ca96364b9 Made error construction methods pub(crate) 2020-11-09 22:03:00 +11:00
Josh Mitchell
55fa21d45c Broke out errors module and handle error codes as rust enum 2020-11-09 21:58:39 +11:00
Daniel Bauer
75bd0c9437 Merge pull request #4 from Yoshanuikabundi/no-unwrap
Removed unwrap()s, refactored iterator, removed memleak
2020-11-09 08:37:37 +01:00
Josh Mitchell
fc35777aa0 Unit tests for Error::is_eof() 2020-11-09 18:15:56 +11:00
Josh Mitchell
e95fea3bfc Unit test for path_to_cstring() 2020-11-09 17:33:03 +11:00
Josh Mitchell
f9cf479baf Removed unwrap()s, refactored iterator, removed memleak 2020-11-09 17:20:46 +11:00
Daniel Bauer
05b309e4bd Update README.md 2020-11-08 11:25:36 +01:00
Daniel Bauer
e19f7efd73 Update README.md 2020-11-08 11:22:21 +01:00
daniel
e07d71beb6 travis 2020-11-08 11:16:00 +01:00
Daniel Bauer
ca7624fe1a Merge pull request #2 from Yoshanuikabundi/open-ergonomics
Improved ergonomics for opening files
2020-11-08 09:57:43 +01:00
daniel
c998ded6bb unit tests 2020-11-08 09:56:27 +01:00
Josh Mitchell
0351857606 Added open_read, open_write and open_append convenience methods 2020-11-08 00:43:29 +11:00
Josh Mitchell
4a99228d87 Changed an integration test to take path as &str 2020-11-08 00:26:30 +11:00
Josh Mitchell
e67b6a0e5f Functions that took &Path now take impl AsRef<Path> 2020-11-08 00:25:07 +11:00
16 changed files with 1356 additions and 468 deletions

9
.travis.yml Normal file
View File

@@ -0,0 +1,9 @@
language: rust
rust:
- stable
- beta
- nightly
git:
depth: 50
after_success:
./.travis_bench.sh

21
.travis_bench.sh Executable file
View File

@@ -0,0 +1,21 @@
#!/usr/bin/env bash
# Clone the repository
REMOTE_URL="$(git config --get remote.origin.url)";
cd ${TRAVIS_BUILD_DIR}/.. && \
git clone ${REMOTE_URL} "${TRAVIS_REPO_SLUG}-bench" && \
cd "${TRAVIS_REPO_SLUG}-bench" && \
# Bench master
git checkout master && \
cargo bench -- --noplot --save-baseline before && \
# Bench current branch
git checkout ${TRAVIS_COMMIT} && \
cargo bench -- --noplot --save-baseline after && \
# Install https://github.com/BurntSushi/critcmp
cargo install critcmp --force && \
# Compare the two generated benches
critcmp before after;

View File

@@ -1,10 +1,14 @@
[package] [package]
name = "xdrfile" name = "xdrfile"
version = "0.2.0" 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"
categories = ["external-ffi-bindings", "encoding", "science"]
keywords = ["bioinformatics", "molecular-dynamics", "GROMACS", "trajectory"]
build = "build.rs" build = "build.rs"
[dependencies] [dependencies]
@@ -13,6 +17,14 @@ lazy-init = "0.3"
[dev-dependencies] [dev-dependencies]
tempfile = "3.1.0" tempfile = "3.1.0"
assert_approx_eq = "1.1.0" assert_approx_eq = "1.1.0"
criterion = "0.3"
[build-dependencies] [build-dependencies]
cc = { version = "1.0", features = ["parallel" ]} cc = { version = "1.0", features = ["parallel" ]}
[lib]
bench = false
[[bench]]
name = "benchmarks"
harness = false

View File

@@ -1,4 +1,4 @@
[![crates](https://img.shields.io/badge/crates.io-v0.1.0-orange.svg?longCache=true)](https://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
@@ -11,32 +11,28 @@ files with a safe api.
### Basic usage ### Basic usage
```rust ```rust
use xdrfile::*; use xdrfile::*;
use std::path::Path;
let mut path = Path::new("tests/1l2y.xtc"); fn main() -> Result<()> {
// get a handle to the file // get a handle to the file
let mut trj = XTCTrajectory::open(path, FileMode::Read).unwrap(); let mut trj = XTCTrajectory::open_read("tests/1l2y.xtc")?;
// find number of atoms in the file // find number of atoms in the file
let num_atoms = trj.get_num_atoms().unwrap(); let num_atoms = trj.get_num_atoms()?;
// a frame object is used to get to read or write from a trajectory // a frame object is used to get to read or write from a trajectory
// without instantiating data arrays for every step // without instantiating data arrays for every step
let mut frame = Frame::with_capacity(num_atoms); let mut frame = Frame::with_len(num_atoms);
// read the first frame of the trajectory // read the first frame of the trajectory
let result = trj.read(&mut frame); trj.read(&mut frame)?;
match result {
Ok(_) => {
assert_eq!(frame.step, 1);
assert_eq!(frame.num_atoms, num_atoms);
let first_atom_coords = frame.coords[0]; assert_eq!(frame.step, 1);
assert_eq!(first_atom_coords, [-0.8901, 0.4127, -0.055499997]); assert_eq!(frame.len(), num_atoms);
}
Err(msg) => { let first_atom_coords = frame[0]; // shorthand for frame.coords[0]
panic!("Something went wrong: {}", msg); assert_eq!(first_atom_coords, [-0.8901, 0.4127, -0.055499997]);
}
Ok(())
} }
``` ```
@@ -48,16 +44,18 @@ Rc is required)
```rust ```rust
use xdrfile::*; use xdrfile::*;
use std::path::Path;
let mut path = Path::new("tests/1l2y.xtc"); fn main() -> Result<()> {
// get a handle to the file // get a handle to the file
let trj = XTCTrajectory::open(path, FileMode::Read).unwrap(); let trj = XTCTrajectory::open_read("tests/1l2y.xtc")?;
// iterate over all frames // iterate over all frames
for (idx, frame) in trj.into_iter().filter_map(Result::ok).enumerate() { for (idx, result) in trj.into_iter().enumerate() {
println!("{}", frame.time); let frame = result?;
assert_eq!(idx+1, frame.step as usize); println!("{}", frame.time);
assert_eq!(idx+1, frame.step);
}
Ok(())
} }
``` ```

75
benches/benchmarks.rs Normal file
View File

@@ -0,0 +1,75 @@
use std::rc::Rc;
use tempfile::NamedTempFile;
use xdrfile::*;
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use std::time::Duration;
/// generate a temporary test trajectory of given length
fn gen_test_traj(num_atoms: usize, num_frames: usize) -> Result<NamedTempFile> {
let tempfile = NamedTempFile::new().expect("Could not create temporary file");
let tmp_path = tempfile.path().to_path_buf();
let mut f = XTCTrajectory::open_write(&tmp_path)?;
let mut frame = Frame {
step: 1,
time: 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.1, 1.2]; num_atoms],
};
for _ in 0..num_frames {
for j in 0..num_atoms {
frame.coords[j][0] += 1.0;
frame.coords[j][1] += 1.0;
frame.coords[j][2] += 1.0;
frame.time += 1.0;
frame.step += 1;
}
f.write(&frame)?;
}
f.flush()?;
Ok(tempfile)
}
// Iterate over a trajectory and do some stuff on it
fn iterate_traj(file: &NamedTempFile, num_atoms: usize) -> Result<()> {
let path = file.path();
let traj = XTCTrajectory::open_read(path)?;
for frame in traj.into_iter() {
assert_eq!(frame?.len(), num_atoms);
}
Ok(())
}
// Iterate over a trajectory and keep_frames its frames
// This benchmarks iteration speed when frames are not reused
fn iterate_traj_keep_frames(file: &NamedTempFile, num_frames: usize) -> Result<()> {
let path = file.path();
let traj = XTCTrajectory::open_read(path)?;
let frames = traj.into_iter().filter_map(Result::ok).collect::<Vec<Rc<Frame>>>();
assert_eq!(num_frames, frames.len());
Ok(())
}
fn bench_iterate_traj(c: &mut Criterion) {
let num_atoms = 100;
let num_frames = 1000;
let tempfile = gen_test_traj(num_atoms, num_frames).unwrap();
let mut group = c.benchmark_group("iterate_traj");
group.significance_level(0.05)
.warm_up_time(Duration::from_secs(10))
.sample_size(2500)
.noise_threshold(0.05); // high noise thresholds because of disk i/o
group.bench_function("iterate_traj", |b| b.iter(|| {
iterate_traj(black_box(&tempfile), black_box(num_atoms)).unwrap()
}));
group.bench_function("iterate_traj_keep_frames", |b| b.iter(|| {
iterate_traj_keep_frames(black_box(&tempfile), black_box(num_frames)).unwrap()
}));
}
criterion_group!(benches, bench_iterate_traj);
criterion_main!(benches);

View File

@@ -1,17 +1,17 @@
extern crate cc; extern crate cc;
use std::fs; use std::fs;
use std::io::Result;
fn main() { fn main() -> Result<()> {
// This builds gromacs' xdrfile library // This builds gromacs' xdrfile library
let source_files: Vec<_> = fs::read_dir("external/xdrfile/src") let source_files = fs::read_dir("external/xdrfile/src")?
.unwrap() .map(|r| r.map(|f| f.path()))
.map(|f| f.unwrap()) .collect::<Result<Vec<_>>>()?;
.map(|f| f.path())
.collect();
cc::Build::new() cc::Build::new()
.files(source_files) .files(source_files)
.include("external/xdrfile/include") .include("external/xdrfile/include")
.warnings(false) .warnings(false)
.compile("libxdrfile.a") .compile("libxdrfile.a");
Ok(())
} }

View File

@@ -1,3 +1,4 @@
//! # Low level bindings to the c library from GROMACS
#![allow(non_upper_case_globals, non_camel_case_types)] #![allow(non_upper_case_globals, non_camel_case_types)]
pub mod xdr_seek; pub mod xdr_seek;

View File

@@ -111,8 +111,8 @@ mod tests {
use std::ffi::CString; use std::ffi::CString;
#[test] #[test]
fn test_xdr_tell() { fn test_xdr_tell() -> Result<(), Box<dyn std::error::Error>> {
let path = CString::new("tests/1l2y.xtc").unwrap(); let path = CString::new("tests/1l2y.xtc")?;
let num_atoms = 304; let num_atoms = 304;
let mut time: f32 = 2.0; let mut time: f32 = 2.0;
let mut step: i32 = 5; let mut step: i32 = 5;
@@ -121,7 +121,7 @@ mod tests {
let mut prec: f32 = 0.0; let mut prec: f32 = 0.0;
unsafe { unsafe {
let mode = CString::new("r").unwrap(); let mode = CString::new("r")?;
let xdr = xdrfile_open(path.as_ptr(), mode.as_ptr()); let xdr = xdrfile_open(path.as_ptr(), mode.as_ptr());
assert!(!xdr.is_null()); assert!(!xdr.is_null());
@@ -140,15 +140,16 @@ mod tests {
let tell = xdr_tell(xdr); let tell = xdr_tell(xdr);
assert!(tell > 0, "{}", tell); assert!(tell > 0, "{}", tell);
} };
Ok(())
} }
#[test] #[test]
fn test_xdr_seek() { fn test_xdr_seek() -> Result<(), Box<dyn std::error::Error>> {
let path = CString::new("tests/1l2y.xtc").unwrap(); let path = CString::new("tests/1l2y.xtc")?;
unsafe { unsafe {
let mode = CString::new("r").unwrap(); let mode = CString::new("r")?;
let xdr = xdrfile_open(path.as_ptr(), mode.as_ptr()); let xdr = xdrfile_open(path.as_ptr(), mode.as_ptr());
assert!(!xdr.is_null()); assert!(!xdr.is_null());
@@ -160,5 +161,6 @@ mod tests {
let tell = xdr_tell(xdr); let tell = xdr_tell(xdr);
assert!(tell == 500, "{}", tell); assert!(tell == 500, "{}", tell);
} }
Ok(())
} }
} }

View File

@@ -6,21 +6,20 @@ pub struct XDRFILE {
_unused: [u8; 0], _unused: [u8; 0],
} }
pub type BindgenTy1 = u32; pub const exdrOK: i32 = 0;
pub const exdrOK: BindgenTy1 = 0; pub const exdrHEADER: i32 = 1;
pub const exdrHEADER: BindgenTy1 = 1; pub const exdrSTRING: i32 = 2;
pub const exdrSTRING: BindgenTy1 = 2; pub const exdrDOUBLE: i32 = 3;
pub const exdrDOUBLE: BindgenTy1 = 3; pub const exdrINT: i32 = 4;
pub const exdrINT: BindgenTy1 = 4; pub const exdrFLOAT: i32 = 5;
pub const exdrFLOAT: BindgenTy1 = 5; pub const exdrUINT: i32 = 6;
pub const exdrUINT: BindgenTy1 = 6; pub const exdr3DX: i32 = 7;
pub const exdr3DX: BindgenTy1 = 7; pub const exdrCLOSE: i32 = 8;
pub const exdrCLOSE: BindgenTy1 = 8; pub const exdrMAGIC: i32 = 9;
pub const exdrMAGIC: BindgenTy1 = 9; pub const exdrNOMEM: i32 = 10;
pub const exdrNOMEM: BindgenTy1 = 10; pub const exdrENDOFFILE: i32 = 11;
pub const exdrENDOFFILE: BindgenTy1 = 11; pub const exdrFILENOTFOUND: i32 = 12;
pub const exdrFILENOTFOUND: BindgenTy1 = 12; pub const exdrNR: i32 = 13;
pub const exdrNR: BindgenTy1 = 13;
extern "C" { extern "C" {
pub static mut exdr_message: [*mut ::std::os::raw::c_char; 13usize]; pub static mut exdr_message: [*mut ::std::os::raw::c_char; 13usize];

View File

@@ -47,32 +47,39 @@ mod tests {
use tempfile::NamedTempFile; use tempfile::NamedTempFile;
#[test] #[test]
fn test_read_trr_natoms() { fn test_read_trr_natoms() -> Result<(), Box<dyn std::error::Error>> {
let path = CString::new("tests/1l2y.trr").unwrap(); let path = CString::new("tests/1l2y.trr")?;
let mut natoms = 0; let mut natoms = 0;
unsafe { unsafe {
read_trr_natoms(path.as_ptr() as *const i8, &mut natoms); read_trr_natoms(path.as_ptr(), &mut natoms);
} }
assert!(natoms == 304); assert!(natoms == 304);
Ok(())
} }
#[test] #[test]
fn test_read_trr_nframes() { fn test_read_trr_nframes() -> Result<(), Box<dyn std::error::Error>> {
let path = CString::new("tests/1l2y.trr").unwrap(); let path = CString::new("tests/1l2y.trr")?;
let mut nframes: u64 = 0; let mut nframes: u64 = 0;
unsafe { unsafe {
let code = read_trr_nframes(path.as_ptr() as *const i8, &mut nframes); let code = read_trr_nframes(path.as_ptr(), &mut nframes);
assert!(code as u32 == exdrOK); assert!(code == exdrOK);
} }
assert!(nframes == 38, "{:?}", nframes); assert!(nframes == 38, "{:?}", nframes);
Ok(())
} }
#[test] #[test]
fn test_read_write_trr() { fn test_read_write_trr() -> Result<(), Box<dyn std::error::Error>> {
let tempfile = NamedTempFile::new().unwrap(); let tempfile = NamedTempFile::new()?;
let tmp_path = CString::new(tempfile.path().to_str().unwrap()).unwrap(); let tmp_path = CString::new(
tempfile
.path()
.to_str()
.expect("Could not convert path to str"),
)?;
// write atoms to tempfile // write atoms to tempfile
let natoms: i32 = 2; let natoms: i32 = 2;
@@ -86,7 +93,7 @@ mod tests {
let f: Vec<Rvec> = vec![[1.0, 1.0, 1.0], [1.0, 1.0, 1.0]]; let f: Vec<Rvec> = vec![[1.0, 1.0, 1.0], [1.0, 1.0, 1.0]];
unsafe { unsafe {
let mode = CString::new("w").unwrap(); let mode = CString::new("w")?;
let xdr = xdrfile_open(tmp_path.as_ptr(), mode.as_ptr()); let xdr = xdrfile_open(tmp_path.as_ptr(), mode.as_ptr());
let write_code = write_trr( let write_code = write_trr(
xdr, xdr,
@@ -99,7 +106,7 @@ mod tests {
v.as_ptr() as *mut Rvec, v.as_ptr() as *mut Rvec,
f.as_ptr() as *mut Rvec, f.as_ptr() as *mut Rvec,
); );
assert!(write_code as u32 == exdrOK); assert!(write_code == exdrOK);
xdrfile_close(xdr); xdrfile_close(xdr);
} }
@@ -114,7 +121,7 @@ mod tests {
let f2: Vec<Rvec> = vec![[0.0, 0.0, 0.0]; 2]; let f2: Vec<Rvec> = vec![[0.0, 0.0, 0.0]; 2];
unsafe { unsafe {
let mode = CString::new("r").unwrap(); let mode = CString::new("r")?;
let xdr = xdrfile_open(tmp_path.as_ptr(), mode.as_ptr()); let xdr = xdrfile_open(tmp_path.as_ptr(), mode.as_ptr());
let read_code = read_trr( let read_code = read_trr(
xdr, xdr,
@@ -127,7 +134,7 @@ mod tests {
v2.as_ptr() as *mut Rvec, v2.as_ptr() as *mut Rvec,
f2.as_ptr() as *mut Rvec, f2.as_ptr() as *mut Rvec,
); );
assert!(read_code as u32 == exdrOK); assert!(read_code == exdrOK);
xdrfile_close(xdr); xdrfile_close(xdr);
} }
@@ -139,5 +146,6 @@ mod tests {
assert!(x2 == x); assert!(x2 == x);
assert!(v2 == v); assert!(v2 == v);
assert!(f2 == f); assert!(f2 == f);
Ok(())
} }
} }

View File

@@ -29,8 +29,8 @@ extern "C" {
natoms: ::std::os::raw::c_int, natoms: ::std::os::raw::c_int,
step: ::std::os::raw::c_int, step: ::std::os::raw::c_int,
time: ::std::os::raw::c_float, time: ::std::os::raw::c_float,
box_vec: *mut Matrix, box_vec: *const Matrix,
x: *mut Rvec, x: *const Rvec,
prec: ::std::os::raw::c_float, prec: ::std::os::raw::c_float,
) -> ::std::os::raw::c_int; ) -> ::std::os::raw::c_int;
} }
@@ -43,32 +43,39 @@ mod tests {
use tempfile::NamedTempFile; use tempfile::NamedTempFile;
#[test] #[test]
fn test_read_xtc_natoms() { fn test_read_xtc_natoms() -> Result<(), Box<dyn std::error::Error>> {
let path = CString::new("tests/1l2y.xtc").unwrap(); let path = CString::new("tests/1l2y.xtc")?;
let mut natoms = 0; let mut natoms = 0;
unsafe { unsafe {
read_xtc_natoms(path.as_ptr() as *mut i8, &mut natoms); read_xtc_natoms(path.as_ptr() as *mut i8, &mut natoms);
} }
assert!(natoms == 304); assert!(natoms == 304);
Ok(())
} }
#[test] #[test]
fn test_read_xtc_nframes() { fn test_read_xtc_nframes() -> Result<(), Box<dyn std::error::Error>> {
let path = CString::new("tests/1l2y.xtc").unwrap(); let path = CString::new("tests/1l2y.xtc")?;
let mut nframes: u64 = 0; let mut nframes: u64 = 0;
unsafe { unsafe {
let code = read_xtc_nframes(path.as_ptr() as *const i8, &mut nframes); let code = read_xtc_nframes(path.as_ptr(), &mut nframes);
assert!(code as u32 == exdrOK); assert!(code == exdrOK);
} }
assert!(nframes == 38, "{:?}", nframes); assert!(nframes == 38, "{:?}", nframes);
Ok(())
} }
#[test] #[test]
fn test_read_write_xtc() { fn test_read_write_xtc() -> Result<(), Box<dyn std::error::Error>> {
let tempfile = NamedTempFile::new().unwrap(); let tempfile = NamedTempFile::new()?;
let tmp_path = CString::new(tempfile.path().to_str().unwrap()).unwrap(); let tmp_path = CString::new(
tempfile
.path()
.to_str()
.expect("Could not convert path to str"),
)?;
// write atoms to tempfile // write atoms to tempfile
let natoms: i32 = 2; let natoms: i32 = 2;
@@ -78,7 +85,7 @@ mod tests {
let x: Vec<Rvec> = vec![[1.0, 1.0, 1.0], [1.0, 1.0, 1.0]]; let x: Vec<Rvec> = vec![[1.0, 1.0, 1.0], [1.0, 1.0, 1.0]];
unsafe { unsafe {
let mode = CString::new("w").unwrap(); let mode = CString::new("w")?;
let xdr = xdrfile_open(tmp_path.as_ptr(), mode.as_ptr()); let xdr = xdrfile_open(tmp_path.as_ptr(), mode.as_ptr());
let write_code = write_xtc( let write_code = write_xtc(
xdr, xdr,
@@ -89,7 +96,7 @@ mod tests {
x.as_ptr() as *mut Rvec, x.as_ptr() as *mut Rvec,
1000.0, 1000.0,
); );
assert!(write_code as u32 == exdrOK); assert!(write_code == exdrOK);
xdrfile_close(xdr); xdrfile_close(xdr);
} }
@@ -101,7 +108,7 @@ mod tests {
let mut prec: f32 = 0.0; let mut prec: f32 = 0.0;
unsafe { unsafe {
let mode = CString::new("r").unwrap(); let mode = CString::new("r")?;
let xdr = xdrfile_open(tmp_path.as_ptr(), mode.as_ptr()); let xdr = xdrfile_open(tmp_path.as_ptr(), mode.as_ptr());
let read_code = read_xtc( let read_code = read_xtc(
xdr, xdr,
@@ -112,7 +119,7 @@ mod tests {
x2.as_ptr() as *mut Rvec, x2.as_ptr() as *mut Rvec,
&mut prec, &mut prec,
); );
assert!(read_code as u32 == exdrOK); assert!(read_code == exdrOK);
xdrfile_close(xdr); xdrfile_close(xdr);
} }
@@ -121,5 +128,6 @@ mod tests {
assert!(time2 == time); assert!(time2 == time);
assert!(box_vec2 == box_vec); assert!(box_vec2 == box_vec);
assert!(x2 == x); assert!(x2 == x);
Ok(())
} }
} }

311
src/errors.rs Normal file
View File

@@ -0,0 +1,311 @@
use crate::c_abi;
use crate::FileMode;
use crate::Frame;
use std::error::Error as StdError;
use std::path::{Path, PathBuf};
/// Error type for the xdrfile library
#[derive(Debug, Clone, PartialEq)]
pub enum Error {
/// An error code from the C API
CApiError { code: ErrorCode, task: ErrorTask },
/// Passed in a frame of the wrong size
WrongSizeFrame { expected: usize, found: usize },
/// C API failed to open a file (No return code provided)
CouldNotOpen { path: PathBuf, mode: FileMode },
/// A path could not be converted to &OsStr
InvalidOsStr(Option<std::ffi::NulError>),
/// Checking the number of atoms failed while reading a frame
CouldNotCheckNAtoms(Box<Error>),
/// Error for an out-of-range numeric conversion
OutOfRange {
name: &'static str,
task: ErrorTask,
value: String,
target: &'static str,
},
}
impl Error {
/// Get the error code returned by the C API, if any
pub fn code(&self) -> Option<ErrorCode> {
if let Error::CApiError { code, .. } = self {
Some(*code)
} else if let Some(e) = self.source() {
e.downcast_ref::<Self>().and_then(Self::code)
} else {
None
}
}
/// Get the task being attempted when the C API returned an error, if any
pub fn task(&self) -> Option<ErrorTask> {
if let Error::CApiError { task, .. } = self {
Some(*task)
} else if let Some(e) = self.source() {
e.downcast_ref::<Self>().and_then(Self::task)
} else {
None
}
}
/// True if the error is an end of file error, false otherwise
pub fn is_eof(&self) -> bool {
self.code().map_or(false, |e| e.is_eof())
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match &self {
Error::InvalidOsStr(err) => {
if let Some(err) = err {
Some(err)
} else {
None
}
}
Error::CouldNotCheckNAtoms(err) => Some(err.as_ref()),
_ => None,
}
}
}
impl From<(ErrorCode, ErrorTask)> for Error {
fn from(value: (ErrorCode, ErrorTask)) -> Self {
let (code, task) = value;
Self::CApiError { code, task }
}
}
impl From<(&Path, FileMode)> for Error {
fn from(value: (&Path, FileMode)) -> Self {
let (path, mode) = value;
Error::CouldNotOpen {
path: path.to_owned(),
mode,
}
}
}
impl From<(&Frame, usize)> for Error {
fn from(value: (&Frame, usize)) -> Self {
let (frame, num_atoms) = value;
Error::WrongSizeFrame {
expected: num_atoms,
found: frame.coords.len(),
}
}
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::CApiError { code, task } => write!(
f,
"Error while {task}: C API returned error code {code}",
task = task,
code = code
),
Error::WrongSizeFrame { expected, found } => write!(
f,
"Expected frame of size {:?}, found {:?}",
expected, found
),
Error::CouldNotOpen { path, mode } => {
write!(f, "Could not open file at {:?} in mode {:?}", path, mode)
}
Error::InvalidOsStr(_) => write!(f, "Cannot convert path to CString."),
Error::CouldNotCheckNAtoms(_) => {
write!(f, "Failed to read number of atoms in trajectory file")
}
Error::OutOfRange {
name,
task,
value,
target,
} => write!(
f,
"Illegal {name} while {task}: Failed to cast {value} to {target}",
name = name,
task = task,
value = value,
target = target
),
}
}
}
/// The task being attempted when the C API returns an error
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ErrorTask {
/// The number of atoms was being read from a file
ReadNumAtoms,
/// A frame was being read from a file
Read,
/// A frame was being written to a file
Write,
/// A file was being flushed to disk
Flush,
/// A seek operation was being run on a file
Seek,
}
impl std::fmt::Display for ErrorTask {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self {
ErrorTask::ReadNumAtoms => write!(f, "reading atom number from trajectory"),
ErrorTask::Read => write!(f, "reading trajectory"),
ErrorTask::Write => write!(f, "writing trajectory"),
ErrorTask::Flush => write!(f, "flushing trajectory"),
ErrorTask::Seek => write!(f, "seeking in trajectory"),
}
}
}
/// Error codes returned from the C API
#[derive(Debug, Clone, PartialEq, Copy)]
pub enum ErrorCode {
/// No error, C API returned successfully
ExdrOk,
/// TRR file had corrupt Header
ExdrHeader,
/// Failed to read a string where expected
ExdrString,
/// Failed to read a double where expected
ExdrDouble,
/// Failed to read an int where expected
ExdrInt,
/// Failed to read a float where expected
ExdrFloat,
/// Failed to read a uint where expected
ExdrUint,
/// Failed to read compressed XTC coordinates
Exdr3dx,
/// Error encountered while closing file
ExdrClose,
/// File had incorrect "magic number" (not an XTC file)
ExdrMagic,
/// Failed to allocate memory for TRR file
ExdrNoMem,
/// End of file was reached while trying to read
ExdrEndOfFile,
/// File was not found when trying to open
ExdrFileNotFound,
/// Failed to seek within file
ExdrNr,
/// Something unexpected happened
UnmatchedCode(i32),
}
impl ErrorCode {
/// True if the error is an end of file error, false otherwise
pub fn is_eof(self) -> bool {
matches!(self, Self::ExdrEndOfFile)
}
}
impl From<i32> for ErrorCode {
fn from(code: i32) -> Self {
match code {
c_abi::xdrfile::exdrOK => Self::ExdrOk,
c_abi::xdrfile::exdrHEADER => Self::ExdrHeader,
c_abi::xdrfile::exdrSTRING => Self::ExdrString,
c_abi::xdrfile::exdrDOUBLE => Self::ExdrDouble,
c_abi::xdrfile::exdrINT => Self::ExdrInt,
c_abi::xdrfile::exdrFLOAT => Self::ExdrFloat,
c_abi::xdrfile::exdrUINT => Self::ExdrUint,
c_abi::xdrfile::exdr3DX => Self::Exdr3dx,
c_abi::xdrfile::exdrCLOSE => Self::ExdrClose,
c_abi::xdrfile::exdrMAGIC => Self::ExdrMagic,
c_abi::xdrfile::exdrNOMEM => Self::ExdrNoMem,
c_abi::xdrfile::exdrENDOFFILE => Self::ExdrEndOfFile,
c_abi::xdrfile::exdrFILENOTFOUND => Self::ExdrFileNotFound,
c_abi::xdrfile::exdrNR => Self::ExdrNr,
code => Self::UnmatchedCode(code),
}
}
}
impl std::fmt::Display for ErrorCode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Self::UnmatchedCode(i) = self {
write!(f, "{}", i)
} else {
write!(f, "{:?}", self)
}
}
}
/// `Result` type for errors in the `xdrfile` crate
pub type Result<T, E = Error> = std::result::Result<T, E>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_is_eof() {
let error = Error::CApiError {
code: c_abi::xdrfile::exdrENDOFFILE.into(),
task: ErrorTask::Read,
};
assert!(error.is_eof());
let error = Error::CApiError {
code: ErrorCode::ExdrEndOfFile,
task: ErrorTask::Read,
};
assert!(error.is_eof());
let error = Error::CApiError {
code: (c_abi::xdrfile::exdrENDOFFILE + 1).into(),
task: ErrorTask::Read,
};
assert!(!error.is_eof());
let error = Error::CApiError {
code: 0.into(),
task: ErrorTask::Read,
};
assert!(!error.is_eof());
let error = Error::CApiError {
code: 255.into(),
task: ErrorTask::Read,
};
assert!(!error.is_eof());
let error = Error::CouldNotOpen {
path: PathBuf::from("not/a/file"),
mode: FileMode::Read,
};
assert!(!error.is_eof());
}
#[test]
fn test_from_correct_type() {
let code = 3.into();
let task = ErrorTask::Read;
let expected = Error::CApiError { code, task };
let err = Error::from((code, task));
assert_eq!(expected, err);
let path = Path::new(".");
let mode = FileMode::Read;
let expected = Error::CouldNotOpen {
path: path.to_path_buf(),
mode: mode.to_owned(),
};
let err = Error::from((path, mode));
assert_eq!(expected, err);
let frame = Frame::with_len(0);
let expected = Error::WrongSizeFrame {
expected: 10,
found: 0,
};
let err = Error::from((&frame, 10));
assert_eq!(expected, err);
}
}

View File

@@ -1,28 +1,24 @@
use std::fmt; 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)] #[derive(Clone, Debug)]
pub struct Frame { pub struct Frame {
/// Number of atoms in the frame
pub num_atoms: u32,
/// Trajectory step /// Trajectory step
pub step: u32, pub step: usize,
/// Time step (usually in picoseconds) /// Time step (usually in picoseconds)
pub time: f32, pub time: f32,
/// 3x3 box vector /// 3x3 box vector
pub box_vector: [[f32; 3usize]; 3usize], pub box_vector: [[f32; 3]; 3],
/// 3D coordinates for N atoms where N is num_atoms /// 3D coordinates for N atoms where N is num_atoms
pub coords: Vec<[f32; 3usize]>, pub coords: Vec<[f32; 3]>,
} }
impl Default for Frame { impl Default for Frame {
fn default() -> Frame { fn default() -> Frame {
Frame { Frame {
num_atoms: 0,
step: 0, step: 0,
time: 0.0, time: 0.0,
box_vector: [[0.0; 3]; 3], box_vector: [[0.0; 3]; 3],
@@ -31,51 +27,59 @@ impl Default for Frame {
} }
} }
impl fmt::Debug for Frame {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Frame {{ atoms: {}, step: {}, time: {}, \
box: {:?}, coords: {:?} }}",
self.num_atoms, self.step, self.time, self.box_vector, self.coords
)
}
}
impl Frame { impl Frame {
/// Creates an empty frame with a capacity of 0 /// Creates an empty frame with a capacity of 0
pub fn new() -> Frame { pub fn new() -> Frame {
Frame { Default::default()
..Default::default()
}
} }
/// Creates a frame with the given capacity /// Creates a frame with the given capacity
pub fn with_capacity(num_atoms: u32) -> Frame { pub fn with_len(num_atoms: usize) -> Frame {
Frame { Frame {
num_atoms, coords: vec![[0.0, 0.0, 0.0]; num_atoms],
coords: vec![[0.0, 0.0, 0.0]; num_atoms as usize],
..Default::default() ..Default::default()
} }
} }
/// 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();
self.num_atoms = self.coords.len() as u32;
} }
/// Length of the frame (number of atoms) /// Length of the frame (number of atoms)
pub fn len(self: &Frame) -> usize { pub fn len(self: &Frame) -> usize {
self.num_atoms as usize self.num_atoms()
} }
/// The number of atoms in the frame
pub fn num_atoms(self: &Frame) -> usize {
self.coords.len()
}
/// Resize the frame to have exactly `num_atoms` atoms, filling coords with zeros if necessary
pub fn resize(&mut self, num_atoms: usize) {
self.coords.resize(num_atoms, [0.0; 3])
}
}
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)]
@@ -84,28 +88,84 @@ mod tests {
#[test] #[test]
fn test_frame_with_capacity() { fn test_frame_with_capacity() {
let frame = Frame::with_capacity(10); let frame = Frame::with_len(10);
println!("{:?}", frame.coords); println!("{:?}", frame.coords);
assert_eq!(frame.coords.len(), 10); assert_eq!(frame.coords.len(), 10);
} }
#[test] #[test]
fn test_frame_filter_atoms() { fn test_frame_filter_atoms() {
let mut frame = Frame::with_capacity(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.num_atoms as usize == 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]
fn test_frame_len() { fn test_frame_len() {
let frame = Frame::with_capacity(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

@@ -1,116 +1,89 @@
use crate::c_abi::xdrfile::exdrENDOFFILE;
use crate::*; use crate::*;
use std::rc::Rc; use std::rc::Rc;
impl IntoIterator for XTCTrajectory { fn into_iter_inner<T: Trajectory>(mut traj: T) -> TrajectoryIterator<T> {
type Item = Result<Rc<Frame>>; let num_atoms = traj.get_num_atoms();
type IntoIter = XTCTrajectoryIterator; let frame = match &num_atoms {
Ok(num_atoms) => Frame::with_len(*num_atoms),
fn into_iter(mut self) -> Self::IntoIter { Err(_) => Frame::new(),
// TODO this should be handled without the requirement of unwrap };
let num_atoms = self.get_num_atoms().unwrap(); TrajectoryIterator {
XTCTrajectoryIterator { trajectory: traj,
trajectory: self, item: Rc::new(frame),
item: Rc::new(Frame::with_capacity(num_atoms)), has_error: false,
has_error: false,
}
} }
} }
/* impl IntoIterator for XTCTrajectory {
Iterator for trajectories. This iterator yields a Result<Frame, Error>
for each frame in the trajectory file and stops with yielding None once the
trajectory is finished. Also yields None after the first occurence of an error
*/
pub struct XTCTrajectoryIterator {
trajectory: XTCTrajectory,
item: Rc<Frame>,
has_error: bool,
}
impl Iterator for XTCTrajectoryIterator {
type Item = Result<Rc<Frame>>; type Item = Result<Rc<Frame>>;
type IntoIter = TrajectoryIterator<XTCTrajectory>;
fn next(&mut self) -> Option<Self::Item> { fn into_iter(self) -> Self::IntoIter {
// Reuse old frame into_iter_inner(self)
if self.has_error {
return None;
}
let item: &mut Frame = match Rc::get_mut(&mut self.item) {
Some(item) => item,
None => {
// caller kept frame. Create new one
self.item = Rc::new(Frame::with_capacity(self.item.num_atoms));
Rc::get_mut(&mut self.item).unwrap()
}
};
match self.trajectory.read(item) {
Ok(()) => Some(Ok(Rc::clone(&self.item))),
Err(msg) => {
if msg.to_string().contains(&exdrENDOFFILE.to_string()) {
None
} else {
self.has_error = true;
Some(Err(msg))
}
}
}
} }
} }
impl IntoIterator for TRRTrajectory { impl IntoIterator for TRRTrajectory {
type Item = Result<Rc<Frame>>; type Item = Result<Rc<Frame>>;
type IntoIter = TRRTrajectoryIterator; type IntoIter = TrajectoryIterator<TRRTrajectory>;
fn into_iter(mut self) -> Self::IntoIter { fn into_iter(self) -> Self::IntoIter {
// TODO this should be handled without the requirement of unwrap into_iter_inner(self)
let num_atoms = self.get_num_atoms().unwrap();
TRRTrajectoryIterator {
trajectory: self,
item: Rc::new(Frame::with_capacity(num_atoms)),
has_error: false,
}
} }
} }
/* /// Iterator for trajectories.
Iterator for trajectories. This iterator yields a Result<Frame, Error> /// This iterator yields a Result<Frame, Error> for each frame in the
for each frame in the trajectory file and stops with yielding None once the /// trajectory file and stops with yielding None once the trajectory is
trajectory is finished. Also yields None after the first occurence of an error /// EOF. Also yields None after the first occurrence of an error
*/ pub struct TrajectoryIterator<T> {
pub struct TRRTrajectoryIterator { trajectory: T,
trajectory: TRRTrajectory,
item: Rc<Frame>, item: Rc<Frame>,
has_error: bool, has_error: bool,
} }
impl Iterator for TRRTrajectoryIterator { impl<T: Trajectory> TrajectoryIterator<T> {
type Item = Result<Rc<Frame>>; /// Inner function for `next()` to seperate error handling from iteration logic
fn next_inner(&mut self) -> <Self as Iterator>::Item {
// If we couldn't read the number of frames when we called into_iter, return that error now
// It's OK to do this every frame because the result is cached by Trajectory
let num_atoms = match &self.trajectory.get_num_atoms() {
&Ok(n) => n,
Err(e) => return Err(Error::CouldNotCheckNAtoms(Box::new(e.clone()))),
};
fn next(&mut self) -> Option<Self::Item> {
// Reuse old frame // Reuse old frame
if self.has_error {
return None;
}
let item: &mut Frame = match Rc::get_mut(&mut self.item) { let item: &mut Frame = match Rc::get_mut(&mut self.item) {
Some(item) => item, Some(item) => item,
None => { None => {
// caller kept frame. Create new one // caller kept frame. Create new one
self.item = Rc::new(Frame::with_capacity(self.item.num_atoms)); self.item = Rc::new(Frame::with_len(num_atoms as usize));
Rc::get_mut(&mut self.item).unwrap() Rc::get_mut(&mut self.item).expect("Could not get mutable access to new Rc")
} }
}; };
match self.trajectory.read(item) {
Ok(()) => Some(Ok(Rc::clone(&self.item))), self.trajectory.read(item)?;
Err(msg) => { Ok(Rc::clone(&self.item))
if msg.to_string().contains(&exdrENDOFFILE.to_string()) { }
None }
} else {
self.has_error = true; impl<T> Iterator for TrajectoryIterator<T>
Some(Err(msg)) where
} T: Trajectory,
{
type Item = Result<Rc<Frame>>;
fn next(&mut self) -> Option<Self::Item> {
if self.has_error {
return None;
}
match self.next_inner() {
Ok(item) => Some(Ok(item)),
Err(e) if e.is_eof() => None,
Err(e) => {
self.has_error = true;
Some(Err(e))
} }
} }
} }
@@ -121,20 +94,24 @@ mod tests {
use super::*; use super::*;
#[test] #[test]
pub fn test_xtc_trajectory_iterator() { pub fn test_xtc_trajectory_iterator() -> Result<()> {
let traj = XTCTrajectory::open(Path::new("tests/1l2y.xtc"), FileMode::Read).unwrap(); let traj = XTCTrajectory::open_read("tests/1l2y.xtc")?;
let frames: Vec<Rc<Frame>> = traj.into_iter().filter_map(Result::ok).collect(); let frames: Result<Vec<Rc<Frame>>> = traj.into_iter().collect();
let frames = frames?;
assert!(frames.len() == 38); assert!(frames.len() == 38);
assert!(frames[0].step == 1, frames[0].step); assert!(frames[0].step == 1, frames[0].step);
assert!(frames[37].step == 38); assert!(frames[37].step == 38);
Ok(())
} }
#[test] #[test]
pub fn test_trr_trajectory_iterator() { pub fn test_trr_trajectory_iterator() -> Result<()> {
let traj = TRRTrajectory::open(Path::new("tests/1l2y.trr"), FileMode::Read).unwrap(); let traj = TRRTrajectory::open_read("tests/1l2y.trr")?;
let frames: Vec<Rc<Frame>> = traj.into_iter().filter_map(Result::ok).collect(); let frames: Result<Vec<Rc<Frame>>> = traj.into_iter().collect();
let frames = frames?;
assert!(frames.len() == 38); assert!(frames.len() == 38);
assert!(frames[0].step == 1, frames[0].step); assert!(frames[0].step == 1, frames[0].step);
assert!(frames[37].step == 38); assert!(frames[37].step == 38);
Ok(())
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -1,31 +1,28 @@
#[cfg(test)] #[cfg(test)]
mod integration { mod integration {
use std::path::Path;
use std::rc::Rc; use std::rc::Rc;
use xdrfile::*; use xdrfile::*;
#[test] #[test]
fn test_use_library() { fn test_use_library() -> Result<()> {
let path = Path::new("tests/1l2y.xtc"); let mut trj = XTCTrajectory::open_read("tests/1l2y.xtc")?;
let num_atoms = trj.get_num_atoms()?;
let mut frame = Frame::with_len(num_atoms);
let mut trj = XTCTrajectory::open(path, FileMode::Read).unwrap(); trj.read(&mut frame)?;
let num_atoms = trj.get_num_atoms().unwrap(); trj.read(&mut frame)?;
let mut frame = Frame::with_capacity(num_atoms);
trj.read(&mut frame).unwrap();
trj.read(&mut frame).unwrap();
assert_eq!(frame.step, 2); assert_eq!(frame.step, 2);
Ok(())
} }
#[test] #[test]
fn test_use_library_iterator() { fn test_use_library_iterator() -> Result<()> {
let path = Path::new("tests/1l2y.xtc"); let trj = XTCTrajectory::open_read("tests/1l2y.xtc")?;
let frames: Result<Vec<Rc<Frame>>> = trj.into_iter().collect();
let trj = XTCTrajectory::open(path, FileMode::Read).unwrap(); for (idx, frame) in frames?.iter().enumerate() {
let frames: Vec<Rc<Frame>> = trj.into_iter().filter_map(Result::ok).collect(); assert_eq!(frame.step, idx + 1);
for (idx, frame) in frames.iter().enumerate() {
assert_eq!(frame.step as usize, idx + 1);
} }
Ok(())
} }
} }