1use std::fs::File;
2use std::io::{self, Read, Seek, SeekFrom};
3use std::{cell::Cell, path::Path};
4
5use glam::{Mat3, Vec3};
6use reader::read_nbytes;
7
8use crate::buffer::{Buffer, UnBuffered};
9use crate::reader::{
10 read_boxvec, read_compressed_positions, read_f32, read_f32s, read_i32, read_u32,
11};
12use crate::selection::{AtomSelection, FrameSelection};
13
14pub mod buffer;
15pub mod reader;
16pub mod selection;
17
18pub const XTC_1995_MAX_NATOMS: usize = 298261617;
20
21thread_local! {
22 static SCRATCH: Cell<Vec<u8>> = const { Cell::new(Vec::new()) };
24}
25
26pub type BoxVec = Mat3;
27
28#[repr(i32)]
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum Magic {
31 Xtc1995 = 1995,
32 Xtc2023 = 2023,
33}
34
35impl Magic {
36 pub const XTC_1995: i32 = Magic::Xtc1995 as _;
37 pub const XTC_2023: i32 = Magic::Xtc2023 as _;
38
39 fn to_be_bytes(&self) -> [u8; 4] {
40 (*self as i32).to_be_bytes()
41 }
42}
43
44impl TryFrom<i32> for Magic {
45 type Error = String;
46
47 fn try_from(value: i32) -> Result<Self, Self::Error> {
48 match value {
49 Magic::XTC_1995 => Ok(Self::Xtc1995),
50 Magic::XTC_2023 => Ok(Self::Xtc2023),
51 unknown => Err(format!(
52 "found invalid magic number '{unknown}' ({unknown:#0x}), {} and {} are supported",
53 Self::XTC_1995,
54 Self::XTC_2023
55 )),
56 }
57 }
58}
59
60impl std::fmt::Display for Magic {
61 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62 write!(f, "{}", *self as i32)
63 }
64}
65
66pub struct Header {
68 pub magic: Magic,
69 pub natoms: usize,
70 pub step: u32,
71 pub time: f32,
72 pub boxvec: Mat3,
73 pub natoms_repeated: usize,
74}
75
76impl Header {
77 pub const SIZE: usize = 4 * (5 + 9);
78
79 pub fn read(file: &mut impl Read) -> io::Result<Self> {
80 let magic = Magic::try_from(read_i32(file)?)
81 .map_err(|err| io::Error::other(format!("could not read header: {err}")))?;
82 let natoms: usize = read_u32(file)?
83 .try_into()
84 .map_err(|err| io::Error::other(format!("could not read natoms: {err}")))?;
85 let step: u32 = read_u32(file)?;
86 let time = read_f32(file)?;
87
88 let boxvec = read_boxvec(file)?;
90 let natoms_repeated = read_u32(file)?
91 .try_into()
92 .map_err(|err| io::Error::other(format!("could not read second natoms: {err}")))?;
93 assert_eq!(natoms, natoms_repeated);
94
95 Ok(Header {
96 magic,
97 natoms,
98 step,
99 time,
100 boxvec,
101 natoms_repeated,
102 })
103 }
104
105 pub fn to_be_bytes(&self) -> [u8; Self::SIZE] {
106 let mut bytes = Vec::new();
107 bytes.extend(self.magic.to_be_bytes()); let natoms = u32::try_from(self.natoms).unwrap().to_be_bytes();
110 bytes.extend(natoms); bytes.extend(self.step.to_be_bytes()); bytes.extend(self.time.to_be_bytes()); bytes.extend(
114 self.boxvec
115 .to_cols_array()
116 .map(f32::to_be_bytes)
117 .iter()
118 .flatten(),
119 ); assert_eq!(self.natoms, self.natoms_repeated);
121 bytes.extend(natoms); bytes.try_into().unwrap()
124 }
125}
126
127#[derive(Debug, Default, Clone, PartialEq)]
128pub struct Frame {
129 pub step: u32,
130 pub time: f32,
132 pub boxvec: BoxVec,
133 pub precision: f32,
134 pub positions: Vec<f32>,
135}
136
137impl Frame {
138 pub fn coords(&self) -> impl Iterator<Item = Vec3> + '_ {
140 self.positions.chunks_exact(3).map(Vec3::from_slice)
141 }
142
143 pub fn natoms(&self) -> usize {
145 let npos = self.positions.len();
146 assert_eq!(
147 npos % 3,
148 0,
149 "the number of single positions in a frame must always be a multiple of 3"
150 );
151 npos / 3
152 }
153}
154
155#[doc(hidden)]
157pub fn padding(n: usize) -> usize {
158 (4 - (n % 4)) % 4
159}
160
161#[doc(hidden)]
167pub fn read_positions<'s, 'r, B: buffer::Buffered<'s, 'r, R>, R: Read>(
168 file: &'r mut R,
169 header_natoms: usize,
170 scratch: &'s mut Vec<u8>,
171 frame: &mut Frame,
172 atom_selection: &AtomSelection,
173 magic: Magic,
174) -> io::Result<usize> {
175 let natoms_selected = atom_selection.natoms_selected(header_natoms);
177
178 frame.positions.resize(natoms_selected * 3, f32::NAN);
180 frame.precision = read_f32(file)?;
181 read_compressed_positions::<B, R>(
182 file,
183 header_natoms,
184 &mut frame.positions,
185 frame.precision,
186 scratch,
187 atom_selection,
188 magic,
189 )
190}
191
192#[derive(Debug, Clone)]
193pub struct XTCReader<R> {
194 pub file: R,
195 pub step: usize,
196}
197
198impl XTCReader<std::fs::File> {
199 pub fn open<P: AsRef<Path>>(path: P) -> io::Result<Self> {
200 let file = std::fs::File::open(path)?;
201 Ok(Self::new(file))
202 }
203}
204
205impl<R: Read> XTCReader<R> {
206 pub fn new(reader: R) -> Self {
207 Self {
208 file: reader,
209 step: 0,
210 }
211 }
212
213 pub fn read_header(&mut self) -> io::Result<Header> {
217 Header::read(&mut self.file)
218 }
219
220 pub fn read_smol_positions(
231 &mut self,
232 natoms: usize,
233 frame: &mut Frame,
234 atom_selection: &AtomSelection,
235 ) -> io::Result<usize> {
236 assert!(
237 natoms <= 9,
238 "only read uncomprossed positions when the number of atoms is 9 or less"
239 );
240
241 frame.positions.resize(natoms * 3, 0.0);
243 let mut buf = [0.0; 9 * 3]; let buf = &mut buf[..natoms * 3];
245 read_f32s(&mut self.file, buf)?;
246 frame.positions.clear();
247 frame.positions.extend(
248 buf.chunks_exact(3)
249 .enumerate()
250 .filter_map(|(idx, pos): (usize, &[f32])| -> Option<[f32; 3]> {
251 if atom_selection.is_included(idx).unwrap_or_default() {
252 Some(pos.try_into().unwrap())
253 } else {
254 None
255 }
256 })
257 .flatten(),
258 );
259 Ok(buf.len() * std::mem::size_of::<f32>())
264 }
265
266 pub fn read_all_frames(&mut self) -> io::Result<Box<[Frame]>> {
271 let mut frames = Vec::new();
272 loop {
273 let mut frame = Frame::default();
274 if let Err(err) = self.read_frame(&mut frame) {
275 match err.kind() {
276 io::ErrorKind::UnexpectedEof => break,
278 _ => Err(err)?,
280 }
281 }
282 frames.push(frame);
283 }
284 Ok(frames.into_boxed_slice())
285 }
286
287 pub fn read_frame(&mut self, frame: &mut Frame) -> io::Result<()> {
289 self.read_frame_with_selection(frame, &AtomSelection::All)
290 }
291
292 pub fn read_frame_with_selection(
294 &mut self,
295 frame: &mut Frame,
296 atom_selection: &AtomSelection,
297 ) -> io::Result<()> {
298 let mut scratch = SCRATCH.take();
300 self.read_frame_with_scratch(frame, &mut scratch, atom_selection)
301 }
302
303 pub fn read_frame_with_scratch(
317 &mut self,
318 frame: &mut Frame,
319 scratch: &mut Vec<u8>,
320 atom_selection: &AtomSelection,
321 ) -> io::Result<()> {
322 self.read_frame_with_scratch_impl::<UnBuffered>(frame, scratch, atom_selection)
323 }
324
325 fn read_frame_with_scratch_impl<'s, 'r, B: buffer::Buffered<'s, 'r, R>>(
327 &'r mut self,
328 frame: &mut Frame,
329 scratch: &'s mut Vec<u8>,
330 atom_selection: &AtomSelection,
331 ) -> io::Result<()> {
332 let header = self.read_header()?;
334
335 if header.natoms <= 9 {
337 self.read_smol_positions(header.natoms, frame, atom_selection)?;
338 } else {
339 read_positions::<B, R>(
340 &mut self.file,
341 header.natoms,
342 scratch,
343 frame,
344 atom_selection,
345 header.magic,
346 )?;
347 }
348
349 self.step += 1;
350
351 frame.step = header.step;
352 frame.time = header.time;
353 frame.boxvec = header.boxvec;
354
355 Ok(())
356 }
357}
358
359impl XTCReader<File> {
360 pub fn home(&mut self) -> io::Result<()> {
364 self.file.seek(SeekFrom::Start(0))?;
365 self.step = 0;
366 Ok(())
367 }
368
369 pub fn determine_offsets_exclusive(&mut self, until: Option<usize>) -> io::Result<Box<[u64]>> {
381 let file = &mut self.file;
382 let start_pos = file.stream_position()?;
384
385 let mut offsets = Vec::new();
386
387 while until.map_or(true, |until| offsets.len() < until) {
388 let header = match Header::read(file) {
389 Ok(header) => header,
390 Err(err) if err.kind() == io::ErrorKind::UnexpectedEof => break,
391 Err(err) => Err(err)?,
392 };
393
394 let skip = if header.natoms <= 9 {
395 header.natoms as u64 * 3 * 4
398 } else {
399 file.seek(SeekFrom::Current(32))?;
401 let nbytes = read_nbytes(file, header.magic)? as u64;
404 nbytes + padding(nbytes as usize) as u64
405 };
406 let offset = file.seek(SeekFrom::Current(skip as i64))?;
407 offsets.push(offset);
408 }
409
410 file.seek(SeekFrom::Start(start_pos))?;
412
413 Ok(offsets.into_boxed_slice())
414 }
415
416 pub fn determine_offsets(&mut self, until: Option<usize>) -> io::Result<Box<[u64]>> {
428 let mut offsets = vec![0];
429 let exclusive = self.determine_offsets_exclusive(until)?;
430 offsets.extend(exclusive.iter().take(exclusive.len().saturating_sub(1)));
431 Ok(offsets.into_boxed_slice())
432 }
433
434 pub fn determine_frame_sizes(&mut self, until: Option<usize>) -> io::Result<Box<[u64]>> {
440 let starts = self.determine_offsets_exclusive(until)?;
441 let ends = starts.iter().clone().skip(1);
442 Ok(starts
443 .iter()
444 .zip(ends)
445 .map(|(s, e)| e - s)
446 .collect::<Vec<_>>()
447 .into_boxed_slice())
448 }
449
450 pub fn read_frame_at_offset<const BUFFERED: bool>(
461 &mut self,
462 frame: &mut Frame,
463 offset: u64,
464 atom_selection: &AtomSelection,
465 ) -> io::Result<()> {
466 self.file.seek(SeekFrom::Start(offset))?;
467 match BUFFERED {
468 false => self.read_frame_with_selection(frame, atom_selection),
469 true => self.read_frame_with_selection_buffered(frame, atom_selection),
470 }
471 }
472
473 pub fn read_frames<const BUFFERED: bool>(
488 &mut self,
489 frames: &mut impl Extend<Frame>,
490 frame_selection: &FrameSelection,
491 atom_selection: &AtomSelection,
492 ) -> io::Result<usize> {
493 let offsets = self.determine_offsets(frame_selection.until())?;
494 let mut n = 0;
495 for (idx, &offset) in offsets.iter().enumerate() {
496 match frame_selection.is_included(idx) {
497 Some(true) => {}
498 Some(false) => continue,
499 None => break,
500 }
501 let mut frame = Frame::default();
502 self.read_frame_at_offset::<BUFFERED>(&mut frame, offset, atom_selection)?;
503 frames.extend(Some(frame));
504 n += 1;
505 }
506
507 Ok(n)
508 }
509
510 pub fn read_frame_with_selection_buffered(
512 &mut self,
513 frame: &mut Frame,
514 atom_selection: &AtomSelection,
515 ) -> io::Result<()> {
516 let mut scratch = SCRATCH.take();
518 self.read_frame_with_scratch_buffered(frame, &mut scratch, atom_selection)
519 }
520
521 pub fn read_frame_with_scratch_buffered(
535 &mut self,
536 frame: &mut Frame,
537 scratch: &mut Vec<u8>,
538 atom_selection: &AtomSelection,
539 ) -> io::Result<()> {
540 self.read_frame_with_scratch_impl::<Buffer>(frame, scratch, atom_selection)
541 }
542}