use std::io;
use std::path::Path;
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
use crate::structure::{Atom, Structure};
use crate::GRO_INTEGER_LIMIT;
pub trait WriteGro<'a, A: 'a> {
#[must_use]
fn title(&self) -> String;
#[must_use]
fn natoms(&self) -> usize;
#[must_use]
fn atoms(&'a self) -> impl Iterator<Item = &'a A>;
#[must_use]
fn boxvecs(&self) -> String;
#[must_use]
fn format_atom_line(atom: &A) -> String;
#[must_use]
fn format_atom_lines_iter(&'a self) -> impl Iterator<Item = String> {
self.atoms().map(Self::format_atom_line)
}
#[must_use]
fn format_atom_lines(&'a self) -> String {
self.format_atom_lines_iter().collect()
}
fn write(&'a self, writer: &mut impl io::Write) -> io::Result<()> {
writeln!(writer, "{}", self.title())?;
writeln!(writer, "{}", self.natoms())?;
let lines = self.format_atom_lines_iter();
for line in lines {
writer.write_all(line.as_bytes())?;
}
writeln!(writer, "{}", self.boxvecs())
}
fn save_gro<P: AsRef<Path>>(&'a self, path: P) -> io::Result<()> {
let file = std::fs::File::create(path)?;
let mut writer = std::io::BufWriter::new(file);
self.write(&mut writer)
}
}
pub trait WriteGroPar<'a, A>: WriteGro<'a, A>
where
A: 'a,
&'a A: Send,
{
#[must_use]
fn atoms_par(&'a self) -> impl ParallelIterator<Item = &'a A>;
#[must_use]
fn format_atom_lines_par(&'a self) -> String {
self.atoms_par()
.map(|atom| Self::format_atom_line(atom))
.collect()
}
fn write_par(&'a self, writer: &mut impl io::Write) -> io::Result<()> {
writeln!(writer, "{}", self.title())?;
writeln!(writer, "{}", self.natoms())?;
let lines = self.format_atom_lines_par();
write!(writer, "{lines}")?;
writeln!(writer, "{}", self.boxvecs())
}
fn save_gro_par<P: AsRef<Path>>(&'a self, path: P) -> io::Result<()> {
let mut file = std::fs::File::create(path)?;
self.write_par(&mut file)
}
}
impl<'a> WriteGro<'a, Atom> for Structure {
fn title(&self) -> String {
self.title.clone()
}
fn natoms(&self) -> usize {
self.natoms()
}
fn atoms(&'a self) -> impl Iterator<Item = &'a Atom> {
self.atoms.iter()
}
fn boxvecs(&self) -> String {
self.boxvecs.to_string()
}
fn format_atom_line(atom: &Atom) -> String {
format_atom_line(
atom.resnum,
atom.resname,
atom.atomname,
atom.atomnum,
atom.position.to_array(),
match atom.velocity.to_array() {
[0.0, 0.0, 0.0] => None,
vel => Some(vel),
},
)
}
}
impl<'a> WriteGroPar<'a, Atom> for Structure {
fn atoms_par(&'a self) -> impl ParallelIterator<Item = &'a Atom> {
self.atoms.par_iter()
}
}
#[must_use]
pub fn format_atom_line(
resnum: u32,
resname: impl ToString,
atomname: impl ToString,
atomnum: u32,
position: [f32; 3],
velocity: Option<[f32; 3]>,
) -> String {
let atomnum = atomnum % GRO_INTEGER_LIMIT;
let resnum = resnum % GRO_INTEGER_LIMIT;
let resname = resname.to_string();
let atomname = atomname.to_string();
assert!(resname.len() <= 5);
assert!(atomname.len() <= 5);
let [x, y, z] = position;
if let Some([vx, vy, vz]) = velocity {
format!("{resnum:>5}{resname:<5}{atomname:>5}{atomnum:>5}{x:8.3}{y:8.3}{z:8.3}{vx:8.4}{vy:8.4}{vz:8.4}\n")
} else {
format!("{resnum:>5}{resname:<5}{atomname:>5}{atomnum:>5}{x:8.3}{y:8.3}{z:8.3}\n")
}
}
#[cfg(test)]
mod tests {
use std::io::{self, Read};
use super::*;
use crate::reader::ReadGro;
#[test]
fn in_out() -> io::Result<()> {
let mut gro = Vec::new();
std::fs::File::open(crate::tests::PATH)?.read_to_end(&mut gro)?;
let structure = Structure::read(gro.as_slice())?;
let mut out = Vec::new();
structure.write(&mut out)?;
let processed = Structure::read(out.as_slice())?;
assert_eq!(structure.title, processed.title);
assert_eq!(structure.natoms(), processed.natoms());
assert_eq!(structure.boxvecs, processed.boxvecs);
for idx in 0..structure.natoms() {
let original = structure.atoms[idx];
let processed = processed.atoms[idx];
assert_eq!(original, processed, "atom {idx} does not match");
}
Ok(())
}
#[test]
fn in_out_par() -> io::Result<()> {
let structure = Structure::open_gro(crate::tests::PATH)?;
let mut out_par = Vec::new();
structure.write_par(&mut out_par)?;
let processed_par = Structure::read(out_par.as_slice())?;
assert_eq!(structure.title, processed_par.title);
assert_eq!(structure.natoms(), processed_par.natoms());
assert_eq!(structure.boxvecs, processed_par.boxvecs);
for idx in 0..structure.natoms() {
let original = structure.atoms[idx];
let processed_par = processed_par.atoms[idx];
assert_eq!(original, processed_par, "atom {idx} does not match");
}
Ok(())
}
#[test]
fn seq_and_par() -> io::Result<()> {
let structure = Structure::open_gro(crate::tests::PATH)?;
let mut out = Vec::new();
structure.write(&mut out)?;
let mut out_par = Vec::new();
structure.write_par(&mut out_par)?;
let processed = Structure::read(out.as_slice())?;
let processed_par = Structure::read(out_par.as_slice())?;
assert_eq!(processed.title, processed_par.title);
assert_eq!(processed.natoms(), processed_par.natoms());
assert_eq!(processed.boxvecs, processed_par.boxvecs);
for idx in 0..structure.natoms() {
let processed = processed.atoms[idx];
let processed_par = processed_par.atoms[idx];
assert_eq!(processed, processed_par, "atom {idx} does not match");
}
Ok(())
}
}