ringkernel_wavesim3d/simulation/
mod.rs

1//! 3D acoustic wave simulation module.
2//!
3//! Provides:
4//! - Physics parameters with realistic acoustic modeling
5//! - 3D FDTD simulation grid (CPU and GPU backends)
6//! - GPU-accelerated stencil operations
7//! - **Actor-based GPU simulation** (cell-as-actor paradigm)
8//!
9//! # Computation Methods
10//!
11//! The simulation supports two GPU computation methods:
12//!
13//! ## Stencil Method (Default)
14//! Traditional GPU stencil computation where each thread reads neighbor values
15//! from shared memory. Fast and efficient for regular grids.
16//!
17//! ## Actor Method
18//! Novel cell-as-actor paradigm where each spatial cell is an independent actor.
19//! Actors communicate via message passing (halo exchange) instead of shared memory.
20//! Uses HLC (Hybrid Logical Clocks) for temporal alignment.
21//!
22//! ```ignore
23//! use ringkernel_wavesim3d::simulation::{ComputationMethod, SimulationConfig};
24//!
25//! let config = SimulationConfig::default()
26//!     .with_computation_method(ComputationMethod::Actor);
27//! let engine = config.build();
28//! ```
29
30pub mod grid3d;
31pub mod physics;
32
33#[cfg(feature = "cuda")]
34pub mod gpu_backend;
35
36#[cfg(feature = "cuda")]
37pub mod actor_backend;
38
39pub use grid3d::{CellType, GridParams, SimulationGrid3D};
40pub use physics::{
41    AcousticParams3D, AtmosphericAbsorption, Environment, Medium, MediumProperties,
42    MultiBandDamping, Orientation3D, Position3D,
43};
44
45#[cfg(feature = "cuda")]
46pub use actor_backend::{
47    ActorBackendConfig, ActorError, ActorStats, CellActorState, Direction3D, HaloMessage,
48};
49
50/// Computation method for GPU-accelerated simulation.
51#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
52pub enum ComputationMethod {
53    /// Traditional stencil-based GPU computation.
54    /// Each thread reads neighbor values from global/shared memory.
55    /// Fast and efficient for regular grids.
56    #[default]
57    Stencil,
58
59    /// Actor-based GPU computation (cell-as-actor paradigm).
60    /// Each spatial cell is an independent actor that:
61    /// - Holds its own state (pressure, cell type)
62    /// - Communicates with neighbors via message passing
63    /// - Uses HLC for temporal alignment
64    ///
65    /// This method demonstrates the actor model on GPUs but may have
66    /// higher overhead than stencil computation.
67    Actor,
68}
69
70/// Simulation engine that manages grid updates and GPU/CPU dispatch.
71pub struct SimulationEngine {
72    /// 3D simulation grid
73    pub grid: SimulationGrid3D,
74    /// Use GPU backend if available
75    pub use_gpu: bool,
76    /// Computation method for GPU
77    pub computation_method: ComputationMethod,
78    /// Stencil GPU backend
79    #[cfg(feature = "cuda")]
80    pub gpu: Option<gpu_backend::GpuBackend3D>,
81    /// Actor GPU backend
82    #[cfg(feature = "cuda")]
83    pub actor_gpu: Option<actor_backend::ActorGpuBackend3D>,
84}
85
86impl SimulationEngine {
87    /// Create a new simulation engine with CPU backend.
88    pub fn new_cpu(width: usize, height: usize, depth: usize, params: AcousticParams3D) -> Self {
89        Self {
90            grid: SimulationGrid3D::new(width, height, depth, params),
91            use_gpu: false,
92            computation_method: ComputationMethod::Stencil,
93            #[cfg(feature = "cuda")]
94            gpu: None,
95            #[cfg(feature = "cuda")]
96            actor_gpu: None,
97        }
98    }
99
100    /// Create a new simulation engine with GPU backend (stencil method).
101    #[cfg(feature = "cuda")]
102    pub fn new_gpu(
103        width: usize,
104        height: usize,
105        depth: usize,
106        params: AcousticParams3D,
107    ) -> Result<Self, gpu_backend::GpuError> {
108        let grid = SimulationGrid3D::new(width, height, depth, params);
109        let gpu = gpu_backend::GpuBackend3D::new(&grid)?;
110
111        Ok(Self {
112            grid,
113            use_gpu: true,
114            computation_method: ComputationMethod::Stencil,
115            gpu: Some(gpu),
116            actor_gpu: None,
117        })
118    }
119
120    /// Create a new simulation engine with GPU backend using actor model.
121    ///
122    /// This uses the cell-as-actor paradigm where each spatial cell is an
123    /// independent actor that communicates with neighbors via message passing.
124    #[cfg(feature = "cuda")]
125    pub fn new_gpu_actor(
126        width: usize,
127        height: usize,
128        depth: usize,
129        params: AcousticParams3D,
130        actor_config: actor_backend::ActorBackendConfig,
131    ) -> Result<Self, actor_backend::ActorError> {
132        let grid = SimulationGrid3D::new(width, height, depth, params);
133        let actor_gpu = actor_backend::ActorGpuBackend3D::new(&grid, actor_config)?;
134
135        Ok(Self {
136            grid,
137            use_gpu: true,
138            computation_method: ComputationMethod::Actor,
139            gpu: None,
140            actor_gpu: Some(actor_gpu),
141        })
142    }
143
144    /// Perform one simulation step.
145    pub fn step(&mut self) {
146        #[cfg(feature = "cuda")]
147        if self.use_gpu {
148            match self.computation_method {
149                ComputationMethod::Stencil => {
150                    if let Some(ref mut gpu) = self.gpu {
151                        if gpu.step(&mut self.grid).is_ok() {
152                            return;
153                        }
154                    }
155                }
156                ComputationMethod::Actor => {
157                    if let Some(ref mut actor_gpu) = self.actor_gpu {
158                        if actor_gpu.step(&mut self.grid, 1).is_ok() {
159                            return;
160                        }
161                    }
162                }
163            }
164        }
165
166        // Fall back to sequential CPU (parallel can cause issues with GUI event loops)
167        self.grid.step_sequential();
168    }
169
170    /// Perform multiple simulation steps.
171    pub fn step_n(&mut self, n: usize) {
172        for _ in 0..n {
173            self.step();
174        }
175    }
176
177    /// Reset the simulation.
178    pub fn reset(&mut self) {
179        self.grid.reset();
180
181        #[cfg(feature = "cuda")]
182        {
183            if let Some(ref mut gpu) = self.gpu {
184                let _ = gpu.reset(&self.grid);
185            }
186            if let Some(ref mut actor_gpu) = self.actor_gpu {
187                let _ = actor_gpu.reset(&self.grid);
188            }
189        }
190    }
191
192    /// Inject an impulse at the given position.
193    pub fn inject_impulse(&mut self, x: usize, y: usize, z: usize, amplitude: f32) {
194        self.grid.inject_impulse(x, y, z, amplitude);
195
196        #[cfg(feature = "cuda")]
197        {
198            if let Some(ref mut gpu) = self.gpu {
199                let _ = gpu.upload_pressure(&self.grid);
200            }
201            if let Some(ref mut actor_gpu) = self.actor_gpu {
202                let _ = actor_gpu.upload_pressure(&self.grid);
203            }
204        }
205    }
206
207    /// Get current simulation time.
208    pub fn time(&self) -> f32 {
209        self.grid.time
210    }
211
212    /// Get current step count.
213    pub fn step_count(&self) -> u64 {
214        self.grid.step
215    }
216
217    /// Get grid dimensions.
218    pub fn dimensions(&self) -> (usize, usize, usize) {
219        self.grid.dimensions()
220    }
221
222    /// Enable or disable GPU acceleration.
223    #[cfg(feature = "cuda")]
224    pub fn set_use_gpu(&mut self, use_gpu: bool) {
225        if use_gpu {
226            match self.computation_method {
227                ComputationMethod::Stencil => {
228                    if self.gpu.is_none() {
229                        if let Ok(gpu) = gpu_backend::GpuBackend3D::new(&self.grid) {
230                            self.gpu = Some(gpu);
231                        }
232                    }
233                    self.use_gpu = self.gpu.is_some();
234                }
235                ComputationMethod::Actor => {
236                    if self.actor_gpu.is_none() {
237                        if let Ok(actor_gpu) = actor_backend::ActorGpuBackend3D::new(
238                            &self.grid,
239                            actor_backend::ActorBackendConfig::default(),
240                        ) {
241                            self.actor_gpu = Some(actor_gpu);
242                        }
243                    }
244                    self.use_gpu = self.actor_gpu.is_some();
245                }
246            }
247        } else {
248            self.use_gpu = false;
249        }
250    }
251
252    /// Set the computation method for GPU.
253    #[cfg(feature = "cuda")]
254    pub fn set_computation_method(&mut self, method: ComputationMethod) {
255        self.computation_method = method;
256        // Re-initialize the appropriate backend if GPU is in use
257        if self.use_gpu {
258            self.set_use_gpu(true);
259        }
260    }
261
262    /// Check if GPU is being used.
263    pub fn is_using_gpu(&self) -> bool {
264        #[cfg(feature = "cuda")]
265        {
266            match self.computation_method {
267                ComputationMethod::Stencil => self.use_gpu && self.gpu.is_some(),
268                ComputationMethod::Actor => self.use_gpu && self.actor_gpu.is_some(),
269            }
270        }
271        #[cfg(not(feature = "cuda"))]
272        {
273            false
274        }
275    }
276
277    /// Get the current computation method.
278    pub fn computation_method(&self) -> ComputationMethod {
279        self.computation_method
280    }
281
282    /// Download pressure data from GPU to CPU grid (if using GPU).
283    #[cfg(feature = "cuda")]
284    pub fn sync_from_gpu(&mut self) {
285        if self.use_gpu {
286            match self.computation_method {
287                ComputationMethod::Stencil => {
288                    if let Some(ref gpu) = self.gpu {
289                        let _ = gpu.download_pressure(&mut self.grid);
290                    }
291                }
292                ComputationMethod::Actor => {
293                    if let Some(ref actor_gpu) = self.actor_gpu {
294                        let _ = actor_gpu.download_pressure(&mut self.grid);
295                    }
296                }
297            }
298        }
299    }
300
301    /// Get actor backend statistics (if using actor method).
302    #[cfg(feature = "cuda")]
303    pub fn actor_stats(&self) -> Option<actor_backend::ActorStats> {
304        self.actor_gpu.as_ref().map(|gpu| gpu.stats())
305    }
306}
307
308/// Configuration for creating a simulation.
309#[derive(Debug, Clone)]
310pub struct SimulationConfig {
311    /// Grid width (cells)
312    pub width: usize,
313    /// Grid height (cells)
314    pub height: usize,
315    /// Grid depth (cells)
316    pub depth: usize,
317    /// Cell size (meters)
318    pub cell_size: f32,
319    /// Environment settings
320    pub environment: Environment,
321    /// Use GPU if available
322    pub prefer_gpu: bool,
323    /// Computation method for GPU
324    pub computation_method: ComputationMethod,
325    /// Actor backend configuration (for Actor method)
326    #[cfg(feature = "cuda")]
327    pub actor_config: actor_backend::ActorBackendConfig,
328}
329
330impl Default for SimulationConfig {
331    fn default() -> Self {
332        Self {
333            width: 64,
334            height: 64,
335            depth: 64,
336            cell_size: 0.05, // 5cm cells
337            environment: Environment::default(),
338            prefer_gpu: true,
339            computation_method: ComputationMethod::Stencil,
340            #[cfg(feature = "cuda")]
341            actor_config: actor_backend::ActorBackendConfig::default(),
342        }
343    }
344}
345
346impl SimulationConfig {
347    /// Create a configuration for a small room (10m x 10m x 3m).
348    pub fn small_room() -> Self {
349        Self {
350            width: 200,
351            height: 60,
352            depth: 200,
353            ..Default::default()
354        }
355    }
356
357    /// Create a configuration for a medium room (20m x 20m x 5m).
358    pub fn medium_room() -> Self {
359        Self {
360            width: 200,
361            height: 50,
362            depth: 200,
363            cell_size: 0.1, // 10cm cells
364            ..Default::default()
365        }
366    }
367
368    /// Create a configuration for a large space (50m x 50m x 10m).
369    pub fn large_space() -> Self {
370        Self {
371            width: 250,
372            height: 50,
373            depth: 250,
374            cell_size: 0.2, // 20cm cells
375            ..Default::default()
376        }
377    }
378
379    /// Create a configuration for underwater simulation.
380    pub fn underwater() -> Self {
381        Self {
382            environment: Environment::default().with_medium(Medium::Water),
383            ..Self::medium_room()
384        }
385    }
386
387    /// Set the environment.
388    pub fn with_environment(mut self, env: Environment) -> Self {
389        self.environment = env;
390        self
391    }
392
393    /// Set the computation method.
394    ///
395    /// - `Stencil`: Traditional GPU stencil computation (default)
396    /// - `Actor`: Cell-as-actor paradigm with message-based halo exchange
397    pub fn with_computation_method(mut self, method: ComputationMethod) -> Self {
398        self.computation_method = method;
399        self
400    }
401
402    /// Set the actor backend configuration (only used with Actor method).
403    #[cfg(feature = "cuda")]
404    pub fn with_actor_config(mut self, config: actor_backend::ActorBackendConfig) -> Self {
405        self.actor_config = config;
406        self
407    }
408
409    /// Build the simulation engine.
410    pub fn build(self) -> SimulationEngine {
411        let params = AcousticParams3D::new(self.environment, self.cell_size);
412
413        #[cfg(feature = "cuda")]
414        if self.prefer_gpu {
415            match self.computation_method {
416                ComputationMethod::Stencil => {
417                    if let Ok(engine) = SimulationEngine::new_gpu(
418                        self.width,
419                        self.height,
420                        self.depth,
421                        params.clone(),
422                    ) {
423                        return engine;
424                    }
425                }
426                ComputationMethod::Actor => {
427                    if let Ok(engine) = SimulationEngine::new_gpu_actor(
428                        self.width,
429                        self.height,
430                        self.depth,
431                        params.clone(),
432                        self.actor_config,
433                    ) {
434                        return engine;
435                    }
436                }
437            }
438        }
439
440        SimulationEngine::new_cpu(self.width, self.height, self.depth, params)
441    }
442
443    /// Get the physical dimensions of the simulation space.
444    pub fn physical_dimensions(&self) -> (f32, f32, f32) {
445        (
446            self.width as f32 * self.cell_size,
447            self.height as f32 * self.cell_size,
448            self.depth as f32 * self.cell_size,
449        )
450    }
451
452    /// Get the maximum accurately simulated frequency.
453    pub fn max_frequency(&self, speed_of_sound: f32) -> f32 {
454        speed_of_sound / (10.0 * self.cell_size)
455    }
456}
457
458#[cfg(test)]
459mod tests {
460    use super::*;
461
462    #[test]
463    fn test_simulation_engine_cpu() {
464        let mut engine = SimulationEngine::new_cpu(32, 32, 32, AcousticParams3D::default());
465
466        engine.inject_impulse(16, 16, 16, 1.0);
467        engine.step();
468
469        assert_eq!(engine.step_count(), 1);
470        assert!(engine.time() > 0.0);
471    }
472
473    #[test]
474    fn test_simulation_config() {
475        let config = SimulationConfig::default();
476        let (w, h, d) = config.physical_dimensions();
477
478        assert!(w > 0.0);
479        assert!(h > 0.0);
480        assert!(d > 0.0);
481    }
482
483    #[test]
484    fn test_config_presets() {
485        let small = SimulationConfig::small_room();
486        let medium = SimulationConfig::medium_room();
487        let large = SimulationConfig::large_space();
488        let water = SimulationConfig::underwater();
489
490        assert!(small.width < medium.width || small.cell_size < medium.cell_size);
491        assert!(medium.width < large.width || medium.cell_size < large.cell_size);
492        assert_eq!(water.environment.medium, Medium::Water);
493    }
494
495    #[test]
496    fn test_computation_method_default() {
497        let config = SimulationConfig::default();
498        assert_eq!(config.computation_method, ComputationMethod::Stencil);
499    }
500
501    #[test]
502    fn test_computation_method_actor() {
503        let config = SimulationConfig::default().with_computation_method(ComputationMethod::Actor);
504        assert_eq!(config.computation_method, ComputationMethod::Actor);
505    }
506
507    #[test]
508    fn test_engine_computation_method() {
509        let engine = SimulationEngine::new_cpu(16, 16, 16, AcousticParams3D::default());
510        assert_eq!(engine.computation_method(), ComputationMethod::Stencil);
511    }
512}