ringkernel_wavesim3d/simulation/
mod.rs1pub 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
52pub enum ComputationMethod {
53 #[default]
57 Stencil,
58
59 Actor,
68}
69
70pub struct SimulationEngine {
72 pub grid: SimulationGrid3D,
74 pub use_gpu: bool,
76 pub computation_method: ComputationMethod,
78 #[cfg(feature = "cuda")]
80 pub gpu: Option<gpu_backend::GpuBackend3D>,
81 #[cfg(feature = "cuda")]
83 pub actor_gpu: Option<actor_backend::ActorGpuBackend3D>,
84}
85
86impl SimulationEngine {
87 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 #[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 #[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 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 self.grid.step_sequential();
168 }
169
170 pub fn step_n(&mut self, n: usize) {
172 for _ in 0..n {
173 self.step();
174 }
175 }
176
177 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 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 pub fn time(&self) -> f32 {
209 self.grid.time
210 }
211
212 pub fn step_count(&self) -> u64 {
214 self.grid.step
215 }
216
217 pub fn dimensions(&self) -> (usize, usize, usize) {
219 self.grid.dimensions()
220 }
221
222 #[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 #[cfg(feature = "cuda")]
254 pub fn set_computation_method(&mut self, method: ComputationMethod) {
255 self.computation_method = method;
256 if self.use_gpu {
258 self.set_use_gpu(true);
259 }
260 }
261
262 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 pub fn computation_method(&self) -> ComputationMethod {
279 self.computation_method
280 }
281
282 #[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 #[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#[derive(Debug, Clone)]
310pub struct SimulationConfig {
311 pub width: usize,
313 pub height: usize,
315 pub depth: usize,
317 pub cell_size: f32,
319 pub environment: Environment,
321 pub prefer_gpu: bool,
323 pub computation_method: ComputationMethod,
325 #[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, 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 pub fn small_room() -> Self {
349 Self {
350 width: 200,
351 height: 60,
352 depth: 200,
353 ..Default::default()
354 }
355 }
356
357 pub fn medium_room() -> Self {
359 Self {
360 width: 200,
361 height: 50,
362 depth: 200,
363 cell_size: 0.1, ..Default::default()
365 }
366 }
367
368 pub fn large_space() -> Self {
370 Self {
371 width: 250,
372 height: 50,
373 depth: 250,
374 cell_size: 0.2, ..Default::default()
376 }
377 }
378
379 pub fn underwater() -> Self {
381 Self {
382 environment: Environment::default().with_medium(Medium::Water),
383 ..Self::medium_room()
384 }
385 }
386
387 pub fn with_environment(mut self, env: Environment) -> Self {
389 self.environment = env;
390 self
391 }
392
393 pub fn with_computation_method(mut self, method: ComputationMethod) -> Self {
398 self.computation_method = method;
399 self
400 }
401
402 #[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 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 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 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}