use std::fmt;
use std::sync::Arc;
use std::error::Error;
use nohash_hasher::IntMap;
use ff_energy::EnergyModel;
use ff_structure::DotBracketVec;
use crate::macrostates::MacrostateRegistry;
#[derive(Debug)]
pub enum TimelineError {
Io(std::io::Error),
Parse(std::num::ParseFloatError),
Json(serde_json::Error),
TimepointCountMismatch { found: usize, expected: usize },
TimeMismatch { file_time: f64, expected_time: f64 },
MacrostateNotFound(String),
}
impl fmt::Display for TimelineError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Io(e) => write!(f, "I/O error: {}", e),
Self::Parse(e) => write!(f, "Parse error: {}", e),
Self::Json(e) => write!(f, "JSON parse error: {}", e),
Self::TimepointCountMismatch { found, expected } =>
write!(f, "Timeline file has {found} timepoints, expected {expected}"),
Self::TimeMismatch { file_time, expected_time } =>
write!(f, "Time mismatch: {file_time} vs {expected_time}"),
Self::MacrostateNotFound(name) =>
write!(f, "Macrostate '{name}' not found in registry"),
}
}
}
impl Error for TimelineError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
Self::Io(e) => Some(e),
Self::Json(e) => Some(e),
_ => None,
}
}
}
impl From<std::io::Error> for TimelineError {
fn from(e: std::io::Error) -> Self { Self::Io(e) }
}
impl From<serde_json::Error> for TimelineError {
fn from(e: serde_json::Error) -> Self { Self::Json(e) }
}
impl From<std::num::ParseFloatError> for TimelineError {
fn from(e: std::num::ParseFloatError) -> Self { Self::Parse(e) }
}
#[derive(Debug)]
pub struct Timepoint {
pub time: f64,
pub ensemble: IntMap<usize, usize>,
pub counter: usize,
}
impl Timepoint {
pub fn new(time: f64) -> Self {
Self {
time,
ensemble: IntMap::default(),
counter: 0,
}
}
pub fn add(&mut self, macro_idx: usize) {
*self.ensemble.entry(macro_idx).or_insert(0) += 1;
self.counter += 1;
}
pub fn count(&self, macro_idx: usize) -> usize {
*self.ensemble.get(¯o_idx).unwrap_or(&0)
}
pub fn occupancy(&self, macro_idx: usize) -> f64 {
if self.counter == 0 {
0.0
} else {
self.count(macro_idx) as f64 / self.counter as f64
}
}
pub fn iter(&self) -> impl Iterator<Item = (usize, usize)> + '_ {
self.ensemble.iter().map(|(k, v)| (*k, *v))
}
}
pub struct Timeline<E: EnergyModel> {
pub registry: Arc<MacrostateRegistry<E>>,
pub points: Vec<Timepoint>,
}
impl<E: EnergyModel> Timeline<E> {
pub fn new(times: &[f64], registry: Arc<MacrostateRegistry<E>>) -> Self {
let points = times.iter().map(|&t| Timepoint::new(t)).collect();
Self { registry, points }
}
pub fn assign_structure(&mut self, t_idx: usize, structure: &DotBracketVec) {
let m_idx = self.registry.classify(structure);
self.points[t_idx].add(m_idx);
}
pub fn point(&self, t_idx: usize) -> &Timepoint {
&self.points[t_idx]
}
pub fn iter(&self) -> impl Iterator<Item = (usize, &Timepoint)> {
self.points.iter().enumerate()
}
pub fn merge(&mut self, other: Timeline<E>) {
assert!(
Arc::ptr_eq(&self.registry, &other.registry),
"Cannot merge timelines with different registries"
);
assert_eq!(self.points.len(), other.points.len(),
"Cannot merge timelines with different numbers of timepoints");
for (self_tp, other_tp) in self.points.iter_mut().zip(other.points) {
for (macro_idx, count) in other_tp.iter() {
*self_tp.ensemble.entry(macro_idx).or_insert(0) += count;
}
self_tp.counter += other_tp.counter;
}
}
}
impl<E: EnergyModel> fmt::Display for Timeline<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let macrostates = self.registry.macrostates();
write!(f, "{:>13}", "time")?;
for ms in macrostates.iter() {
write!(f, " {:>13}", ms.name())?;
}
writeln!(f)?;
for tp in &self.points {
write!(f, "{:13.6e}", tp.time)?;
for m_idx in 0..macrostates.len() {
write!(f, " {:13.6e}", tp.occupancy(m_idx))?;
}
writeln!(f)?;
}
Ok(())
}
}