use crate::config::BenchmarkConfig;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BenchmarkPhase {
Warmup,
Particle,
Convergence,
Settled,
Materialized,
Dissolution,
Complete,
}
impl BenchmarkPhase {
pub fn label(self) -> &'static str {
match self {
Self::Warmup => "Warmup",
Self::Particle => "Particle",
Self::Convergence => "Convergence",
Self::Settled => "Settled",
Self::Materialized => "Materialized",
Self::Dissolution => "Dissolution",
Self::Complete => "Complete",
}
}
}
pub struct BenchmarkState {
elapsed: f32,
duration: f32,
phase: BenchmarkPhase,
active: bool,
phase_transitions: Vec<(f32, BenchmarkPhase)>,
warmup_end: f32,
particle_end: f32,
convergence_end: f32,
settled_end: f32,
materialized_end: f32,
}
impl BenchmarkState {
pub fn new(duration: f32) -> Self {
let config = BenchmarkConfig::default();
Self {
elapsed: 0.0,
duration,
phase: BenchmarkPhase::Warmup,
active: false,
phase_transitions: Vec::with_capacity(8),
warmup_end: config.warmup_end,
particle_end: config.particle_end,
convergence_end: config.convergence_end,
settled_end: config.settled_end,
materialized_end: config.materialized_end,
}
}
pub fn from_config(config: &BenchmarkConfig) -> Self {
Self {
elapsed: 0.0,
duration: config.duration_secs,
phase: BenchmarkPhase::Warmup,
active: false,
phase_transitions: Vec::with_capacity(8),
warmup_end: config.warmup_end,
particle_end: config.particle_end,
convergence_end: config.convergence_end,
settled_end: config.settled_end,
materialized_end: config.materialized_end,
}
}
pub fn start(&mut self) {
self.elapsed = 0.0;
self.active = true;
self.phase = BenchmarkPhase::Warmup;
self.phase_transitions.clear();
self.phase_transitions.push((0.0, BenchmarkPhase::Warmup));
}
pub fn advance(&mut self, dt: f32) {
if !self.active {
return;
}
self.elapsed += dt;
let new_phase = if self.elapsed < self.warmup_end {
BenchmarkPhase::Warmup
} else if self.elapsed < self.particle_end {
BenchmarkPhase::Particle
} else if self.elapsed < self.convergence_end {
BenchmarkPhase::Convergence
} else if self.elapsed < self.settled_end {
BenchmarkPhase::Settled
} else if self.elapsed < self.materialized_end {
BenchmarkPhase::Materialized
} else if self.elapsed < self.duration {
BenchmarkPhase::Dissolution
} else {
BenchmarkPhase::Complete
};
if new_phase != self.phase {
log::info!(
"Phase transition: {} -> {} at {:.1}s",
self.phase.label(), new_phase.label(), self.elapsed
);
self.phase_transitions.push((self.elapsed, new_phase));
self.phase = new_phase;
}
if self.phase == BenchmarkPhase::Complete {
self.active = false;
}
}
pub fn phase(&self) -> BenchmarkPhase {
self.phase
}
pub fn phase_label(&self) -> &str {
self.phase.label()
}
pub fn elapsed(&self) -> f32 {
self.elapsed
}
pub fn is_complete(&self) -> bool {
self.phase == BenchmarkPhase::Complete
}
pub fn transitions(&self) -> &[(f32, BenchmarkPhase)] {
&self.phase_transitions
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn phase_progression() {
let config = BenchmarkConfig::default();
let mut state = BenchmarkState::from_config(&config);
state.start();
assert_eq!(state.phase(), BenchmarkPhase::Warmup);
state.advance(6.0);
assert_eq!(state.phase(), BenchmarkPhase::Particle);
state.advance(10.0);
assert_eq!(state.phase(), BenchmarkPhase::Convergence);
state.advance(15.0);
assert_eq!(state.phase(), BenchmarkPhase::Settled);
state.advance(10.0);
assert_eq!(state.phase(), BenchmarkPhase::Materialized);
state.advance(13.0);
assert_eq!(state.phase(), BenchmarkPhase::Dissolution);
state.advance(10.0);
assert_eq!(state.phase(), BenchmarkPhase::Complete);
assert!(state.is_complete());
}
#[test]
fn transitions_logged() {
let config = BenchmarkConfig::default();
let mut state = BenchmarkState::from_config(&config);
state.start();
for _ in 0..700 {
state.advance(0.1);
}
assert_eq!(state.transitions().len(), 7, "Expected 7 transitions, got {}", state.transitions().len());
}
#[test]
fn not_started_is_noop() {
let mut state = BenchmarkState::new(30.0);
state.advance(10.0);
assert_eq!(state.phase(), BenchmarkPhase::Warmup);
assert_eq!(state.elapsed(), 0.0);
}
#[test]
fn restart_resets_state() {
let mut state = BenchmarkState::new(30.0);
state.start();
state.advance(15.0);
state.start(); assert_eq!(state.phase(), BenchmarkPhase::Warmup);
assert_eq!(state.elapsed(), 0.0);
assert_eq!(state.transitions().len(), 1);
}
#[test]
fn zero_dt_doesnt_transition() {
let mut state = BenchmarkState::new(30.0);
state.start();
state.advance(0.0);
assert_eq!(state.phase(), BenchmarkPhase::Warmup);
}
#[test]
fn phase_labels_nonempty() {
let phases = [
BenchmarkPhase::Warmup, BenchmarkPhase::Particle,
BenchmarkPhase::Convergence, BenchmarkPhase::Settled,
BenchmarkPhase::Materialized, BenchmarkPhase::Dissolution,
BenchmarkPhase::Complete,
];
for p in phases {
assert!(!p.label().is_empty());
}
}
#[test]
fn complete_stops_advancing() {
let config = BenchmarkConfig::default();
let mut state = BenchmarkState::from_config(&config);
state.start();
for _ in 0..700 { state.advance(0.1); }
assert!(state.is_complete());
let elapsed_at_complete = state.elapsed();
state.advance(10.0);
assert_eq!(state.elapsed(), elapsed_at_complete);
}
#[test]
fn negative_dt_ignored() {
let config = BenchmarkConfig::default();
let mut state = BenchmarkState::from_config(&config);
state.start();
state.advance(-5.0);
assert_eq!(state.phase(), BenchmarkPhase::Warmup);
}
#[test]
fn large_dt_jumps_to_complete() {
let config = BenchmarkConfig::default();
let mut state = BenchmarkState::from_config(&config);
state.start();
state.advance(100.0);
assert!(state.is_complete());
}
#[test]
fn elapsed_tracks_cumulative() {
let config = BenchmarkConfig::default();
let mut state = BenchmarkState::from_config(&config);
state.start();
state.advance(1.0);
state.advance(2.0);
state.advance(3.0);
assert!((state.elapsed() - 6.0).abs() < 0.001);
}
#[test]
fn custom_config_phase_boundaries() {
let config = BenchmarkConfig {
warmup_end: 2.0,
particle_end: 5.0,
convergence_end: 8.0,
settled_end: 10.0,
materialized_end: 12.0,
duration_secs: 15.0,
..Default::default()
};
let mut state = BenchmarkState::from_config(&config);
state.start();
state.advance(3.0);
assert_eq!(state.phase(), BenchmarkPhase::Particle);
state.advance(3.0);
assert_eq!(state.phase(), BenchmarkPhase::Convergence);
}
}