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 canonical: bool,
34 metadata_cache: Option<MetadataCacheEntry>,
41}
42
43#[derive(Debug)]
44struct MetadataCacheEntry {
45 spec_version: u32,
49 has_velocities: bool,
50 has_forces: bool,
51 has_energies: bool,
52 metadata: std::collections::BTreeMap<String, serde_json::Value>,
53 serialized: String,
55}
56
57impl MetadataCacheEntry {
58 fn matches(
59 &self,
60 spec_version: u32,
61 has_velocities: bool,
62 has_forces: bool,
63 has_energies: bool,
64 metadata: &std::collections::BTreeMap<String, serde_json::Value>,
65 ) -> bool {
66 self.spec_version == spec_version
67 && self.has_velocities == has_velocities
68 && self.has_forces == has_forces
69 && self.has_energies == has_energies
70 && &self.metadata == metadata
71 }
72}
73
74impl<W: Write> ConFrameWriter<W> {
76 pub fn new(writer: W) -> Self {
82 Self {
83 writer: BufWriter::new(writer),
84 precision: DEFAULT_FLOAT_PRECISION,
85 canonical: false,
86 metadata_cache: None,
87 }
88 }
89
90 pub fn with_precision(writer: W, precision: usize) -> Self {
97 Self {
98 writer: BufWriter::new(writer),
99 precision,
100 canonical: false,
101 metadata_cache: None,
102 }
103 }
104
105 pub fn canonical(mut self, on: bool) -> Self {
110 self.set_canonical(on);
111 self
112 }
113
114 pub fn set_canonical(&mut self, on: bool) {
116 self.canonical = on;
117 if on {
118 self.metadata_cache = None;
119 }
120 }
121
122 pub fn is_canonical(&self) -> bool {
124 self.canonical
125 }
126
127 pub fn write_frame(&mut self, frame: &ConFrame) -> io::Result<()> {
129 let prec = self.precision;
130
131 writeln!(self.writer, "{}", frame.header.prebox_header.user)?;
133
134 let spec_version = frame.header.spec_version;
142 let has_vel = frame.has_velocities();
143 let has_frc = frame.has_forces();
144 let has_eng = frame.has_energies();
145
146 let cache_hit = !self.canonical
147 && self
148 .metadata_cache
149 .as_ref()
150 .is_some_and(|c| c.matches(spec_version, has_vel, has_frc, has_eng, &frame.header.metadata));
151
152 if !cache_hit {
153 let mut meta_obj = serde_json::Map::new();
154 meta_obj.insert(
155 meta::CON_SPEC_VERSION.into(),
156 json!(spec_version),
157 );
158 let mut sections = Vec::new();
159 if has_vel {
160 sections.push(json!(SECTION_VELOCITIES));
161 }
162 if has_frc {
163 sections.push(json!(SECTION_FORCES));
164 }
165 if has_eng {
166 sections.push(json!(SECTION_ENERGIES));
167 }
168 let validate = frame
169 .header
170 .metadata
171 .get(meta::VALIDATE)
172 .and_then(|value| value.as_bool())
173 .unwrap_or(false);
174 if !sections.is_empty() || validate {
175 meta_obj.insert(meta::SECTIONS.into(), json!(sections));
176 }
177 for (k, v) in &frame.header.metadata {
179 if k == meta::CON_SPEC_VERSION || k == meta::SECTIONS {
180 continue;
181 }
182 meta_obj.insert(k.clone(), v.clone());
183 }
184 if spec_version >= 3 {
186 let need_default = match meta_obj.get(meta::UNITS) {
187 None => true,
188 Some(u) => crate::units::validate_v3_units_metadata(u).is_err(),
189 };
190 if need_default {
191 meta_obj.insert(
192 meta::UNITS.into(),
193 crate::units::default_v3_units_json(),
194 );
195 }
196 }
197 let serialized = serde_json::Value::Object(meta_obj).to_string();
199 self.metadata_cache = Some(MetadataCacheEntry {
200 spec_version,
201 has_velocities: has_vel,
202 has_forces: has_frc,
203 has_energies: has_eng,
204 metadata: frame.header.metadata.clone(),
205 serialized,
206 });
207 }
208
209 let cached = self
210 .metadata_cache
211 .as_ref()
212 .expect("metadata_cache populated above");
213 writeln!(self.writer, "{}", cached.serialized)?;
214 writeln!(
215 self.writer,
216 "{1:.0$} {2:.0$} {3:.0$}",
217 prec, frame.header.boxl[0], frame.header.boxl[1], frame.header.boxl[2]
218 )?;
219 writeln!(
220 self.writer,
221 "{1:.0$} {2:.0$} {3:.0$}",
222 prec, frame.header.angles[0], frame.header.angles[1], frame.header.angles[2]
223 )?;
224 writeln!(self.writer, "{}", frame.header.postbox_header[0])?;
225 writeln!(self.writer, "{}", frame.header.postbox_header[1])?;
226 writeln!(self.writer, "{}", frame.header.natm_types)?;
227
228 let natms_str: Vec<String> = frame
229 .header
230 .natms_per_type
231 .iter()
232 .map(|n| n.to_string())
233 .collect();
234 writeln!(self.writer, "{}", natms_str.join(" "))?;
235
236 let masses_str: Vec<String> = frame
237 .header
238 .masses_per_type
239 .iter()
240 .map(|m| format!("{:.1$}", m, prec))
241 .collect();
242 writeln!(self.writer, "{}", masses_str.join(" "))?;
243
244 let mut atom_idx_offset = 0;
246 for (type_idx, &num_atoms_in_type) in frame.header.natms_per_type.iter().enumerate() {
247 let symbol = &frame.atom_data[atom_idx_offset].symbol;
248 writeln!(self.writer, "{}", symbol)?;
249 writeln!(self.writer, "Coordinates of Component {}", type_idx + 1)?;
250
251 for i in 0..num_atoms_in_type {
252 let atom = &frame.atom_data[atom_idx_offset + i];
253 writeln!(
254 self.writer,
255 "{x:.prec$} {y:.prec$} {z:.prec$} {fixed_flag} {atom_id}",
256 prec = prec,
257 x = atom.x,
258 y = atom.y,
259 z = atom.z,
260 fixed_flag = encode_fixed_bitmask(atom.fixed),
261 atom_id = atom.atom_id
262 )?;
263 }
264 atom_idx_offset += num_atoms_in_type;
265 }
266
267 if frame.has_velocities() {
269 writeln!(self.writer)?;
271
272 let mut vel_idx_offset = 0;
273 for (type_idx, &num_atoms_in_type) in frame.header.natms_per_type.iter().enumerate() {
274 let symbol = &frame.atom_data[vel_idx_offset].symbol;
275 writeln!(self.writer, "{}", symbol)?;
276 writeln!(self.writer, "Velocities of Component {}", type_idx + 1)?;
277
278 for i in 0..num_atoms_in_type {
279 let atom = &frame.atom_data[vel_idx_offset + i];
280 let [vx, vy, vz] = atom.velocity.unwrap_or([0.0; 3]);
281 writeln!(
282 self.writer,
283 "{vx:.prec$} {vy:.prec$} {vz:.prec$} {fixed_flag} {atom_id}",
284 prec = prec,
285 fixed_flag = encode_fixed_bitmask(atom.fixed),
286 atom_id = atom.atom_id
287 )?;
288 }
289 vel_idx_offset += num_atoms_in_type;
290 }
291 }
292
293 if frame.has_forces() {
295 writeln!(self.writer)?;
297
298 let mut force_idx_offset = 0;
299 for (type_idx, &num_atoms_in_type) in frame.header.natms_per_type.iter().enumerate() {
300 let symbol = &frame.atom_data[force_idx_offset].symbol;
301 writeln!(self.writer, "{}", symbol)?;
302 writeln!(self.writer, "Forces of Component {}", type_idx + 1)?;
303
304 for i in 0..num_atoms_in_type {
305 let atom = &frame.atom_data[force_idx_offset + i];
306 let [fx, fy, fz] = atom.force.unwrap_or([0.0; 3]);
307 writeln!(
308 self.writer,
309 "{fx:.prec$} {fy:.prec$} {fz:.prec$} {fixed_flag} {atom_id}",
310 prec = prec,
311 fixed_flag = encode_fixed_bitmask(atom.fixed),
312 atom_id = atom.atom_id
313 )?;
314 }
315 force_idx_offset += num_atoms_in_type;
316 }
317 }
318
319 if frame.has_energies() {
321 writeln!(self.writer)?;
322
323 let mut energy_idx_offset = 0;
324 for (type_idx, &num_atoms_in_type) in frame.header.natms_per_type.iter().enumerate() {
325 let symbol = &frame.atom_data[energy_idx_offset].symbol;
326 writeln!(self.writer, "{}", symbol)?;
327 writeln!(self.writer, "Energies of Component {}", type_idx + 1)?;
328
329 for i in 0..num_atoms_in_type {
330 let atom = &frame.atom_data[energy_idx_offset + i];
331 let e = atom.energy.unwrap_or(0.0);
332 writeln!(
333 self.writer,
334 "{e:.prec$} {fixed_flag} {atom_id}",
335 prec = prec,
336 fixed_flag = encode_fixed_bitmask(atom.fixed),
337 atom_id = atom.atom_id
338 )?;
339 }
340 energy_idx_offset += num_atoms_in_type;
341 }
342 }
343
344 Ok(())
345 }
346
347 pub fn extend<'a>(&mut self, frames: impl Iterator<Item = &'a ConFrame>) -> io::Result<()> {
351 for frame in frames {
352 self.write_frame(frame)?;
353 }
354 Ok(())
355 }
356}
357
358impl ConFrameWriter<File> {
360 pub fn from_path<P: AsRef<Path>>(path: P) -> io::Result<Self> {
364 let file = File::create(path)?;
365 Ok(Self::new(file))
366 }
367
368 pub fn from_path_with_precision<P: AsRef<Path>>(path: P, precision: usize) -> io::Result<Self> {
370 let file = File::create(path)?;
371 Ok(Self::with_precision(file, precision))
372 }
373}
374
375impl ConFrameWriter<flate2::write::GzEncoder<File>> {
377 pub fn from_path_gzip<P: AsRef<Path>>(path: P) -> io::Result<Self> {
379 let encoder = crate::compression::gzip_writer(path.as_ref())?;
380 Ok(Self::new(encoder))
381 }
382
383 pub fn from_path_gzip_with_precision<P: AsRef<Path>>(
385 path: P,
386 precision: usize,
387 ) -> io::Result<Self> {
388 let encoder = crate::compression::gzip_writer(path.as_ref())?;
389 Ok(Self::with_precision(encoder, precision))
390 }
391}
392
393#[cfg(feature = "zstd")]
396impl ConFrameWriter<zstd::stream::write::AutoFinishEncoder<'static, File>> {
397 pub fn from_path_zstd<P: AsRef<Path>>(path: P) -> io::Result<Self> {
399 let encoder = crate::compression::zstd_writer(path.as_ref())?;
400 Ok(Self::new(encoder))
401 }
402
403 pub fn from_path_zstd_with_precision<P: AsRef<Path>>(
405 path: P,
406 precision: usize,
407 ) -> io::Result<Self> {
408 let encoder = crate::compression::zstd_writer(path.as_ref())?;
409 Ok(Self::with_precision(encoder, precision))
410 }
411}