use serde::{Deserialize, Serialize};
use serde_json::{Map as JsonMap, Value as JsonValue};
use crate::MolRsError;
use crate::store::block::Column;
use crate::store::frame::Frame;
use crate::types::F;
pub type SchemaValue = JsonValue;
#[derive(Debug, Clone, Default)]
pub struct Trajectory {
pub frames: Vec<Frame>,
pub step: Option<Vec<i64>>,
pub time: Option<Vec<F>>,
}
impl Trajectory {
pub fn new() -> Self {
Self::default()
}
pub fn from_frames(frames: Vec<Frame>) -> Self {
Self {
frames,
step: None,
time: None,
}
}
pub fn len(&self) -> usize {
self.frames.len()
}
pub fn is_empty(&self) -> bool {
self.frames.is_empty()
}
pub fn validate(&self) -> Result<(), MolRsError> {
let n = self.frames.len();
if let Some(step) = &self.step
&& step.len() != n
{
return Err(MolRsError::validation(format!(
"trajectory.step length mismatch: expected {}, got {}",
n,
step.len()
)));
}
if let Some(time) = &self.time
&& time.len() != n
{
return Err(MolRsError::validation(format!(
"trajectory.time length mismatch: expected {}, got {}",
n,
time.len()
)));
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ObservableKind {
Scalar,
Vector,
}
#[derive(Debug, Clone)]
pub enum ObservableData {
Column(Column),
}
#[derive(Debug, Clone)]
pub struct ObservableRecord {
pub name: String,
pub kind: ObservableKind,
pub description: String,
pub time_dependent: bool,
pub unit: Option<String>,
pub axes: Vec<String>,
pub sampling: Option<String>,
pub domain: Option<String>,
pub target: Option<String>,
pub extra: JsonMap<String, JsonValue>,
pub data: ObservableData,
}
impl ObservableRecord {
pub fn scalar(name: impl Into<String>, data: Column) -> Self {
Self {
name: name.into(),
kind: ObservableKind::Scalar,
description: String::new(),
time_dependent: false,
unit: None,
axes: Vec::new(),
sampling: None,
domain: None,
target: None,
extra: JsonMap::new(),
data: ObservableData::Column(data),
}
}
pub fn vector(name: impl Into<String>, data: Column) -> Self {
Self {
name: name.into(),
kind: ObservableKind::Vector,
description: String::new(),
time_dependent: false,
unit: None,
axes: Vec::new(),
sampling: None,
domain: None,
target: None,
extra: JsonMap::new(),
data: ObservableData::Column(data),
}
}
pub fn validate(&self) -> Result<(), MolRsError> {
match (&self.kind, &self.data) {
(ObservableKind::Scalar | ObservableKind::Vector, ObservableData::Column(_)) => Ok(()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_trajectory_has_no_frames() {
let traj = Trajectory::new();
assert_eq!(traj.len(), 0);
assert!(traj.is_empty());
}
#[test]
fn from_frames_counts_states() {
let traj = Trajectory::from_frames(vec![Frame::new(), Frame::new()]);
assert_eq!(traj.len(), 2);
assert!(!traj.is_empty());
traj.validate().unwrap();
}
#[test]
fn mismatched_step_axis_fails_validation() {
let mut traj = Trajectory::from_frames(vec![Frame::new(), Frame::new()]);
traj.step = Some(vec![0]); assert!(traj.validate().is_err());
}
}