oxirs_physics/simulation/
simulation_runner.rs1use super::parameter_extraction::SimulationParameters;
4use super::result_injection::{
5 ConvergenceInfo, SimulationProvenance, SimulationResult, StateVector,
6};
7use crate::error::{PhysicsError, PhysicsResult};
8use async_trait::async_trait;
9use chrono::Utc;
10use std::collections::HashMap;
11use uuid::Uuid;
12
13#[async_trait]
15pub trait PhysicsSimulation: Send + Sync {
16 fn simulation_type(&self) -> &str;
18
19 async fn run(&self, params: &SimulationParameters) -> PhysicsResult<SimulationResult>;
21
22 fn validate_results(&self, result: &SimulationResult) -> PhysicsResult<()>;
24}
25
26pub struct SimulationRunner;
28
29impl SimulationRunner {
30 pub fn new() -> Self {
31 Self
32 }
33}
34
35impl Default for SimulationRunner {
36 fn default() -> Self {
37 Self::new()
38 }
39}
40
41pub struct MockThermalSimulation;
43
44#[async_trait]
45impl PhysicsSimulation for MockThermalSimulation {
46 fn simulation_type(&self) -> &str {
47 "thermal"
48 }
49
50 async fn run(&self, params: &SimulationParameters) -> PhysicsResult<SimulationResult> {
51 let mut trajectory = Vec::new();
53
54 for i in 0..params.time_steps {
55 let time = params.time_span.0
56 + (params.time_span.1 - params.time_span.0) * (i as f64 / params.time_steps as f64);
57
58 let mut state = HashMap::new();
59 state.insert("temperature".to_string(), 20.0 + time * 0.1);
60
61 trajectory.push(StateVector { time, state });
62 }
63
64 Ok(SimulationResult {
65 entity_iri: params.entity_iri.clone(),
66 simulation_run_id: Uuid::new_v4().to_string(),
67 timestamp: Utc::now(),
68 state_trajectory: trajectory,
69 derived_quantities: HashMap::new(),
70 convergence_info: ConvergenceInfo {
71 converged: true,
72 iterations: params.time_steps,
73 final_residual: 1e-6,
74 },
75 provenance: SimulationProvenance {
76 software: "oxirs-physics".to_string(),
77 version: crate::VERSION.to_string(),
78 parameters_hash: "mock_hash".to_string(),
79 executed_at: Utc::now(),
80 execution_time_ms: 100,
81 },
82 })
83 }
84
85 fn validate_results(&self, result: &SimulationResult) -> PhysicsResult<()> {
86 if !result.convergence_info.converged {
87 return Err(PhysicsError::Simulation(
88 "Simulation did not converge".to_string(),
89 ));
90 }
91 Ok(())
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98
99 #[tokio::test]
100 async fn test_mock_thermal_simulation() {
101 let sim = MockThermalSimulation;
102
103 let params = SimulationParameters {
104 entity_iri: "urn:example:battery:001".to_string(),
105 simulation_type: "thermal".to_string(),
106 initial_conditions: HashMap::new(),
107 boundary_conditions: Vec::new(),
108 time_span: (0.0, 100.0),
109 time_steps: 10,
110 material_properties: HashMap::new(),
111 constraints: Vec::new(),
112 };
113
114 let result = sim.run(¶ms).await.unwrap();
115
116 assert_eq!(result.state_trajectory.len(), 10);
117 assert!(result.convergence_info.converged);
118 assert!(sim.validate_results(&result).is_ok());
119 }
120}