1pub 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
71pub enum ComputationMethod {
72 #[default]
76 Stencil,
77
78 Actor,
87
88 BlockActor,
96
97 Persistent,
107}
108
109pub struct SimulationEngine {
111 pub grid: SimulationGrid3D,
113 pub use_gpu: bool,
115 pub computation_method: ComputationMethod,
117 #[cfg(feature = "cuda")]
119 pub gpu: Option<gpu_backend::GpuBackend3D>,
120 #[cfg(feature = "cuda")]
122 pub actor_gpu: Option<actor_backend::ActorGpuBackend3D>,
123 #[cfg(feature = "cuda")]
125 pub block_actor_gpu: Option<block_actor_backend::BlockActorGpuBackend>,
126 #[cfg(all(feature = "cuda", feature = "cuda-codegen"))]
128 pub persistent_gpu: Option<persistent_backend::PersistentBackend>,
129}
130
131impl SimulationEngine {
132 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 #[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 #[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 #[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 #[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 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 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 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 }
296 }
297 }
298
299 self.grid.step_sequential();
301 }
302
303 pub fn step_n(&mut self, n: usize) {
305 for _ in 0..n {
306 self.step();
307 }
308 }
309
310 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 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 let _ = persistent_gpu.inject_impulse(x as u32, y as u32, z as u32, amplitude);
351 }
352 }
353 }
354
355 pub fn time(&self) -> f32 {
357 self.grid.time
358 }
359
360 pub fn step_count(&self) -> u64 {
362 self.grid.step
363 }
364
365 pub fn dimensions(&self) -> (usize, usize, usize) {
367 self.grid.dimensions()
368 }
369
370 #[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 self.use_gpu = false;
421 }
422 }
423 } else {
424 self.use_gpu = false;
425 }
426 }
427
428 #[cfg(feature = "cuda")]
430 pub fn set_computation_method(&mut self, method: ComputationMethod) {
431 self.computation_method = method;
432 if self.use_gpu {
434 self.set_use_gpu(true);
435 }
436 }
437
438 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 pub fn computation_method(&self) -> ComputationMethod {
460 self.computation_method
461 }
462
463 #[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 #[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 #[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 #[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 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 pub fn grid(&self) -> &grid3d::SimulationGrid3D {
529 &self.grid
530 }
531}
532
533#[derive(Debug, Clone)]
535pub struct SimulationConfig {
536 pub width: usize,
538 pub height: usize,
540 pub depth: usize,
542 pub cell_size: f32,
544 pub environment: Environment,
546 pub prefer_gpu: bool,
548 pub computation_method: ComputationMethod,
550 #[cfg(feature = "cuda")]
552 pub actor_config: actor_backend::ActorBackendConfig,
553 #[cfg(feature = "cuda")]
555 pub block_actor_config: block_actor_backend::BlockActorConfig,
556 #[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, 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 pub fn small_room() -> Self {
584 Self {
585 width: 200,
586 height: 60,
587 depth: 200,
588 ..Default::default()
589 }
590 }
591
592 pub fn medium_room() -> Self {
594 Self {
595 width: 200,
596 height: 50,
597 depth: 200,
598 cell_size: 0.1, ..Default::default()
600 }
601 }
602
603 pub fn large_space() -> Self {
605 Self {
606 width: 250,
607 height: 50,
608 depth: 250,
609 cell_size: 0.2, ..Default::default()
611 }
612 }
613
614 pub fn underwater() -> Self {
616 Self {
617 environment: Environment::default().with_medium(Medium::Water),
618 ..Self::medium_room()
619 }
620 }
621
622 pub fn with_environment(mut self, env: Environment) -> Self {
624 self.environment = env;
625 self
626 }
627
628 pub fn with_computation_method(mut self, method: ComputationMethod) -> Self {
633 self.computation_method = method;
634 self
635 }
636
637 #[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 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 }
700 }
701 }
702
703 SimulationEngine::new_cpu(self.width, self.height, self.depth, params)
704 }
705
706 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 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}