Skip to main content

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
39#[cfg(feature = "cuda")]
40pub mod block_actor_backend;
41
42#[cfg(all(feature = "cuda", feature = "cuda-codegen"))]
43pub mod persistent_backend;
44
45#[cfg(feature = "cuda-codegen")]
46pub mod kernels3d;
47
48pub use grid3d::{CellType, GridParams, SimulationGrid3D};
49pub use physics::{
50    AcousticParams3D, AtmosphericAbsorption, Environment, Medium, MediumProperties,
51    MultiBandDamping, Orientation3D, Position3D,
52};
53
54#[cfg(feature = "cuda")]
55pub use actor_backend::{
56    ActorBackendConfig, ActorError, ActorStats, CellActorState, Direction3D, HaloMessage,
57};
58
59#[cfg(feature = "cuda")]
60pub use block_actor_backend::{
61    BlockActorConfig, BlockActorError, BlockActorGpuBackend, BlockActorStats,
62};
63
64#[cfg(all(feature = "cuda", feature = "cuda-codegen"))]
65pub use persistent_backend::{
66    PersistentBackend, PersistentBackendConfig, PersistentBackendError, PersistentBackendStats,
67};
68
69/// Computation method for GPU-accelerated simulation.
70#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
71pub enum ComputationMethod {
72    /// Traditional stencil-based GPU computation.
73    /// Each thread reads neighbor values from global/shared memory.
74    /// Fast and efficient for regular grids.
75    #[default]
76    Stencil,
77
78    /// Actor-based GPU computation (cell-as-actor paradigm).
79    /// Each spatial cell is an independent actor that:
80    /// - Holds its own state (pressure, cell type)
81    /// - Communicates with neighbors via message passing
82    /// - Uses HLC for temporal alignment
83    ///
84    /// This method demonstrates the actor model on GPUs but may have
85    /// higher overhead than stencil computation.
86    Actor,
87
88    /// Block-based actor computation (hybrid approach).
89    /// Each 8×8×8 block of cells is a single actor (512 cells per actor).
90    /// - Intra-block: Fast stencil computation with shared memory
91    /// - Inter-block: Double-buffered message passing (no atomics)
92    ///
93    /// Combines actor model benefits with stencil performance.
94    /// Expected: 10-50× faster than per-cell Actor method.
95    BlockActor,
96
97    /// Truly persistent GPU actor paradigm.
98    /// A single kernel runs for the entire simulation lifetime:
99    /// - H2K (Host→Kernel) command messaging via mapped memory
100    /// - K2H (Kernel→Host) response messaging
101    /// - K2K halo exchange between blocks on device memory
102    /// - Grid-wide synchronization via cooperative groups
103    ///
104    /// No kernel launch overhead per step - commands control simulation
105    /// from host while GPU runs continuously.
106    Persistent,
107}
108
109/// Simulation engine that manages grid updates and GPU/CPU dispatch.
110pub struct SimulationEngine {
111    /// 3D simulation grid
112    pub grid: SimulationGrid3D,
113    /// Use GPU backend if available
114    pub use_gpu: bool,
115    /// Computation method for GPU
116    pub computation_method: ComputationMethod,
117    /// Stencil GPU backend
118    #[cfg(feature = "cuda")]
119    pub gpu: Option<gpu_backend::GpuBackend3D>,
120    /// Actor GPU backend
121    #[cfg(feature = "cuda")]
122    pub actor_gpu: Option<actor_backend::ActorGpuBackend3D>,
123    /// Block actor GPU backend
124    #[cfg(feature = "cuda")]
125    pub block_actor_gpu: Option<block_actor_backend::BlockActorGpuBackend>,
126    /// Persistent GPU backend (true GPU actor with single kernel launch)
127    #[cfg(all(feature = "cuda", feature = "cuda-codegen"))]
128    pub persistent_gpu: Option<persistent_backend::PersistentBackend>,
129}
130
131impl SimulationEngine {
132    /// Create a new simulation engine with CPU backend.
133    pub fn new_cpu(width: usize, height: usize, depth: usize, params: AcousticParams3D) -> Self {
134        Self {
135            grid: SimulationGrid3D::new(width, height, depth, params),
136            use_gpu: false,
137            computation_method: ComputationMethod::Stencil,
138            #[cfg(feature = "cuda")]
139            gpu: None,
140            #[cfg(feature = "cuda")]
141            actor_gpu: None,
142            #[cfg(feature = "cuda")]
143            block_actor_gpu: None,
144            #[cfg(all(feature = "cuda", feature = "cuda-codegen"))]
145            persistent_gpu: None,
146        }
147    }
148
149    /// Create a new simulation engine with GPU backend (stencil method).
150    #[cfg(feature = "cuda")]
151    pub fn new_gpu(
152        width: usize,
153        height: usize,
154        depth: usize,
155        params: AcousticParams3D,
156    ) -> Result<Self, gpu_backend::GpuError> {
157        let grid = SimulationGrid3D::new(width, height, depth, params);
158        let gpu = gpu_backend::GpuBackend3D::new(&grid)?;
159
160        Ok(Self {
161            grid,
162            use_gpu: true,
163            computation_method: ComputationMethod::Stencil,
164            gpu: Some(gpu),
165            actor_gpu: None,
166            block_actor_gpu: None,
167            #[cfg(feature = "cuda-codegen")]
168            persistent_gpu: None,
169        })
170    }
171
172    /// Create a new simulation engine with GPU backend using actor model.
173    ///
174    /// This uses the cell-as-actor paradigm where each spatial cell is an
175    /// independent actor that communicates with neighbors via message passing.
176    #[cfg(feature = "cuda")]
177    pub fn new_gpu_actor(
178        width: usize,
179        height: usize,
180        depth: usize,
181        params: AcousticParams3D,
182        actor_config: actor_backend::ActorBackendConfig,
183    ) -> Result<Self, actor_backend::ActorError> {
184        let grid = SimulationGrid3D::new(width, height, depth, params);
185        let actor_gpu = actor_backend::ActorGpuBackend3D::new(&grid, actor_config)?;
186
187        Ok(Self {
188            grid,
189            use_gpu: true,
190            computation_method: ComputationMethod::Actor,
191            gpu: None,
192            actor_gpu: Some(actor_gpu),
193            block_actor_gpu: None,
194            #[cfg(feature = "cuda-codegen")]
195            persistent_gpu: None,
196        })
197    }
198
199    /// Create a new simulation engine with GPU backend using block actor model.
200    ///
201    /// This uses a hybrid approach where each 8×8×8 block of cells is a single actor.
202    /// Intra-block computation uses fast stencil, inter-block uses double-buffered messaging.
203    #[cfg(feature = "cuda")]
204    pub fn new_gpu_block_actor(
205        width: usize,
206        height: usize,
207        depth: usize,
208        params: AcousticParams3D,
209        config: block_actor_backend::BlockActorConfig,
210    ) -> Result<Self, block_actor_backend::BlockActorError> {
211        let grid = SimulationGrid3D::new(width, height, depth, params);
212        let block_actor_gpu = block_actor_backend::BlockActorGpuBackend::new(&grid, config)?;
213
214        Ok(Self {
215            grid,
216            use_gpu: true,
217            computation_method: ComputationMethod::BlockActor,
218            gpu: None,
219            actor_gpu: None,
220            block_actor_gpu: Some(block_actor_gpu),
221            #[cfg(feature = "cuda-codegen")]
222            persistent_gpu: None,
223        })
224    }
225
226    /// Create a new simulation engine with persistent GPU backend.
227    ///
228    /// This uses the truly persistent GPU actor paradigm where a single kernel
229    /// runs for the entire simulation lifetime. Commands are sent via H2K queue
230    /// and the kernel processes them continuously.
231    #[cfg(all(feature = "cuda", feature = "cuda-codegen"))]
232    pub fn new_gpu_persistent(
233        width: usize,
234        height: usize,
235        depth: usize,
236        params: AcousticParams3D,
237        config: persistent_backend::PersistentBackendConfig,
238    ) -> Result<Self, persistent_backend::PersistentBackendError> {
239        let grid = SimulationGrid3D::new(width, height, depth, params);
240        let persistent_gpu = persistent_backend::PersistentBackend::new(&grid, config)?;
241
242        Ok(Self {
243            grid,
244            use_gpu: true,
245            computation_method: ComputationMethod::Persistent,
246            #[cfg(feature = "cuda")]
247            gpu: None,
248            #[cfg(feature = "cuda")]
249            actor_gpu: None,
250            #[cfg(feature = "cuda")]
251            block_actor_gpu: None,
252            persistent_gpu: Some(persistent_gpu),
253        })
254    }
255
256    /// Perform one simulation step.
257    pub fn step(&mut self) {
258        #[cfg(feature = "cuda")]
259        if self.use_gpu {
260            match self.computation_method {
261                ComputationMethod::Stencil => {
262                    if let Some(ref mut gpu) = self.gpu {
263                        if gpu.step(&mut self.grid).is_ok() {
264                            return;
265                        }
266                    }
267                }
268                ComputationMethod::Actor => {
269                    if let Some(ref mut actor_gpu) = self.actor_gpu {
270                        if actor_gpu.step(&mut self.grid, 1).is_ok() {
271                            return;
272                        }
273                    }
274                }
275                ComputationMethod::BlockActor => {
276                    if let Some(ref mut block_actor_gpu) = self.block_actor_gpu {
277                        // Use fused kernel for better performance (single kernel launch)
278                        if block_actor_gpu.step_fused(&mut self.grid, 1).is_ok() {
279                            return;
280                        }
281                    }
282                }
283                #[cfg(feature = "cuda-codegen")]
284                ComputationMethod::Persistent => {
285                    if let Some(ref mut persistent_gpu) = self.persistent_gpu {
286                        // Persistent kernel runs continuously - just request one step
287                        if persistent_gpu.step(&mut self.grid, 1).is_ok() {
288                            return;
289                        }
290                    }
291                }
292                #[cfg(not(feature = "cuda-codegen"))]
293                ComputationMethod::Persistent => {
294                    // Fall through to CPU when cuda-codegen not enabled
295                }
296            }
297        }
298
299        // Fall back to sequential CPU (parallel can cause issues with GUI event loops)
300        self.grid.step_sequential();
301    }
302
303    /// Perform multiple simulation steps.
304    pub fn step_n(&mut self, n: usize) {
305        for _ in 0..n {
306            self.step();
307        }
308    }
309
310    /// Reset the simulation.
311    pub fn reset(&mut self) {
312        self.grid.reset();
313
314        #[cfg(feature = "cuda")]
315        {
316            if let Some(ref mut gpu) = self.gpu {
317                let _ = gpu.reset(&self.grid);
318            }
319            if let Some(ref mut actor_gpu) = self.actor_gpu {
320                let _ = actor_gpu.reset(&self.grid);
321            }
322            if let Some(ref mut block_actor_gpu) = self.block_actor_gpu {
323                let _ = block_actor_gpu.reset(&self.grid);
324            }
325            #[cfg(feature = "cuda-codegen")]
326            if let Some(ref mut persistent_gpu) = self.persistent_gpu {
327                let _ = persistent_gpu.reset(&self.grid);
328            }
329        }
330    }
331
332    /// Inject an impulse at the given position.
333    pub fn inject_impulse(&mut self, x: usize, y: usize, z: usize, amplitude: f32) {
334        self.grid.inject_impulse(x, y, z, amplitude);
335
336        #[cfg(feature = "cuda")]
337        {
338            if let Some(ref mut gpu) = self.gpu {
339                let _ = gpu.upload_pressure(&self.grid);
340            }
341            if let Some(ref mut actor_gpu) = self.actor_gpu {
342                let _ = actor_gpu.upload_pressure(&self.grid);
343            }
344            if let Some(ref mut block_actor_gpu) = self.block_actor_gpu {
345                let _ = block_actor_gpu.upload_pressure(&self.grid);
346            }
347            #[cfg(feature = "cuda-codegen")]
348            if let Some(ref mut persistent_gpu) = self.persistent_gpu {
349                // Persistent kernel receives impulse via H2K message queue
350                let _ = persistent_gpu.inject_impulse(x as u32, y as u32, z as u32, amplitude);
351            }
352        }
353    }
354
355    /// Get current simulation time.
356    pub fn time(&self) -> f32 {
357        self.grid.time
358    }
359
360    /// Get current step count.
361    pub fn step_count(&self) -> u64 {
362        self.grid.step
363    }
364
365    /// Get grid dimensions.
366    pub fn dimensions(&self) -> (usize, usize, usize) {
367        self.grid.dimensions()
368    }
369
370    /// Enable or disable GPU acceleration.
371    #[cfg(feature = "cuda")]
372    pub fn set_use_gpu(&mut self, use_gpu: bool) {
373        if use_gpu {
374            match self.computation_method {
375                ComputationMethod::Stencil => {
376                    if self.gpu.is_none() {
377                        if let Ok(gpu) = gpu_backend::GpuBackend3D::new(&self.grid) {
378                            self.gpu = Some(gpu);
379                        }
380                    }
381                    self.use_gpu = self.gpu.is_some();
382                }
383                ComputationMethod::Actor => {
384                    if self.actor_gpu.is_none() {
385                        if let Ok(actor_gpu) = actor_backend::ActorGpuBackend3D::new(
386                            &self.grid,
387                            actor_backend::ActorBackendConfig::default(),
388                        ) {
389                            self.actor_gpu = Some(actor_gpu);
390                        }
391                    }
392                    self.use_gpu = self.actor_gpu.is_some();
393                }
394                ComputationMethod::BlockActor => {
395                    if self.block_actor_gpu.is_none() {
396                        if let Ok(block_actor_gpu) = block_actor_backend::BlockActorGpuBackend::new(
397                            &self.grid,
398                            block_actor_backend::BlockActorConfig::default(),
399                        ) {
400                            self.block_actor_gpu = Some(block_actor_gpu);
401                        }
402                    }
403                    self.use_gpu = self.block_actor_gpu.is_some();
404                }
405                #[cfg(feature = "cuda-codegen")]
406                ComputationMethod::Persistent => {
407                    if self.persistent_gpu.is_none() {
408                        if let Ok(persistent_gpu) = persistent_backend::PersistentBackend::new(
409                            &self.grid,
410                            persistent_backend::PersistentBackendConfig::default(),
411                        ) {
412                            self.persistent_gpu = Some(persistent_gpu);
413                        }
414                    }
415                    self.use_gpu = self.persistent_gpu.is_some();
416                }
417                #[cfg(not(feature = "cuda-codegen"))]
418                ComputationMethod::Persistent => {
419                    // Persistent method requires cuda-codegen feature
420                    self.use_gpu = false;
421                }
422            }
423        } else {
424            self.use_gpu = false;
425        }
426    }
427
428    /// Set the computation method for GPU.
429    #[cfg(feature = "cuda")]
430    pub fn set_computation_method(&mut self, method: ComputationMethod) {
431        self.computation_method = method;
432        // Re-initialize the appropriate backend if GPU is in use
433        if self.use_gpu {
434            self.set_use_gpu(true);
435        }
436    }
437
438    /// Check if GPU is being used.
439    pub fn is_using_gpu(&self) -> bool {
440        #[cfg(feature = "cuda")]
441        {
442            match self.computation_method {
443                ComputationMethod::Stencil => self.use_gpu && self.gpu.is_some(),
444                ComputationMethod::Actor => self.use_gpu && self.actor_gpu.is_some(),
445                ComputationMethod::BlockActor => self.use_gpu && self.block_actor_gpu.is_some(),
446                #[cfg(feature = "cuda-codegen")]
447                ComputationMethod::Persistent => self.use_gpu && self.persistent_gpu.is_some(),
448                #[cfg(not(feature = "cuda-codegen"))]
449                ComputationMethod::Persistent => false,
450            }
451        }
452        #[cfg(not(feature = "cuda"))]
453        {
454            false
455        }
456    }
457
458    /// Get the current computation method.
459    pub fn computation_method(&self) -> ComputationMethod {
460        self.computation_method
461    }
462
463    /// Download pressure data from GPU to CPU grid (if using GPU).
464    #[cfg(feature = "cuda")]
465    pub fn sync_from_gpu(&mut self) {
466        if self.use_gpu {
467            match self.computation_method {
468                ComputationMethod::Stencil => {
469                    if let Some(ref gpu) = self.gpu {
470                        let _ = gpu.download_pressure(&mut self.grid);
471                    }
472                }
473                ComputationMethod::Actor => {
474                    if let Some(ref actor_gpu) = self.actor_gpu {
475                        let _ = actor_gpu.download_pressure(&mut self.grid);
476                    }
477                }
478                ComputationMethod::BlockActor => {
479                    if let Some(ref block_actor_gpu) = self.block_actor_gpu {
480                        let _ = block_actor_gpu.download_pressure(&mut self.grid);
481                    }
482                }
483                #[cfg(feature = "cuda-codegen")]
484                ComputationMethod::Persistent => {
485                    if let Some(ref persistent_gpu) = self.persistent_gpu {
486                        let _ = persistent_gpu.download_pressure(&mut self.grid);
487                    }
488                }
489                #[cfg(not(feature = "cuda-codegen"))]
490                ComputationMethod::Persistent => {}
491            }
492        }
493    }
494
495    /// Get actor backend statistics (if using actor method).
496    #[cfg(feature = "cuda")]
497    pub fn actor_stats(&self) -> Option<actor_backend::ActorStats> {
498        self.actor_gpu.as_ref().map(|gpu| gpu.stats())
499    }
500
501    /// Get block actor backend statistics (if using block actor method).
502    #[cfg(feature = "cuda")]
503    pub fn block_actor_stats(&self) -> Option<block_actor_backend::BlockActorStats> {
504        self.block_actor_gpu.as_ref().map(|gpu| gpu.stats())
505    }
506
507    /// Get persistent backend statistics (if using persistent method).
508    #[cfg(all(feature = "cuda", feature = "cuda-codegen"))]
509    pub fn persistent_stats(&self) -> Option<persistent_backend::PersistentBackendStats> {
510        self.persistent_gpu.as_ref().map(|gpu| gpu.stats())
511    }
512
513    /// Get pressure value at a specific grid cell.
514    ///
515    /// Note: If using GPU, this reads from the CPU grid which may be stale.
516    /// Call `sync_from_gpu()` first to get latest values.
517    pub fn pressure_at(&self, x: usize, y: usize, z: usize) -> f32 {
518        let (width, height, depth) = self.grid.dimensions();
519        if x < width && y < height && z < depth {
520            let idx = x + y * width + z * width * height;
521            self.grid.pressure.get(idx).copied().unwrap_or(0.0)
522        } else {
523            0.0
524        }
525    }
526
527    /// Get reference to the underlying grid.
528    pub fn grid(&self) -> &grid3d::SimulationGrid3D {
529        &self.grid
530    }
531}
532
533/// Configuration for creating a simulation.
534#[derive(Debug, Clone)]
535pub struct SimulationConfig {
536    /// Grid width (cells)
537    pub width: usize,
538    /// Grid height (cells)
539    pub height: usize,
540    /// Grid depth (cells)
541    pub depth: usize,
542    /// Cell size (meters)
543    pub cell_size: f32,
544    /// Environment settings
545    pub environment: Environment,
546    /// Use GPU if available
547    pub prefer_gpu: bool,
548    /// Computation method for GPU
549    pub computation_method: ComputationMethod,
550    /// Actor backend configuration (for Actor method)
551    #[cfg(feature = "cuda")]
552    pub actor_config: actor_backend::ActorBackendConfig,
553    /// Block actor backend configuration (for BlockActor method)
554    #[cfg(feature = "cuda")]
555    pub block_actor_config: block_actor_backend::BlockActorConfig,
556    /// Persistent backend configuration (for Persistent method)
557    #[cfg(all(feature = "cuda", feature = "cuda-codegen"))]
558    pub persistent_config: persistent_backend::PersistentBackendConfig,
559}
560
561impl Default for SimulationConfig {
562    fn default() -> Self {
563        Self {
564            width: 64,
565            height: 64,
566            depth: 64,
567            cell_size: 0.05, // 5cm cells
568            environment: Environment::default(),
569            prefer_gpu: true,
570            computation_method: ComputationMethod::Stencil,
571            #[cfg(feature = "cuda")]
572            actor_config: actor_backend::ActorBackendConfig::default(),
573            #[cfg(feature = "cuda")]
574            block_actor_config: block_actor_backend::BlockActorConfig::default(),
575            #[cfg(all(feature = "cuda", feature = "cuda-codegen"))]
576            persistent_config: persistent_backend::PersistentBackendConfig::default(),
577        }
578    }
579}
580
581impl SimulationConfig {
582    /// Create a configuration for a small room (10m x 10m x 3m).
583    pub fn small_room() -> Self {
584        Self {
585            width: 200,
586            height: 60,
587            depth: 200,
588            ..Default::default()
589        }
590    }
591
592    /// Create a configuration for a medium room (20m x 20m x 5m).
593    pub fn medium_room() -> Self {
594        Self {
595            width: 200,
596            height: 50,
597            depth: 200,
598            cell_size: 0.1, // 10cm cells
599            ..Default::default()
600        }
601    }
602
603    /// Create a configuration for a large space (50m x 50m x 10m).
604    pub fn large_space() -> Self {
605        Self {
606            width: 250,
607            height: 50,
608            depth: 250,
609            cell_size: 0.2, // 20cm cells
610            ..Default::default()
611        }
612    }
613
614    /// Create a configuration for underwater simulation.
615    pub fn underwater() -> Self {
616        Self {
617            environment: Environment::default().with_medium(Medium::Water),
618            ..Self::medium_room()
619        }
620    }
621
622    /// Set the environment.
623    pub fn with_environment(mut self, env: Environment) -> Self {
624        self.environment = env;
625        self
626    }
627
628    /// Set the computation method.
629    ///
630    /// - `Stencil`: Traditional GPU stencil computation (default)
631    /// - `Actor`: Cell-as-actor paradigm with message-based halo exchange
632    pub fn with_computation_method(mut self, method: ComputationMethod) -> Self {
633        self.computation_method = method;
634        self
635    }
636
637    /// Set the actor backend configuration (only used with Actor method).
638    #[cfg(feature = "cuda")]
639    pub fn with_actor_config(mut self, config: actor_backend::ActorBackendConfig) -> Self {
640        self.actor_config = config;
641        self
642    }
643
644    /// Build the simulation engine.
645    pub fn build(self) -> SimulationEngine {
646        let params = AcousticParams3D::new(self.environment, self.cell_size);
647
648        #[cfg(feature = "cuda")]
649        if self.prefer_gpu {
650            match self.computation_method {
651                ComputationMethod::Stencil => {
652                    if let Ok(engine) = SimulationEngine::new_gpu(
653                        self.width,
654                        self.height,
655                        self.depth,
656                        params.clone(),
657                    ) {
658                        return engine;
659                    }
660                }
661                ComputationMethod::Actor => {
662                    if let Ok(engine) = SimulationEngine::new_gpu_actor(
663                        self.width,
664                        self.height,
665                        self.depth,
666                        params.clone(),
667                        self.actor_config,
668                    ) {
669                        return engine;
670                    }
671                }
672                ComputationMethod::BlockActor => {
673                    if let Ok(engine) = SimulationEngine::new_gpu_block_actor(
674                        self.width,
675                        self.height,
676                        self.depth,
677                        params.clone(),
678                        self.block_actor_config,
679                    ) {
680                        return engine;
681                    }
682                }
683                #[cfg(feature = "cuda-codegen")]
684                ComputationMethod::Persistent => {
685                    if let Ok(engine) = SimulationEngine::new_gpu_persistent(
686                        self.width,
687                        self.height,
688                        self.depth,
689                        params.clone(),
690                        self.persistent_config,
691                    ) {
692                        return engine;
693                    }
694                }
695                #[cfg(not(feature = "cuda-codegen"))]
696                ComputationMethod::Persistent => {
697                    // Persistent method requires cuda-codegen feature
698                    // Fall through to CPU
699                }
700            }
701        }
702
703        SimulationEngine::new_cpu(self.width, self.height, self.depth, params)
704    }
705
706    /// Get the physical dimensions of the simulation space.
707    pub fn physical_dimensions(&self) -> (f32, f32, f32) {
708        (
709            self.width as f32 * self.cell_size,
710            self.height as f32 * self.cell_size,
711            self.depth as f32 * self.cell_size,
712        )
713    }
714
715    /// Get the maximum accurately simulated frequency.
716    pub fn max_frequency(&self, speed_of_sound: f32) -> f32 {
717        speed_of_sound / (10.0 * self.cell_size)
718    }
719}
720
721#[cfg(test)]
722mod tests {
723    use super::*;
724
725    #[test]
726    fn test_simulation_engine_cpu() {
727        let mut engine = SimulationEngine::new_cpu(32, 32, 32, AcousticParams3D::default());
728
729        engine.inject_impulse(16, 16, 16, 1.0);
730        engine.step();
731
732        assert_eq!(engine.step_count(), 1);
733        assert!(engine.time() > 0.0);
734    }
735
736    #[test]
737    fn test_simulation_config() {
738        let config = SimulationConfig::default();
739        let (w, h, d) = config.physical_dimensions();
740
741        assert!(w > 0.0);
742        assert!(h > 0.0);
743        assert!(d > 0.0);
744    }
745
746    #[test]
747    fn test_config_presets() {
748        let small = SimulationConfig::small_room();
749        let medium = SimulationConfig::medium_room();
750        let large = SimulationConfig::large_space();
751        let water = SimulationConfig::underwater();
752
753        assert!(small.width < medium.width || small.cell_size < medium.cell_size);
754        assert!(medium.width < large.width || medium.cell_size < large.cell_size);
755        assert_eq!(water.environment.medium, Medium::Water);
756    }
757
758    #[test]
759    fn test_computation_method_default() {
760        let config = SimulationConfig::default();
761        assert_eq!(config.computation_method, ComputationMethod::Stencil);
762    }
763
764    #[test]
765    fn test_computation_method_actor() {
766        let config = SimulationConfig::default().with_computation_method(ComputationMethod::Actor);
767        assert_eq!(config.computation_method, ComputationMethod::Actor);
768    }
769
770    #[test]
771    fn test_engine_computation_method() {
772        let engine = SimulationEngine::new_cpu(16, 16, 16, AcousticParams3D::default());
773        assert_eq!(engine.computation_method(), ComputationMethod::Stencil);
774    }
775}