use crate::types::{
ConFrame, SECTION_ENERGIES, SECTION_FORCES, SECTION_VELOCITIES, encode_fixed_bitmask, meta,
};
use serde_json::json;
use std::fs::File;
use std::io::{self, BufWriter, Write};
use std::path::Path;
const DEFAULT_FLOAT_PRECISION: usize = 6;
pub struct ConFrameWriter<W: Write> {
writer: BufWriter<W>,
precision: usize,
metadata_cache: Option<MetadataCacheEntry>,
}
#[derive(Debug)]
struct MetadataCacheEntry {
spec_version: u32,
has_velocities: bool,
has_forces: bool,
has_energies: bool,
metadata: std::collections::BTreeMap<String, serde_json::Value>,
serialized: String,
}
impl MetadataCacheEntry {
fn matches(
&self,
spec_version: u32,
has_velocities: bool,
has_forces: bool,
has_energies: bool,
metadata: &std::collections::BTreeMap<String, serde_json::Value>,
) -> bool {
self.spec_version == spec_version
&& self.has_velocities == has_velocities
&& self.has_forces == has_forces
&& self.has_energies == has_energies
&& &self.metadata == metadata
}
}
impl<W: Write> ConFrameWriter<W> {
pub fn new(writer: W) -> Self {
Self {
writer: BufWriter::new(writer),
precision: DEFAULT_FLOAT_PRECISION,
metadata_cache: None,
}
}
pub fn with_precision(writer: W, precision: usize) -> Self {
Self {
writer: BufWriter::new(writer),
precision,
metadata_cache: None,
}
}
pub fn write_frame(&mut self, frame: &ConFrame) -> io::Result<()> {
let prec = self.precision;
writeln!(self.writer, "{}", frame.header.prebox_header.user)?;
let spec_version = frame.header.spec_version;
let has_vel = frame.has_velocities();
let has_frc = frame.has_forces();
let has_eng = frame.has_energies();
let cache_hit = self
.metadata_cache
.as_ref()
.is_some_and(|c| c.matches(spec_version, has_vel, has_frc, has_eng, &frame.header.metadata));
if !cache_hit {
let mut meta_obj = serde_json::Map::new();
meta_obj.insert(
meta::CON_SPEC_VERSION.into(),
json!(spec_version),
);
let mut sections = Vec::new();
if has_vel {
sections.push(json!(SECTION_VELOCITIES));
}
if has_frc {
sections.push(json!(SECTION_FORCES));
}
if has_eng {
sections.push(json!(SECTION_ENERGIES));
}
let validate = frame
.header
.metadata
.get(meta::VALIDATE)
.and_then(|value| value.as_bool())
.unwrap_or(false);
if !sections.is_empty() || validate {
meta_obj.insert(meta::SECTIONS.into(), json!(sections));
}
for (k, v) in &frame.header.metadata {
if k == meta::CON_SPEC_VERSION || k == meta::SECTIONS {
continue;
}
meta_obj.insert(k.clone(), v.clone());
}
let serialized = serde_json::Value::Object(meta_obj).to_string();
self.metadata_cache = Some(MetadataCacheEntry {
spec_version,
has_velocities: has_vel,
has_forces: has_frc,
has_energies: has_eng,
metadata: frame.header.metadata.clone(),
serialized,
});
}
let cached = self
.metadata_cache
.as_ref()
.expect("metadata_cache populated above");
writeln!(self.writer, "{}", cached.serialized)?;
writeln!(
self.writer,
"{1:.0$} {2:.0$} {3:.0$}",
prec, frame.header.boxl[0], frame.header.boxl[1], frame.header.boxl[2]
)?;
writeln!(
self.writer,
"{1:.0$} {2:.0$} {3:.0$}",
prec, frame.header.angles[0], frame.header.angles[1], frame.header.angles[2]
)?;
writeln!(self.writer, "{}", frame.header.postbox_header[0])?;
writeln!(self.writer, "{}", frame.header.postbox_header[1])?;
writeln!(self.writer, "{}", frame.header.natm_types)?;
let natms_str: Vec<String> = frame
.header
.natms_per_type
.iter()
.map(|n| n.to_string())
.collect();
writeln!(self.writer, "{}", natms_str.join(" "))?;
let masses_str: Vec<String> = frame
.header
.masses_per_type
.iter()
.map(|m| format!("{:.1$}", m, prec))
.collect();
writeln!(self.writer, "{}", masses_str.join(" "))?;
let mut atom_idx_offset = 0;
for (type_idx, &num_atoms_in_type) in frame.header.natms_per_type.iter().enumerate() {
let symbol = &frame.atom_data[atom_idx_offset].symbol;
writeln!(self.writer, "{}", symbol)?;
writeln!(self.writer, "Coordinates of Component {}", type_idx + 1)?;
for i in 0..num_atoms_in_type {
let atom = &frame.atom_data[atom_idx_offset + i];
writeln!(
self.writer,
"{x:.prec$} {y:.prec$} {z:.prec$} {fixed_flag} {atom_id}",
prec = prec,
x = atom.x,
y = atom.y,
z = atom.z,
fixed_flag = encode_fixed_bitmask(atom.fixed),
atom_id = atom.atom_id
)?;
}
atom_idx_offset += num_atoms_in_type;
}
if frame.has_velocities() {
writeln!(self.writer)?;
let mut vel_idx_offset = 0;
for (type_idx, &num_atoms_in_type) in frame.header.natms_per_type.iter().enumerate() {
let symbol = &frame.atom_data[vel_idx_offset].symbol;
writeln!(self.writer, "{}", symbol)?;
writeln!(self.writer, "Velocities of Component {}", type_idx + 1)?;
for i in 0..num_atoms_in_type {
let atom = &frame.atom_data[vel_idx_offset + i];
let [vx, vy, vz] = atom.velocity.unwrap_or([0.0; 3]);
writeln!(
self.writer,
"{vx:.prec$} {vy:.prec$} {vz:.prec$} {fixed_flag} {atom_id}",
prec = prec,
fixed_flag = encode_fixed_bitmask(atom.fixed),
atom_id = atom.atom_id
)?;
}
vel_idx_offset += num_atoms_in_type;
}
}
if frame.has_forces() {
writeln!(self.writer)?;
let mut force_idx_offset = 0;
for (type_idx, &num_atoms_in_type) in frame.header.natms_per_type.iter().enumerate() {
let symbol = &frame.atom_data[force_idx_offset].symbol;
writeln!(self.writer, "{}", symbol)?;
writeln!(self.writer, "Forces of Component {}", type_idx + 1)?;
for i in 0..num_atoms_in_type {
let atom = &frame.atom_data[force_idx_offset + i];
let [fx, fy, fz] = atom.force.unwrap_or([0.0; 3]);
writeln!(
self.writer,
"{fx:.prec$} {fy:.prec$} {fz:.prec$} {fixed_flag} {atom_id}",
prec = prec,
fixed_flag = encode_fixed_bitmask(atom.fixed),
atom_id = atom.atom_id
)?;
}
force_idx_offset += num_atoms_in_type;
}
}
if frame.has_energies() {
writeln!(self.writer)?;
let mut energy_idx_offset = 0;
for (type_idx, &num_atoms_in_type) in frame.header.natms_per_type.iter().enumerate() {
let symbol = &frame.atom_data[energy_idx_offset].symbol;
writeln!(self.writer, "{}", symbol)?;
writeln!(self.writer, "Energies of Component {}", type_idx + 1)?;
for i in 0..num_atoms_in_type {
let atom = &frame.atom_data[energy_idx_offset + i];
let e = atom.energy.unwrap_or(0.0);
writeln!(
self.writer,
"{e:.prec$} {fixed_flag} {atom_id}",
prec = prec,
fixed_flag = encode_fixed_bitmask(atom.fixed),
atom_id = atom.atom_id
)?;
}
energy_idx_offset += num_atoms_in_type;
}
}
Ok(())
}
pub fn extend<'a>(&mut self, frames: impl Iterator<Item = &'a ConFrame>) -> io::Result<()> {
for frame in frames {
self.write_frame(frame)?;
}
Ok(())
}
}
impl ConFrameWriter<File> {
pub fn from_path<P: AsRef<Path>>(path: P) -> io::Result<Self> {
let file = File::create(path)?;
Ok(Self::new(file))
}
pub fn from_path_with_precision<P: AsRef<Path>>(path: P, precision: usize) -> io::Result<Self> {
let file = File::create(path)?;
Ok(Self::with_precision(file, precision))
}
}
impl ConFrameWriter<flate2::write::GzEncoder<File>> {
pub fn from_path_gzip<P: AsRef<Path>>(path: P) -> io::Result<Self> {
let encoder = crate::compression::gzip_writer(path.as_ref())?;
Ok(Self::new(encoder))
}
pub fn from_path_gzip_with_precision<P: AsRef<Path>>(
path: P,
precision: usize,
) -> io::Result<Self> {
let encoder = crate::compression::gzip_writer(path.as_ref())?;
Ok(Self::with_precision(encoder, precision))
}
}
#[cfg(feature = "zstd")]
impl ConFrameWriter<zstd::stream::write::AutoFinishEncoder<'static, File>> {
pub fn from_path_zstd<P: AsRef<Path>>(path: P) -> io::Result<Self> {
let encoder = crate::compression::zstd_writer(path.as_ref())?;
Ok(Self::new(encoder))
}
pub fn from_path_zstd_with_precision<P: AsRef<Path>>(
path: P,
precision: usize,
) -> io::Result<Self> {
let encoder = crate::compression::zstd_writer(path.as_ref())?;
Ok(Self::with_precision(encoder, precision))
}
}