Skip to main content

readcon_core/
writer.rs

1use crate::types::ConFrame;
2use std::fs::File;
3use std::io::{self, BufWriter, Write};
4use std::path::Path;
5
6/// The floating-point precision used for writing coordinates, cell dimensions, and masses.
7const FLOAT_PRECISION: usize = 6;
8/// Always 0 or 1
9/// The value used to indicate a fixed atom in the output file.
10const FIXED_ATOM_FLAG: usize = 1;
11/// The value used to indicate a non-fixed (free) atom in the output file.
12const FREE_ATOM_FLAG: usize = 0;
13
14/// A writer that can serialize and write `ConFrame` objects to any output stream.
15///
16/// This struct encapsulates a writer (like a file) and provides a high-level API
17/// for writing simulation frames in the `.con` format.
18///
19/// # Example
20/// ```no_run
21/// # use std::fs::File;
22/// # use readcon_core::types::ConFrame;
23/// # use readcon_core::writer::ConFrameWriter;
24/// # let frames: Vec<ConFrame> = Vec::new();
25/// let mut writer = ConFrameWriter::from_path("output.con").unwrap();
26/// writer.extend(frames.iter()).unwrap();
27/// ```
28pub struct ConFrameWriter<W: Write> {
29    writer: BufWriter<W>,
30}
31
32// General implementation for any type that implements `Write`.
33impl<W: Write> ConFrameWriter<W> {
34    /// Creates a new `ConFrameWriter` that wraps a given writer.
35    ///
36    /// # Arguments
37    ///
38    /// * `writer` - Any type that implements `std::io::Write`, e.g., a `File`.
39    pub fn new(writer: W) -> Self {
40        Self {
41            writer: BufWriter::new(writer),
42        }
43    }
44
45    /// Writes a single `ConFrame` to the output stream.
46    pub fn write_frame(&mut self, frame: &ConFrame) -> io::Result<()> {
47        // --- Write the 9-line Header ---
48        writeln!(self.writer, "{}", frame.header.prebox_header[0])?;
49        writeln!(self.writer, "{}", frame.header.prebox_header[1])?;
50        writeln!(
51            self.writer,
52            "{1:.0$} {2:.0$} {3:.0$}",
53            FLOAT_PRECISION, frame.header.boxl[0], frame.header.boxl[1], frame.header.boxl[2]
54        )?;
55        writeln!(
56            self.writer,
57            "{1:.0$} {2:.0$} {3:.0$}",
58            FLOAT_PRECISION, frame.header.angles[0], frame.header.angles[1], frame.header.angles[2]
59        )?;
60        writeln!(self.writer, "{}", frame.header.postbox_header[0])?;
61        writeln!(self.writer, "{}", frame.header.postbox_header[1])?;
62        writeln!(self.writer, "{}", frame.header.natm_types)?;
63
64        let natms_str: Vec<String> = frame
65            .header
66            .natms_per_type
67            .iter()
68            .map(|n| n.to_string())
69            .collect();
70        writeln!(self.writer, "{}", natms_str.join(" "))?;
71
72        let masses_str: Vec<String> = frame
73            .header
74            .masses_per_type
75            .iter()
76            .map(|m| format!("{:.1$}", m, FLOAT_PRECISION))
77            .collect();
78        writeln!(self.writer, "{}", masses_str.join(" "))?;
79
80        // --- Write the Atom Data ---
81        let mut atom_idx_offset = 0;
82        for (type_idx, &num_atoms_in_type) in frame.header.natms_per_type.iter().enumerate() {
83            let symbol = &frame.atom_data[atom_idx_offset].symbol;
84            writeln!(self.writer, "{}", symbol)?;
85            writeln!(self.writer, "Coordinates of Component {}", type_idx + 1)?;
86
87            for i in 0..num_atoms_in_type {
88                let atom = &frame.atom_data[atom_idx_offset + i];
89                writeln!(
90                    self.writer,
91                    "{x:.prec$} {y:.prec$} {z:.prec$} {fixed_flag:.0} {atom_id}",
92                    prec = FLOAT_PRECISION,
93                    x = atom.x,
94                    y = atom.y,
95                    z = atom.z,
96                    fixed_flag = if atom.is_fixed {
97                        FIXED_ATOM_FLAG
98                    } else {
99                        FREE_ATOM_FLAG
100                    },
101                    atom_id = atom.atom_id
102                )?;
103            }
104            atom_idx_offset += num_atoms_in_type;
105        }
106
107        // --- Write optional velocity section ---
108        if frame.has_velocities() {
109            // Blank separator line between coordinates and velocities
110            writeln!(self.writer)?;
111
112            let mut vel_idx_offset = 0;
113            for (type_idx, &num_atoms_in_type) in frame.header.natms_per_type.iter().enumerate() {
114                let symbol = &frame.atom_data[vel_idx_offset].symbol;
115                writeln!(self.writer, "{}", symbol)?;
116                writeln!(self.writer, "Velocities of Component {}", type_idx + 1)?;
117
118                for i in 0..num_atoms_in_type {
119                    let atom = &frame.atom_data[vel_idx_offset + i];
120                    writeln!(
121                        self.writer,
122                        "{vx:.prec$} {vy:.prec$} {vz:.prec$} {fixed_flag:.0} {atom_id}",
123                        prec = FLOAT_PRECISION,
124                        vx = atom.vx.unwrap_or(0.0),
125                        vy = atom.vy.unwrap_or(0.0),
126                        vz = atom.vz.unwrap_or(0.0),
127                        fixed_flag = if atom.is_fixed {
128                            FIXED_ATOM_FLAG
129                        } else {
130                            FREE_ATOM_FLAG
131                        },
132                        atom_id = atom.atom_id
133                    )?;
134                }
135                vel_idx_offset += num_atoms_in_type;
136            }
137        }
138
139        Ok(())
140    }
141
142    /// Writes all frames from an iterator to the output stream.
143    ///
144    /// This is the most convenient way to write a multi-frame file.
145    pub fn extend<'a>(&mut self, frames: impl Iterator<Item = &'a ConFrame>) -> io::Result<()> {
146        for frame in frames {
147            self.write_frame(frame)?;
148        }
149        Ok(())
150    }
151}
152
153// Implementation block specifically for when the writer is a `File`.
154impl ConFrameWriter<File> {
155    /// Creates a new `ConFrameWriter` that writes to a file at the given path.
156    ///
157    /// This is a convenience function that creates the file and wraps it.
158    pub fn from_path<P: AsRef<Path>>(path: P) -> io::Result<Self> {
159        let file = File::create(path)?;
160        Ok(Self::new(file))
161    }
162}