1use crate::types::{
2 ConFrame, SECTION_ENERGIES, SECTION_FORCES, SECTION_VELOCITIES, encode_fixed_bitmask, meta,
3};
4use serde_json::json;
5use std::fs::File;
6use std::io::{self, BufWriter, Write};
7use std::path::Path;
8
9const DEFAULT_FLOAT_PRECISION: usize = 6;
11
12pub struct ConFrameWriter<W: Write> {
27 writer: BufWriter<W>,
28 precision: usize,
29 metadata_cache: Option<MetadataCacheEntry>,
36}
37
38#[derive(Debug)]
39struct MetadataCacheEntry {
40 spec_version: u32,
44 has_velocities: bool,
45 has_forces: bool,
46 has_energies: bool,
47 metadata: std::collections::BTreeMap<String, serde_json::Value>,
48 serialized: String,
50}
51
52impl MetadataCacheEntry {
53 fn matches(
54 &self,
55 spec_version: u32,
56 has_velocities: bool,
57 has_forces: bool,
58 has_energies: bool,
59 metadata: &std::collections::BTreeMap<String, serde_json::Value>,
60 ) -> bool {
61 self.spec_version == spec_version
62 && self.has_velocities == has_velocities
63 && self.has_forces == has_forces
64 && self.has_energies == has_energies
65 && &self.metadata == metadata
66 }
67}
68
69impl<W: Write> ConFrameWriter<W> {
71 pub fn new(writer: W) -> Self {
77 Self {
78 writer: BufWriter::new(writer),
79 precision: DEFAULT_FLOAT_PRECISION,
80 metadata_cache: None,
81 }
82 }
83
84 pub fn with_precision(writer: W, precision: usize) -> Self {
91 Self {
92 writer: BufWriter::new(writer),
93 precision,
94 metadata_cache: None,
95 }
96 }
97
98 pub fn write_frame(&mut self, frame: &ConFrame) -> io::Result<()> {
100 let prec = self.precision;
101
102 writeln!(self.writer, "{}", frame.header.prebox_header.user)?;
104
105 let spec_version = frame.header.spec_version;
113 let has_vel = frame.has_velocities();
114 let has_frc = frame.has_forces();
115 let has_eng = frame.has_energies();
116
117 let cache_hit = self
118 .metadata_cache
119 .as_ref()
120 .is_some_and(|c| c.matches(spec_version, has_vel, has_frc, has_eng, &frame.header.metadata));
121
122 if !cache_hit {
123 let mut meta_obj = serde_json::Map::new();
124 meta_obj.insert(
125 meta::CON_SPEC_VERSION.into(),
126 json!(spec_version),
127 );
128 let mut sections = Vec::new();
129 if has_vel {
130 sections.push(json!(SECTION_VELOCITIES));
131 }
132 if has_frc {
133 sections.push(json!(SECTION_FORCES));
134 }
135 if has_eng {
136 sections.push(json!(SECTION_ENERGIES));
137 }
138 let validate = frame
139 .header
140 .metadata
141 .get(meta::VALIDATE)
142 .and_then(|value| value.as_bool())
143 .unwrap_or(false);
144 if !sections.is_empty() || validate {
145 meta_obj.insert(meta::SECTIONS.into(), json!(sections));
146 }
147 for (k, v) in &frame.header.metadata {
148 if k == meta::CON_SPEC_VERSION || k == meta::SECTIONS {
149 continue;
150 }
151 meta_obj.insert(k.clone(), v.clone());
152 }
153 let serialized = serde_json::Value::Object(meta_obj).to_string();
154 self.metadata_cache = Some(MetadataCacheEntry {
155 spec_version,
156 has_velocities: has_vel,
157 has_forces: has_frc,
158 has_energies: has_eng,
159 metadata: frame.header.metadata.clone(),
160 serialized,
161 });
162 }
163
164 let cached = self
165 .metadata_cache
166 .as_ref()
167 .expect("metadata_cache populated above");
168 writeln!(self.writer, "{}", cached.serialized)?;
169 writeln!(
170 self.writer,
171 "{1:.0$} {2:.0$} {3:.0$}",
172 prec, frame.header.boxl[0], frame.header.boxl[1], frame.header.boxl[2]
173 )?;
174 writeln!(
175 self.writer,
176 "{1:.0$} {2:.0$} {3:.0$}",
177 prec, frame.header.angles[0], frame.header.angles[1], frame.header.angles[2]
178 )?;
179 writeln!(self.writer, "{}", frame.header.postbox_header[0])?;
180 writeln!(self.writer, "{}", frame.header.postbox_header[1])?;
181 writeln!(self.writer, "{}", frame.header.natm_types)?;
182
183 let natms_str: Vec<String> = frame
184 .header
185 .natms_per_type
186 .iter()
187 .map(|n| n.to_string())
188 .collect();
189 writeln!(self.writer, "{}", natms_str.join(" "))?;
190
191 let masses_str: Vec<String> = frame
192 .header
193 .masses_per_type
194 .iter()
195 .map(|m| format!("{:.1$}", m, prec))
196 .collect();
197 writeln!(self.writer, "{}", masses_str.join(" "))?;
198
199 let mut atom_idx_offset = 0;
201 for (type_idx, &num_atoms_in_type) in frame.header.natms_per_type.iter().enumerate() {
202 let symbol = &frame.atom_data[atom_idx_offset].symbol;
203 writeln!(self.writer, "{}", symbol)?;
204 writeln!(self.writer, "Coordinates of Component {}", type_idx + 1)?;
205
206 for i in 0..num_atoms_in_type {
207 let atom = &frame.atom_data[atom_idx_offset + i];
208 writeln!(
209 self.writer,
210 "{x:.prec$} {y:.prec$} {z:.prec$} {fixed_flag} {atom_id}",
211 prec = prec,
212 x = atom.x,
213 y = atom.y,
214 z = atom.z,
215 fixed_flag = encode_fixed_bitmask(atom.fixed),
216 atom_id = atom.atom_id
217 )?;
218 }
219 atom_idx_offset += num_atoms_in_type;
220 }
221
222 if frame.has_velocities() {
224 writeln!(self.writer)?;
226
227 let mut vel_idx_offset = 0;
228 for (type_idx, &num_atoms_in_type) in frame.header.natms_per_type.iter().enumerate() {
229 let symbol = &frame.atom_data[vel_idx_offset].symbol;
230 writeln!(self.writer, "{}", symbol)?;
231 writeln!(self.writer, "Velocities of Component {}", type_idx + 1)?;
232
233 for i in 0..num_atoms_in_type {
234 let atom = &frame.atom_data[vel_idx_offset + i];
235 let [vx, vy, vz] = atom.velocity.unwrap_or([0.0; 3]);
236 writeln!(
237 self.writer,
238 "{vx:.prec$} {vy:.prec$} {vz:.prec$} {fixed_flag} {atom_id}",
239 prec = prec,
240 fixed_flag = encode_fixed_bitmask(atom.fixed),
241 atom_id = atom.atom_id
242 )?;
243 }
244 vel_idx_offset += num_atoms_in_type;
245 }
246 }
247
248 if frame.has_forces() {
250 writeln!(self.writer)?;
252
253 let mut force_idx_offset = 0;
254 for (type_idx, &num_atoms_in_type) in frame.header.natms_per_type.iter().enumerate() {
255 let symbol = &frame.atom_data[force_idx_offset].symbol;
256 writeln!(self.writer, "{}", symbol)?;
257 writeln!(self.writer, "Forces of Component {}", type_idx + 1)?;
258
259 for i in 0..num_atoms_in_type {
260 let atom = &frame.atom_data[force_idx_offset + i];
261 let [fx, fy, fz] = atom.force.unwrap_or([0.0; 3]);
262 writeln!(
263 self.writer,
264 "{fx:.prec$} {fy:.prec$} {fz:.prec$} {fixed_flag} {atom_id}",
265 prec = prec,
266 fixed_flag = encode_fixed_bitmask(atom.fixed),
267 atom_id = atom.atom_id
268 )?;
269 }
270 force_idx_offset += num_atoms_in_type;
271 }
272 }
273
274 if frame.has_energies() {
276 writeln!(self.writer)?;
277
278 let mut energy_idx_offset = 0;
279 for (type_idx, &num_atoms_in_type) in frame.header.natms_per_type.iter().enumerate() {
280 let symbol = &frame.atom_data[energy_idx_offset].symbol;
281 writeln!(self.writer, "{}", symbol)?;
282 writeln!(self.writer, "Energies of Component {}", type_idx + 1)?;
283
284 for i in 0..num_atoms_in_type {
285 let atom = &frame.atom_data[energy_idx_offset + i];
286 let e = atom.energy.unwrap_or(0.0);
287 writeln!(
288 self.writer,
289 "{e:.prec$} {fixed_flag} {atom_id}",
290 prec = prec,
291 fixed_flag = encode_fixed_bitmask(atom.fixed),
292 atom_id = atom.atom_id
293 )?;
294 }
295 energy_idx_offset += num_atoms_in_type;
296 }
297 }
298
299 Ok(())
300 }
301
302 pub fn extend<'a>(&mut self, frames: impl Iterator<Item = &'a ConFrame>) -> io::Result<()> {
306 for frame in frames {
307 self.write_frame(frame)?;
308 }
309 Ok(())
310 }
311}
312
313impl ConFrameWriter<File> {
315 pub fn from_path<P: AsRef<Path>>(path: P) -> io::Result<Self> {
319 let file = File::create(path)?;
320 Ok(Self::new(file))
321 }
322
323 pub fn from_path_with_precision<P: AsRef<Path>>(path: P, precision: usize) -> io::Result<Self> {
325 let file = File::create(path)?;
326 Ok(Self::with_precision(file, precision))
327 }
328}
329
330impl ConFrameWriter<flate2::write::GzEncoder<File>> {
332 pub fn from_path_gzip<P: AsRef<Path>>(path: P) -> io::Result<Self> {
334 let encoder = crate::compression::gzip_writer(path.as_ref())?;
335 Ok(Self::new(encoder))
336 }
337
338 pub fn from_path_gzip_with_precision<P: AsRef<Path>>(
340 path: P,
341 precision: usize,
342 ) -> io::Result<Self> {
343 let encoder = crate::compression::gzip_writer(path.as_ref())?;
344 Ok(Self::with_precision(encoder, precision))
345 }
346}
347
348#[cfg(feature = "zstd")]
351impl ConFrameWriter<zstd::stream::write::AutoFinishEncoder<'static, File>> {
352 pub fn from_path_zstd<P: AsRef<Path>>(path: P) -> io::Result<Self> {
354 let encoder = crate::compression::zstd_writer(path.as_ref())?;
355 Ok(Self::new(encoder))
356 }
357
358 pub fn from_path_zstd_with_precision<P: AsRef<Path>>(
360 path: P,
361 precision: usize,
362 ) -> io::Result<Self> {
363 let encoder = crate::compression::zstd_writer(path.as_ref())?;
364 Ok(Self::with_precision(encoder, precision))
365 }
366}