#[cfg(feature = "simulation")]
mod inner {
pub use simular::*;
}
#[cfg(feature = "simulation")]
pub use inner::*;
#[derive(Debug, Clone, PartialEq)]
pub struct SimConfig {
pub seed: Option<u64>,
pub max_steps: usize,
pub dt: f64,
pub trace: bool,
pub stop_on_violation: bool,
}
impl Default for SimConfig {
fn default() -> Self {
Self {
seed: None,
max_steps: 10_000,
dt: 0.001,
trace: false,
stop_on_violation: true,
}
}
}
impl SimConfig {
pub fn deterministic(seed: u64) -> Self {
Self {
seed: Some(seed),
..Default::default()
}
}
pub fn with_max_steps(mut self, steps: usize) -> Self {
self.max_steps = steps;
self
}
pub fn with_dt(mut self, dt: f64) -> Self {
self.dt = dt;
self
}
pub fn with_trace(mut self) -> Self {
self.trace = true;
self
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct SimEvent {
pub time: f64,
pub step: usize,
pub description: String,
}
#[derive(Debug, Clone)]
pub struct SimResult {
pub steps: usize,
pub final_time: f64,
pub invariants_ok: bool,
pub events: Vec<SimEvent>,
pub exit_reason: SimExitReason,
}
#[derive(Debug, Clone, PartialEq)]
pub enum SimExitReason {
Completed,
InvariantViolation(String),
SteadyState,
StopCondition(String),
}
impl SimResult {
pub fn completed(steps: usize, final_time: f64) -> Self {
Self {
steps,
final_time,
invariants_ok: true,
events: Vec::new(),
exit_reason: SimExitReason::Completed,
}
}
pub fn summary(&self) -> String {
let status = if self.invariants_ok { "OK" } else { "FAIL" };
format!(
"Simulation: {} steps, t={:.4}, invariants={}, exit={:?}",
self.steps, self.final_time, status, self.exit_reason
)
}
}
#[derive(Debug, Clone)]
pub struct SimSnapshot {
pub step: usize,
pub time: f64,
pub variables: Vec<(String, f64)>,
}
impl SimSnapshot {
pub fn get(&self, name: &str) -> Option<f64> {
self.variables
.iter()
.find(|(n, _)| n == name)
.map(|(_, v)| *v)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sim_config_default() {
let config = SimConfig::default();
assert!(config.seed.is_none());
assert_eq!(config.max_steps, 10_000);
assert!((config.dt - 0.001).abs() < f64::EPSILON);
assert!(!config.trace);
assert!(config.stop_on_violation);
}
#[test]
fn test_sim_config_deterministic() {
let config = SimConfig::deterministic(42);
assert_eq!(config.seed, Some(42));
assert_eq!(config.max_steps, 10_000);
}
#[test]
fn test_sim_config_builder() {
let config = SimConfig::deterministic(123)
.with_max_steps(500)
.with_dt(0.01)
.with_trace();
assert_eq!(config.seed, Some(123));
assert_eq!(config.max_steps, 500);
assert!((config.dt - 0.01).abs() < f64::EPSILON);
assert!(config.trace);
}
#[test]
fn test_sim_result_completed() {
let result = SimResult::completed(1000, 1.0);
assert_eq!(result.steps, 1000);
assert!(result.invariants_ok);
assert_eq!(result.exit_reason, SimExitReason::Completed);
assert!(result.events.is_empty());
}
#[test]
fn test_sim_result_summary() {
let result = SimResult::completed(500, 0.5);
let summary = result.summary();
assert!(summary.contains("500 steps"));
assert!(summary.contains("OK"));
}
#[test]
fn test_sim_snapshot_get() {
let snap = SimSnapshot {
step: 100,
time: 0.1,
variables: vec![("x".to_string(), 1.5), ("y".to_string(), -0.3)],
};
assert_eq!(snap.get("x"), Some(1.5));
assert_eq!(snap.get("y"), Some(-0.3));
assert_eq!(snap.get("z"), None);
}
#[test]
fn test_sim_exit_reasons() {
assert_eq!(SimExitReason::Completed, SimExitReason::Completed);
let violation = SimExitReason::InvariantViolation("x > 0".to_string());
assert_ne!(violation, SimExitReason::Completed);
}
}