Skip to main content

readcon_db/
export_xyz.rs

1//! Extended-XYZ writer for optional on-disk handoff (no ASE).
2//! Prefer CON + readcon-core; chemfiles ingress for XYZ *input*.
3
4use std::io::{self, Write};
5
6use readcon_core::types::ConFrame;
7
8/// Write one frame as ASE-compatible extxyz (Lattice, Properties, energy/forces in info/arrays).
9pub fn write_frame_extxyz<W: Write>(w: &mut W, frame: &ConFrame, energy_key: &str) -> io::Result<()> {
10    let n = frame.atom_data.len();
11    writeln!(w, "{n}")?;
12
13    // Cell as 3x3 row-major from lengths/angles (orthorhombic approximation if angles ~90)
14    let (lx, ly, lz) = (
15        frame.header.boxl[0],
16        frame.header.boxl[1],
17        frame.header.boxl[2],
18    );
19    // CON stores cell lengths on header; angles on header.angles — use orthorhombic box for export
20    // (full triclinic can be added; metatrain accepts Lattice=)
21    let lattice = format!("{lx:.10} 0 0 0 {ly:.10} 0 0 0 {lz:.10}");
22
23    let mut energy = None;
24    if let Some(v) = frame.header.metadata.get("energy") {
25        if let Some(f) = v.as_f64() {
26            energy = Some(f);
27        } else if let Some(s) = v.as_str() {
28            energy = s.parse().ok();
29        }
30    }
31
32    let has_forces = frame.atom_data.iter().any(|a| a.force.is_some());
33    let props = if has_forces {
34        "species:S:1:pos:R:3:forces:R:3"
35    } else {
36        "species:S:1:pos:R:3"
37    };
38
39    write!(w, "Lattice=\"{lattice}\" Properties={props} pbc=\"T T T\"")?;
40    if let Some(e) = energy {
41        write!(w, " {energy_key}={e:.10}")?;
42    }
43    writeln!(w)?;
44
45    for a in &frame.atom_data {
46        write!(w, "{:<2} {:16.10} {:16.10} {:16.10}", a.symbol, a.x, a.y, a.z)?;
47        if has_forces {
48            let f = a.force.unwrap_or([0.0; 3]);
49            write!(w, " {:16.10} {:16.10} {:16.10}", f[0], f[1], f[2])?;
50        }
51        writeln!(w)?;
52    }
53    Ok(())
54}
55
56pub fn write_frames_extxyz<W: Write>(
57    w: &mut W,
58    frames: &[ConFrame],
59    energy_key: &str,
60) -> io::Result<()> {
61    for fr in frames {
62        write_frame_extxyz(w, fr, energy_key)?;
63    }
64    Ok(())
65}