1use crate::types::{ConFrame, SECTION_FORCES, SECTION_VELOCITIES, encode_fixed_bitmask, meta};
2use serde_json::json;
3use std::fs::File;
4use std::io::{self, BufWriter, Write};
5use std::path::Path;
6
7const DEFAULT_FLOAT_PRECISION: usize = 6;
9
10pub struct ConFrameWriter<W: Write> {
25 writer: BufWriter<W>,
26 precision: usize,
27}
28
29impl<W: Write> ConFrameWriter<W> {
31 pub fn new(writer: W) -> Self {
37 Self {
38 writer: BufWriter::new(writer),
39 precision: DEFAULT_FLOAT_PRECISION,
40 }
41 }
42
43 pub fn with_precision(writer: W, precision: usize) -> Self {
50 Self {
51 writer: BufWriter::new(writer),
52 precision,
53 }
54 }
55
56 pub fn write_frame(&mut self, frame: &ConFrame) -> io::Result<()> {
58 let prec = self.precision;
59
60 writeln!(self.writer, "{}", frame.header.prebox_header.user)?;
62
63 let mut meta_obj = serde_json::Map::new();
66 meta_obj.insert(
67 meta::CON_SPEC_VERSION.into(),
68 json!(frame.header.spec_version),
69 );
70 let mut sections = Vec::new();
72 if frame.has_velocities() {
73 sections.push(json!(SECTION_VELOCITIES));
74 }
75 if frame.has_forces() {
76 sections.push(json!(SECTION_FORCES));
77 }
78 let validate = frame
79 .header
80 .metadata
81 .get(meta::VALIDATE)
82 .and_then(|value| value.as_bool())
83 .unwrap_or(false);
84 if !sections.is_empty() || validate {
85 meta_obj.insert(meta::SECTIONS.into(), json!(sections));
86 }
87 for (k, v) in &frame.header.metadata {
88 if k == meta::CON_SPEC_VERSION || k == meta::SECTIONS {
89 continue;
90 }
91 meta_obj.insert(k.clone(), v.clone());
92 }
93 writeln!(self.writer, "{}", serde_json::Value::Object(meta_obj))?;
94 writeln!(
95 self.writer,
96 "{1:.0$} {2:.0$} {3:.0$}",
97 prec, frame.header.boxl[0], frame.header.boxl[1], frame.header.boxl[2]
98 )?;
99 writeln!(
100 self.writer,
101 "{1:.0$} {2:.0$} {3:.0$}",
102 prec, frame.header.angles[0], frame.header.angles[1], frame.header.angles[2]
103 )?;
104 writeln!(self.writer, "{}", frame.header.postbox_header[0])?;
105 writeln!(self.writer, "{}", frame.header.postbox_header[1])?;
106 writeln!(self.writer, "{}", frame.header.natm_types)?;
107
108 let natms_str: Vec<String> = frame
109 .header
110 .natms_per_type
111 .iter()
112 .map(|n| n.to_string())
113 .collect();
114 writeln!(self.writer, "{}", natms_str.join(" "))?;
115
116 let masses_str: Vec<String> = frame
117 .header
118 .masses_per_type
119 .iter()
120 .map(|m| format!("{:.1$}", m, prec))
121 .collect();
122 writeln!(self.writer, "{}", masses_str.join(" "))?;
123
124 let mut atom_idx_offset = 0;
126 for (type_idx, &num_atoms_in_type) in frame.header.natms_per_type.iter().enumerate() {
127 let symbol = &frame.atom_data[atom_idx_offset].symbol;
128 writeln!(self.writer, "{}", symbol)?;
129 writeln!(self.writer, "Coordinates of Component {}", type_idx + 1)?;
130
131 for i in 0..num_atoms_in_type {
132 let atom = &frame.atom_data[atom_idx_offset + i];
133 writeln!(
134 self.writer,
135 "{x:.prec$} {y:.prec$} {z:.prec$} {fixed_flag} {atom_id}",
136 prec = prec,
137 x = atom.x,
138 y = atom.y,
139 z = atom.z,
140 fixed_flag = encode_fixed_bitmask(atom.fixed),
141 atom_id = atom.atom_id
142 )?;
143 }
144 atom_idx_offset += num_atoms_in_type;
145 }
146
147 if frame.has_velocities() {
149 writeln!(self.writer)?;
151
152 let mut vel_idx_offset = 0;
153 for (type_idx, &num_atoms_in_type) in frame.header.natms_per_type.iter().enumerate() {
154 let symbol = &frame.atom_data[vel_idx_offset].symbol;
155 writeln!(self.writer, "{}", symbol)?;
156 writeln!(self.writer, "Velocities of Component {}", type_idx + 1)?;
157
158 for i in 0..num_atoms_in_type {
159 let atom = &frame.atom_data[vel_idx_offset + i];
160 let [vx, vy, vz] = atom.velocity.unwrap_or([0.0; 3]);
161 writeln!(
162 self.writer,
163 "{vx:.prec$} {vy:.prec$} {vz:.prec$} {fixed_flag} {atom_id}",
164 prec = prec,
165 fixed_flag = encode_fixed_bitmask(atom.fixed),
166 atom_id = atom.atom_id
167 )?;
168 }
169 vel_idx_offset += num_atoms_in_type;
170 }
171 }
172
173 if frame.has_forces() {
175 writeln!(self.writer)?;
177
178 let mut force_idx_offset = 0;
179 for (type_idx, &num_atoms_in_type) in frame.header.natms_per_type.iter().enumerate() {
180 let symbol = &frame.atom_data[force_idx_offset].symbol;
181 writeln!(self.writer, "{}", symbol)?;
182 writeln!(self.writer, "Forces of Component {}", type_idx + 1)?;
183
184 for i in 0..num_atoms_in_type {
185 let atom = &frame.atom_data[force_idx_offset + i];
186 let [fx, fy, fz] = atom.force.unwrap_or([0.0; 3]);
187 writeln!(
188 self.writer,
189 "{fx:.prec$} {fy:.prec$} {fz:.prec$} {fixed_flag} {atom_id}",
190 prec = prec,
191 fixed_flag = encode_fixed_bitmask(atom.fixed),
192 atom_id = atom.atom_id
193 )?;
194 }
195 force_idx_offset += num_atoms_in_type;
196 }
197 }
198
199 Ok(())
200 }
201
202 pub fn extend<'a>(&mut self, frames: impl Iterator<Item = &'a ConFrame>) -> io::Result<()> {
206 for frame in frames {
207 self.write_frame(frame)?;
208 }
209 Ok(())
210 }
211}
212
213impl ConFrameWriter<File> {
215 pub fn from_path<P: AsRef<Path>>(path: P) -> io::Result<Self> {
219 let file = File::create(path)?;
220 Ok(Self::new(file))
221 }
222
223 pub fn from_path_with_precision<P: AsRef<Path>>(path: P, precision: usize) -> io::Result<Self> {
225 let file = File::create(path)?;
226 Ok(Self::with_precision(file, precision))
227 }
228}
229
230impl ConFrameWriter<flate2::write::GzEncoder<File>> {
232 pub fn from_path_gzip<P: AsRef<Path>>(path: P) -> io::Result<Self> {
234 let encoder = crate::compression::gzip_writer(path.as_ref())?;
235 Ok(Self::new(encoder))
236 }
237
238 pub fn from_path_gzip_with_precision<P: AsRef<Path>>(
240 path: P,
241 precision: usize,
242 ) -> io::Result<Self> {
243 let encoder = crate::compression::gzip_writer(path.as_ref())?;
244 Ok(Self::with_precision(encoder, precision))
245 }
246}