pub type TrajId = u64;
pub type FrameIdx = u32;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct ContentHash(pub [u8; 16]);
impl ContentHash {
pub fn to_bytes(self) -> [u8; 16] {
self.0
}
pub fn from_bytes(b: &[u8]) -> Option<Self> {
if b.len() != 16 {
return None;
}
let mut a = [0u8; 16];
a.copy_from_slice(b);
Some(Self(a))
}
pub fn to_hex(self) -> String {
self.0.iter().map(|b| format!("{b:02x}")).collect()
}
}
pub fn hash_frame_bytes(blob: &[u8]) -> ContentHash {
use xxhash_rust::xxh3::xxh3_128;
let h = xxh3_128(blob);
ContentHash(h.to_le_bytes())
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct FrameKey {
pub traj_id: TrajId,
pub frame_idx: FrameIdx,
}
impl FrameKey {
pub fn to_bytes(self) -> [u8; 12] {
let mut out = [0u8; 12];
out[..8].copy_from_slice(&self.traj_id.to_be_bytes());
out[8..].copy_from_slice(&self.frame_idx.to_be_bytes());
out
}
pub fn from_bytes(b: &[u8]) -> Option<Self> {
if b.len() != 12 {
return None;
}
let mut t = [0u8; 8];
let mut f = [0u8; 4];
t.copy_from_slice(&b[..8]);
f.copy_from_slice(&b[8..]);
Some(Self {
traj_id: u64::from_be_bytes(t),
frame_idx: u32::from_be_bytes(f),
})
}
}
pub(crate) fn natoms_key(n_atoms: u32, fk: FrameKey) -> [u8; 16] {
let mut out = [0u8; 16];
out[..4].copy_from_slice(&n_atoms.to_be_bytes());
out[4..].copy_from_slice(&fk.to_bytes());
out
}
pub(crate) fn symbol_key(symbol: &str, fk: FrameKey) -> Vec<u8> {
let mut v = symbol.as_bytes().to_vec();
v.push(0xff);
v.extend_from_slice(&fk.to_bytes());
v
}
pub(crate) fn symbol_prefix(symbol: &str) -> Vec<u8> {
let mut v = symbol.as_bytes().to_vec();
v.push(0xff);
v
}
pub(crate) fn ordered_f64_bits(x: f64) -> Option<u64> {
if !x.is_finite() {
return None;
}
let bits = x.to_bits();
Some(if x.is_sign_negative() {
!bits
} else {
bits ^ (1u64 << 63)
})
}
pub(crate) fn energy_bin_key(energy: f64, fk: FrameKey) -> Option<[u8; 20]> {
let ordered = ordered_f64_bits(energy)?;
let mut out = [0u8; 20];
out[..8].copy_from_slice(&ordered.to_be_bytes());
out[8..].copy_from_slice(&fk.to_bytes());
Some(out)
}
pub(crate) fn fmax_bin_key(fmax: f64, fk: FrameKey) -> Option<[u8; 20]> {
energy_bin_key(fmax, fk)
}
pub(crate) fn flag_key(flag: u8, fk: FrameKey) -> [u8; 13] {
let mut out = [0u8; 13];
out[0] = flag;
out[1..].copy_from_slice(&fk.to_bytes());
out
}
pub(crate) const FLAG_HAS_FORCES: u8 = 1;
pub(crate) const FLAG_HAS_VELOCITIES: u8 = 2;
pub(crate) const FLAG_HAS_ENERGY: u8 = 3;
pub(crate) fn elem_count_key(symbol: &str, count: u32, fk: FrameKey) -> Vec<u8> {
let mut v = symbol.as_bytes().to_vec();
v.push(0xff);
v.extend_from_slice(&count.to_be_bytes());
v.extend_from_slice(&fk.to_bytes());
v
}
pub(crate) fn elem_count_symbol_prefix(symbol: &str) -> Vec<u8> {
let mut v = symbol.as_bytes().to_vec();
v.push(0xff);
v
}
pub(crate) fn parse_elem_count_key(k: &[u8], symbol: &str) -> Option<(u32, FrameKey)> {
let pref = elem_count_symbol_prefix(symbol);
if !k.starts_with(&pref) || k.len() < pref.len() + 4 + 12 {
return None;
}
let mut cb = [0u8; 4];
cb.copy_from_slice(&k[pref.len()..pref.len() + 4]);
let count = u32::from_be_bytes(cb);
let fk = FrameKey::from_bytes(&k[pref.len() + 4..pref.len() + 4 + 12])?;
Some((count, fk))
}
pub use readcon_core::index_proj::{composition_formula, species_counts_from_symbols};
pub(crate) fn formula_key(formula: &str, fk: FrameKey) -> Vec<u8> {
let mut v = formula.as_bytes().to_vec();
v.push(0xff);
v.extend_from_slice(&fk.to_bytes());
v
}
pub(crate) fn formula_prefix(formula: &str) -> Vec<u8> {
let mut v = formula.as_bytes().to_vec();
v.push(0xff);
v
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn formula_canonical_order() {
let f1 = composition_formula(&[("H".into(), 2), ("Cu".into(), 2)]);
let f2 = composition_formula(&[("Cu".into(), 2), ("H".into(), 2)]);
assert_eq!(f1, "Cu:2|H:2");
assert_eq!(f1, f2);
}
#[test]
fn ordered_bits_monotone() {
let a = ordered_f64_bits(-1.0).unwrap();
let b = ordered_f64_bits(0.0).unwrap();
let c = ordered_f64_bits(1.0).unwrap();
assert!(a < b && b < c);
}
#[test]
fn elem_count_roundtrip_parse() {
let fk = FrameKey {
traj_id: 3,
frame_idx: 7,
};
let k = elem_count_key("Cu", 2, fk);
let (c, fk2) = parse_elem_count_key(&k, "Cu").unwrap();
assert_eq!(c, 2);
assert_eq!(fk2, fk);
}
}
pub(crate) const META_TIME: u8 = 1;
pub(crate) const META_TIMESTEP: u8 = 2;
pub(crate) const META_FRAME_INDEX: u8 = 3;
pub(crate) const META_NEB_BEAD: u8 = 4;
pub(crate) const META_NEB_BAND: u8 = 5;
pub(crate) const META_CHARGE: u8 = 6;
pub(crate) const META_MAGMOM: u8 = 7;
pub(crate) fn meta_scalar_key(channel: u8, value: f64, fk: FrameKey) -> Option<[u8; 21]> {
let ordered = ordered_f64_bits(value)?;
let mut out = [0u8; 21];
out[0] = channel;
out[1..9].copy_from_slice(&ordered.to_be_bytes());
out[9..].copy_from_slice(&fk.to_bytes());
Some(out)
}
pub(crate) fn meta_channel_prefix(channel: u8) -> [u8; 1] {
[channel]
}
pub(crate) fn pbc_key(mask: u8, fk: FrameKey) -> [u8; 13] {
let mut out = [0u8; 13];
out[0] = mask & 0x07;
out[1..].copy_from_slice(&fk.to_bytes());
out
}
pub(crate) fn pbc_mask_from_bools(p: [bool; 3]) -> u8 {
(p[0] as u8) | ((p[1] as u8) << 1) | ((p[2] as u8) << 2)
}
pub(crate) fn mass_bin_key(mass: f64, fk: FrameKey) -> Option<[u8; 20]> {
energy_bin_key(mass, fk)
}
pub(crate) fn volume_bin_key(vol: f64, fk: FrameKey) -> Option<[u8; 20]> {
energy_bin_key(vol, fk)
}