taco_format/
header.rs

1//! Header definition and operations for TACO file format
2//!
3//! The header contains critical metadata about the trajectory including:
4//! - Format version
5//! - Number of atoms and frames
6//! - Compression settings
7//! - Simulation parameters
8//! - Atom metadata
9
10use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
11use serde::{Deserialize, Serialize};
12use std::io::{Read, Write};
13
14use crate::compression::CompressionSettings;
15use crate::metadata::{AtomMetadata, SimulationMetadata};
16use crate::{Error, MAGIC, Result, VERSION};
17
18/// File format header containing metadata about the trajectory
19#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct Header {
21    /// The TACO format version
22    pub version: String,
23
24    /// Number of atoms in the trajectory
25    pub num_atoms: u32,
26
27    /// Total number of frames in the trajectory
28    pub num_frames: u64,
29
30    /// Time step between frames in picoseconds
31    pub time_step: f64,
32
33    /// Periodic boundary conditions along [x, y, z]
34    pub pbc: [bool; 3],
35
36    /// Interval between full (non-delta) frames
37    pub full_frame_interval: u32,
38
39    /// Simulation metadata (temperature, ensemble, etc.)
40    pub simulation_metadata: SimulationMetadata,
41
42    /// Atom-specific metadata (masses, names, elements, etc.)
43    pub atom_metadata: AtomMetadata,
44
45    /// Compression settings used for the trajectory
46    pub compression_settings: CompressionSettings,
47
48    /// Offset to the start of the frame index table (in bytes)
49    pub frame_index_offset: u64,
50
51    /// Flags indicating which data components are present
52    /// Bit 0: Positions
53    /// Bit 1: Velocities
54    /// Bit 2: Forces
55    /// Bit 3: Box dimensions
56    /// Bits 4-31: Reserved for future use
57    pub data_flags: u32,
58}
59
60impl Header {
61    /// Create a new header with default values
62    pub fn new(
63        num_atoms: u32,
64        time_step: f64,
65        simulation_metadata: SimulationMetadata,
66        atom_metadata: AtomMetadata,
67        compression_settings: CompressionSettings,
68    ) -> Self {
69        Header {
70            version: VERSION.to_string(),
71            num_atoms,
72            num_frames: 0, // Will be updated during writing
73            time_step,
74            pbc: [true; 3],           // Default: periodic in all directions
75            full_frame_interval: 100, // Default: store a full frame every 100 frames
76            simulation_metadata,
77            atom_metadata,
78            compression_settings,
79            frame_index_offset: 0, // Will be updated during writing
80            data_flags: 0x0F,      // Default: include all data types
81        }
82    }
83
84    /// Write the header to a writer
85    pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
86        // Write magic number to identify the file format
87        writer.write_all(&MAGIC)?;
88
89        // Serialize header to JSON for maximum compatibility and extensibility
90        let header_json = serde_json::to_vec(self)?;
91
92        // Write length of header data as u32
93        writer.write_u32::<LittleEndian>(header_json.len() as u32)?;
94
95        // Write the serialized header
96        writer.write_all(&header_json)?;
97
98        Ok(())
99    }
100
101    /// Read the header from a reader
102    pub fn read<R: Read>(reader: &mut R) -> Result<Self> {
103        // Check magic number
104        let mut magic = [0u8; 4];
105        reader.read_exact(&mut magic)?;
106
107        if magic != MAGIC {
108            return Err(Error::InvalidFormat(format!(
109                "Invalid file format. Expected TACO magic number, found: {magic:?}"
110            )));
111        }
112
113        // Read the length of the header data
114        let header_length = reader.read_u32::<LittleEndian>()?;
115
116        // Read the serialized header
117        let mut header_data = vec![0u8; header_length as usize];
118        reader.read_exact(&mut header_data)?;
119
120        // Deserialize the header
121        let header: Header = serde_json::from_slice(&header_data)
122            .map_err(|e| Error::Deserialization(format!("Failed to deserialize header: {e}")))?;
123
124        // Validate version
125        if !is_compatible_version(&header.version) {
126            return Err(Error::InvalidFormat(format!(
127                "Incompatible format version: {}. This library supports: {}",
128                header.version, VERSION
129            )));
130        }
131
132        Ok(header)
133    }
134}
135
136/// Check if the given version is compatible with the current version
137fn is_compatible_version(version: &str) -> bool {
138    // For now, only exact version matches are supported
139    // In the future, this could be more sophisticated with major/minor versioning
140    version == VERSION
141}