use crate::error::CError;
use crate::format::{FormatKind, FormatReader, FormatWriter};
use crate::frame::Frame;
use log::error;
use std::path::Path;
pub struct TrajectoryReader {
pub size: usize,
strategy: FormatReader,
current_index: usize,
}
pub struct TrajectoryWriter {
strategy: FormatWriter,
frame_count: usize,
}
#[derive(Debug, Clone, Copy)]
pub struct FrameIndex(usize);
impl FrameIndex {
#[must_use]
pub fn new(index: usize, max: usize) -> Option<Self> {
if index < max { Some(Self(index)) } else { None }
}
#[must_use]
pub fn value(self) -> usize {
self.0
}
}
pub struct Trajectory;
impl Trajectory {
pub fn open(path: &Path) -> Result<TrajectoryReader, CError> {
Self::open_with_format(path, FormatKind::Guess)
}
pub fn open_with_format(path: &Path, format: FormatKind) -> Result<TrajectoryReader, CError> {
let kind = format.resolve(path)?;
let strategy = FormatReader::open(path, kind)?;
let size = strategy.len()?;
Ok(TrajectoryReader {
size,
strategy,
current_index: 0,
})
}
pub fn append(path: &Path) -> Result<TrajectoryWriter, CError> {
Self::append_with_format(path, FormatKind::Guess)
}
pub fn append_with_format(path: &Path, format: FormatKind) -> Result<TrajectoryWriter, CError> {
let kind = format.resolve(path)?;
let strategy = FormatWriter::open(path, kind)?;
Ok(TrajectoryWriter {
strategy,
frame_count: 0,
})
}
pub fn create(path: &Path) -> Result<TrajectoryWriter, CError> {
Self::create_with_format(path, FormatKind::Guess)
}
pub fn create_with_format(path: &Path, format: FormatKind) -> Result<TrajectoryWriter, CError> {
let kind = format.resolve(path)?;
let strategy = FormatWriter::create(path, kind)?;
Ok(TrajectoryWriter {
strategy,
frame_count: 0,
})
}
}
impl TrajectoryReader {
pub fn read(&mut self) -> Result<Option<Frame>, CError> {
if self.current_index >= self.size {
return Ok(None);
}
let frame = self.strategy.read()?;
self.current_index += 1;
Ok(Some(frame))
}
pub fn read_frame(&mut self, index: FrameIndex) -> Result<Frame, CError> {
let index = index.value();
let frame = self.strategy.read_at(index)?;
self.current_index = index + 1;
Ok(frame)
}
pub fn read_at(&mut self, index: usize) -> Result<Option<Frame>, CError> {
match self.frame_index(index) {
Some(index) => self.read_frame(index).map(Some),
None => Ok(None),
}
}
#[must_use]
pub fn frame_index(&self, index: usize) -> Option<FrameIndex> {
FrameIndex::new(index, self.size)
}
pub fn frames(&mut self) -> impl Iterator<Item = Result<Frame, CError>> + '_ {
let indices = 0..self.size;
indices.filter_map(move |i| self.frame_index(i).map(|index| self.read_frame(index)))
}
}
impl TrajectoryWriter {
pub fn write(&mut self, frame: &Frame) -> Result<(), CError> {
self.strategy.write(frame)?;
self.frame_count += 1;
Ok(())
}
pub fn finish(&mut self) -> Result<(), CError> {
self.strategy.finish()
}
#[must_use]
pub fn frame_count(&self) -> usize {
self.frame_count
}
}
impl Drop for TrajectoryWriter {
fn drop(&mut self) {
if let Err(error) = self.finish() {
error!("warning: Failed to finalize trajectory file: {error}");
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
const TEST_XYZ_PATH: &str = "./src/tests-data/xyz/extended.xyz";
#[test]
fn test_trajectory_open() {
let path = PathBuf::from(TEST_XYZ_PATH);
let reader = Trajectory::open(&path).unwrap();
assert_eq!(reader.size, 3);
}
#[test]
fn test_trajectory_with_format() {
let path = PathBuf::from(TEST_XYZ_PATH);
let reader = Trajectory::open_with_format(&path, FormatKind::XYZ).unwrap();
assert_eq!(reader.size, 3);
}
#[test]
fn test_read_frame() {
let path = PathBuf::from(TEST_XYZ_PATH);
let mut reader = Trajectory::open(&path).unwrap();
let idx0 = reader.frame_index(0).unwrap();
let frame0 = reader.read_frame(idx0).unwrap();
assert_eq!(frame0.size(), 192);
let idx1 = reader.frame_index(1).unwrap();
let frame1 = reader.read_frame(idx1).unwrap();
assert_eq!(frame1.size(), 62);
}
#[test]
fn test_invalid_frame_index() {
let path = PathBuf::from(TEST_XYZ_PATH);
let reader = Trajectory::open(&path).unwrap();
assert!(reader.frame_index(4).is_none());
}
#[test]
fn test_read_at() {
let path = PathBuf::from(TEST_XYZ_PATH);
let mut reader = Trajectory::open(&path).unwrap();
let frame0 = reader.read_at(0).unwrap().unwrap();
assert_eq!(frame0.size(), 192);
let frame1 = reader.read_at(1).unwrap().unwrap();
assert_eq!(frame1.size(), 62);
let invalid_frame = reader.read_at(4).unwrap();
assert!(invalid_frame.is_none());
}
}