1use super::AcousticParams;
7use rayon::prelude::*;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
13pub enum CellType {
14 #[default]
16 Normal,
17 Absorber,
19 Reflector,
21}
22
23pub struct SimulationGrid {
29 pub width: u32,
31 pub height: u32,
33
34 pressure: Vec<f32>,
36 pressure_prev: Vec<f32>,
38
39 is_boundary: Vec<bool>,
41 reflection_coeff: Vec<f32>,
43 cell_types: Vec<CellType>,
45
46 pub params: AcousticParams,
48}
49
50impl SimulationGrid {
51 pub fn new(width: u32, height: u32, params: AcousticParams) -> Self {
58 let size = (width * height) as usize;
59
60 let mut grid = Self {
61 width,
62 height,
63 pressure: vec![0.0; size],
64 pressure_prev: vec![0.0; size],
65 is_boundary: vec![false; size],
66 reflection_coeff: vec![1.0; size],
67 cell_types: vec![CellType::Normal; size],
68 params,
69 };
70 grid.initialize_boundaries();
71 grid
72 }
73
74 fn initialize_boundaries(&mut self) {
76 let width = self.width as usize;
77 let height = self.height as usize;
78
79 for y in 0..height {
80 for x in 0..width {
81 let idx = y * width + x;
82 let is_boundary = x == 0 || y == 0 || x == width - 1 || y == height - 1;
83 self.is_boundary[idx] = is_boundary;
84 self.reflection_coeff[idx] = if is_boundary { 0.95 } else { 1.0 };
85 }
86 }
87 }
88
89 #[inline(always)]
91 fn idx(&self, x: u32, y: u32) -> usize {
92 (y as usize) * (self.width as usize) + (x as usize)
93 }
94
95 pub fn step(&mut self) {
102 let width = self.width as usize;
103 let height = self.height as usize;
104 let c2 = self.params.courant_number().powi(2);
105 let damping = 1.0 - self.params.damping;
106
107 self.step_interior_parallel(width, height, c2, damping);
109
110 self.step_boundaries(c2, damping);
112
113 std::mem::swap(&mut self.pressure, &mut self.pressure_prev);
115 }
116
117 #[inline]
122 fn step_interior_parallel(&mut self, width: usize, height: usize, c2: f32, damping: f32) {
123 const PARALLEL_THRESHOLD: usize = 512;
126
127 if width >= PARALLEL_THRESHOLD || height >= PARALLEL_THRESHOLD {
128 self.step_interior_parallel_impl(width, height, c2, damping);
129 } else {
130 self.step_interior_sequential(width, height, c2, damping);
131 }
132 }
133
134 #[inline]
136 fn step_interior_sequential(&mut self, width: usize, height: usize, c2: f32, damping: f32) {
137 for y in 1..height - 1 {
138 let row_start = y * width;
139
140 #[cfg(feature = "simd")]
141 {
142 Self::fdtd_row_simd_inline(
143 &self.pressure,
144 &mut self.pressure_prev[row_start..row_start + width],
145 width,
146 row_start,
147 c2,
148 damping,
149 );
150 }
151
152 #[cfg(not(feature = "simd"))]
153 {
154 for x in 1..width - 1 {
155 let idx = row_start + x;
156
157 let p_curr = self.pressure[idx];
158 let p_prev = self.pressure_prev[idx];
159 let p_north = self.pressure[idx - width];
160 let p_south = self.pressure[idx + width];
161 let p_west = self.pressure[idx - 1];
162 let p_east = self.pressure[idx + 1];
163
164 let laplacian = p_north + p_south + p_east + p_west - 4.0 * p_curr;
165 let p_new = 2.0 * p_curr - p_prev + c2 * laplacian;
166
167 self.pressure_prev[idx] = p_new * damping;
168 }
169 }
170 }
171 }
172
173 #[inline]
178 fn step_interior_parallel_impl(&mut self, width: usize, height: usize, c2: f32, damping: f32) {
179 let pressure = &self.pressure;
181
182 let interior_start = width; let interior_end = (height - 1) * width; let pressure_prev_interior = &mut self.pressure_prev[interior_start..interior_end];
189
190 pressure_prev_interior
192 .par_chunks_mut(width)
193 .enumerate()
194 .for_each(|(row_offset, prev_row)| {
195 let y = row_offset + 1; let row_start = y * width;
197
198 #[cfg(feature = "simd")]
199 {
200 Self::fdtd_row_simd_inline(pressure, prev_row, width, row_start, c2, damping);
202 }
203
204 #[cfg(not(feature = "simd"))]
205 {
206 for x in 1..width - 1 {
208 let idx = row_start + x;
209 let local_idx = x; let p_curr = pressure[idx];
212 let p_prev = prev_row[local_idx];
213 let p_north = pressure[idx - width];
214 let p_south = pressure[idx + width];
215 let p_west = pressure[idx - 1];
216 let p_east = pressure[idx + 1];
217
218 let laplacian = p_north + p_south + p_east + p_west - 4.0 * p_curr;
219 let p_new = 2.0 * p_curr - p_prev + c2 * laplacian;
220
221 prev_row[local_idx] = p_new * damping;
222 }
223 }
224 });
225 }
226
227 #[cfg(feature = "simd")]
229 #[inline]
230 fn fdtd_row_simd_inline(
231 pressure: &[f32],
232 prev_row: &mut [f32],
233 width: usize,
234 row_start: usize,
235 c2: f32,
236 damping: f32,
237 ) {
238 use std::simd::f32x8;
239
240 let c2_vec = f32x8::splat(c2);
241 let damping_vec = f32x8::splat(damping);
242 let four = f32x8::splat(4.0);
243 let two = f32x8::splat(2.0);
244
245 let mut x = 1;
246 while x + 8 <= width - 1 {
247 let idx = row_start + x;
248 let local_idx = x;
249
250 let p_curr = f32x8::from_slice(&pressure[idx..idx + 8]);
252 let p_prev = f32x8::from_slice(&prev_row[local_idx..local_idx + 8]);
253 let p_north = f32x8::from_slice(&pressure[idx - width..idx - width + 8]);
254 let p_south = f32x8::from_slice(&pressure[idx + width..idx + width + 8]);
255 let p_west = f32x8::from_slice(&pressure[idx - 1..idx - 1 + 8]);
256 let p_east = f32x8::from_slice(&pressure[idx + 1..idx + 1 + 8]);
257
258 let laplacian = p_north + p_south + p_east + p_west - four * p_curr;
260 let p_new = two * p_curr - p_prev + c2_vec * laplacian;
261 let p_damped = p_new * damping_vec;
262
263 prev_row[local_idx..local_idx + 8].copy_from_slice(&p_damped.to_array());
265
266 x += 8;
267 }
268
269 while x < width - 1 {
271 let idx = row_start + x;
272 let local_idx = x;
273
274 let p_curr = pressure[idx];
275 let p_prev = prev_row[local_idx];
276 let p_north = pressure[idx - width];
277 let p_south = pressure[idx + width];
278 let p_west = pressure[idx - 1];
279 let p_east = pressure[idx + 1];
280
281 let laplacian = p_north + p_south + p_east + p_west - 4.0 * p_curr;
282 let p_new = 2.0 * p_curr - p_prev + c2 * laplacian;
283
284 prev_row[local_idx] = p_new * damping;
285 x += 1;
286 }
287 }
288
289 fn step_boundaries(&mut self, c2: f32, damping: f32) {
291 let width = self.width as usize;
292 let height = self.height as usize;
293
294 for x in 0..width {
296 let idx = x;
298 let p_curr = self.pressure[idx];
299 let p_prev = self.pressure_prev[idx];
300 let refl = self.reflection_coeff[idx];
301
302 let p_south = self.pressure[idx + width];
303 let p_west = if x > 0 {
304 self.pressure[idx - 1]
305 } else {
306 p_curr * refl
307 };
308 let p_east = if x < width - 1 {
309 self.pressure[idx + 1]
310 } else {
311 p_curr * refl
312 };
313 let p_north = p_curr * refl; let laplacian = p_north + p_south + p_east + p_west - 4.0 * p_curr;
316 let p_new = 2.0 * p_curr - p_prev + c2 * laplacian;
317 self.pressure_prev[idx] = p_new * damping * refl;
318
319 let idx = (height - 1) * width + x;
321 let p_curr = self.pressure[idx];
322 let p_prev = self.pressure_prev[idx];
323 let refl = self.reflection_coeff[idx];
324
325 let p_north = self.pressure[idx - width];
326 let p_west = if x > 0 {
327 self.pressure[idx - 1]
328 } else {
329 p_curr * refl
330 };
331 let p_east = if x < width - 1 {
332 self.pressure[idx + 1]
333 } else {
334 p_curr * refl
335 };
336 let p_south = p_curr * refl; let laplacian = p_north + p_south + p_east + p_west - 4.0 * p_curr;
339 let p_new = 2.0 * p_curr - p_prev + c2 * laplacian;
340 self.pressure_prev[idx] = p_new * damping * refl;
341 }
342
343 for y in 1..height - 1 {
345 let idx = y * width;
347 let p_curr = self.pressure[idx];
348 let p_prev = self.pressure_prev[idx];
349 let refl = self.reflection_coeff[idx];
350
351 let p_north = self.pressure[idx - width];
352 let p_south = self.pressure[idx + width];
353 let p_east = self.pressure[idx + 1];
354 let p_west = p_curr * refl; let laplacian = p_north + p_south + p_east + p_west - 4.0 * p_curr;
357 let p_new = 2.0 * p_curr - p_prev + c2 * laplacian;
358 self.pressure_prev[idx] = p_new * damping * refl;
359
360 let idx = y * width + width - 1;
362 let p_curr = self.pressure[idx];
363 let p_prev = self.pressure_prev[idx];
364 let refl = self.reflection_coeff[idx];
365
366 let p_north = self.pressure[idx - width];
367 let p_south = self.pressure[idx + width];
368 let p_west = self.pressure[idx - 1];
369 let p_east = p_curr * refl; let laplacian = p_north + p_south + p_east + p_west - 4.0 * p_curr;
372 let p_new = 2.0 * p_curr - p_prev + c2 * laplacian;
373 self.pressure_prev[idx] = p_new * damping * refl;
374 }
375 }
376
377 pub fn inject_impulse(&mut self, x: u32, y: u32, amplitude: f32) {
379 if x < self.width && y < self.height {
380 let idx = self.idx(x, y);
381 self.pressure[idx] += amplitude;
382 }
383 }
384
385 pub fn get_pressure_grid(&self) -> Vec<Vec<f32>> {
389 let width = self.width as usize;
390 let height = self.height as usize;
391 let mut grid = vec![vec![0.0; width]; height];
392
393 for (y, row) in grid.iter_mut().enumerate().take(height) {
394 for (x, cell) in row.iter_mut().enumerate().take(width) {
395 *cell = self.pressure[y * width + x];
396 }
397 }
398 grid
399 }
400
401 #[inline]
405 pub fn pressure_slice(&self) -> &[f32] {
406 &self.pressure
407 }
408
409 pub fn max_pressure(&self) -> f32 {
411 self.pressure.iter().map(|p| p.abs()).fold(0.0, f32::max)
412 }
413
414 pub fn total_energy(&self) -> f32 {
416 self.pressure.iter().map(|p| p * p).sum()
417 }
418
419 pub fn reset(&mut self) {
421 self.pressure.fill(0.0);
422 self.pressure_prev.fill(0.0);
423 }
424
425 pub fn resize(&mut self, new_width: u32, new_height: u32) {
427 let size = (new_width * new_height) as usize;
428 self.width = new_width;
429 self.height = new_height;
430 self.pressure = vec![0.0; size];
431 self.pressure_prev = vec![0.0; size];
432 self.is_boundary = vec![false; size];
433 self.reflection_coeff = vec![1.0; size];
434 self.cell_types = vec![CellType::Normal; size];
435 self.initialize_boundaries();
436 }
437
438 pub fn set_params(&mut self, params: AcousticParams) {
440 self.params = params;
441 }
442
443 pub fn set_speed_of_sound(&mut self, speed: f32) {
445 self.params.set_speed_of_sound(speed);
446 }
447
448 pub fn set_cell_size(&mut self, size: f32) {
450 self.params.set_cell_size(size);
451 }
452
453 pub fn cell_count(&self) -> usize {
455 self.pressure.len()
456 }
457
458 pub fn get_pressure(&self, x: u32, y: u32) -> Option<f32> {
460 if x < self.width && y < self.height {
461 Some(self.pressure[self.idx(x, y)])
462 } else {
463 None
464 }
465 }
466
467 pub fn is_boundary(&self, x: u32, y: u32) -> bool {
469 if x < self.width && y < self.height {
470 self.is_boundary[self.idx(x, y)]
471 } else {
472 false
473 }
474 }
475
476 pub fn set_cell_type(&mut self, x: u32, y: u32, cell_type: CellType) {
483 if x < self.width && y < self.height {
484 let idx = self.idx(x, y);
485 self.cell_types[idx] = cell_type;
486
487 self.reflection_coeff[idx] = match cell_type {
489 CellType::Normal => {
490 if self.is_boundary[idx] {
492 0.95
493 } else {
494 1.0
495 }
496 }
497 CellType::Absorber => 0.0, CellType::Reflector => 1.0, };
500
501 if cell_type != CellType::Normal {
504 self.is_boundary[idx] = true;
505 } else {
506 let is_edge = x == 0 || y == 0 || x == self.width - 1 || y == self.height - 1;
508 self.is_boundary[idx] = is_edge;
509 }
510 }
511 }
512
513 pub fn get_cell_type(&self, x: u32, y: u32) -> Option<CellType> {
515 if x < self.width && y < self.height {
516 Some(self.cell_types[self.idx(x, y)])
517 } else {
518 None
519 }
520 }
521
522 pub fn cell_types_slice(&self) -> &[CellType] {
524 &self.cell_types
525 }
526
527 pub fn clear_cell_types(&mut self) {
531 for i in 0..self.cell_types.len() {
532 self.cell_types[i] = CellType::Normal;
533 }
534 self.initialize_boundaries();
536 }
537
538 pub fn get_buffers_mut(&mut self) -> (&mut Vec<f32>, &mut Vec<f32>, usize, usize, f32, f32) {
542 let width = self.width as usize;
543 let height = self.height as usize;
544 let c2 = self.params.courant_number().powi(2);
545 let damping = 1.0 - self.params.damping;
546 (
547 &mut self.pressure,
548 &mut self.pressure_prev,
549 width,
550 height,
551 c2,
552 damping,
553 )
554 }
555
556 pub fn swap_buffers(&mut self) {
558 std::mem::swap(&mut self.pressure, &mut self.pressure_prev);
559 }
560}
561
562#[cfg(test)]
563mod tests {
564 use super::*;
565
566 #[test]
567 fn test_grid_creation() {
568 let params = AcousticParams::new(343.0, 1.0);
569 let grid = SimulationGrid::new(8, 8, params);
570
571 assert_eq!(grid.width, 8);
572 assert_eq!(grid.height, 8);
573 assert_eq!(grid.cell_count(), 64);
574 }
575
576 #[test]
577 fn test_boundary_cells() {
578 let params = AcousticParams::new(343.0, 1.0);
579 let grid = SimulationGrid::new(4, 4, params);
580
581 assert!(grid.is_boundary(0, 0));
583 assert!(grid.is_boundary(3, 3));
584
585 assert!(grid.is_boundary(1, 0));
587 assert!(grid.is_boundary(0, 1));
588
589 assert!(!grid.is_boundary(1, 1));
591 assert!(!grid.is_boundary(2, 2));
592 }
593
594 #[test]
595 fn test_impulse_injection() {
596 let params = AcousticParams::new(343.0, 1.0);
597 let mut grid = SimulationGrid::new(8, 8, params);
598
599 grid.inject_impulse(4, 4, 1.0);
600
601 assert_eq!(grid.get_pressure(4, 4), Some(1.0));
602 }
603
604 #[test]
605 fn test_wave_propagation() {
606 let params = AcousticParams::new(343.0, 1.0);
607 let mut grid = SimulationGrid::new(8, 8, params);
608
609 grid.inject_impulse(4, 4, 1.0);
611
612 for _ in 0..10 {
614 grid.step();
615 }
616
617 let neighbor_pressure = grid.get_pressure(4, 5).unwrap();
619 assert!(
620 neighbor_pressure.abs() > 0.0,
621 "Wave should have propagated to neighbor"
622 );
623 }
624
625 #[test]
626 fn test_pressure_grid() {
627 let params = AcousticParams::new(343.0, 1.0);
628 let mut grid = SimulationGrid::new(4, 4, params);
629
630 grid.inject_impulse(2, 2, 0.5);
631
632 let pressure_grid = grid.get_pressure_grid();
633 assert_eq!(pressure_grid.len(), 4);
634 assert_eq!(pressure_grid[0].len(), 4);
635 assert_eq!(pressure_grid[2][2], 0.5);
636 }
637
638 #[test]
639 fn test_reset() {
640 let params = AcousticParams::new(343.0, 1.0);
641 let mut grid = SimulationGrid::new(4, 4, params);
642
643 grid.inject_impulse(2, 2, 1.0);
644 grid.step();
645
646 grid.reset();
647
648 for y in 0..4 {
649 for x in 0..4 {
650 assert_eq!(grid.get_pressure(x, y), Some(0.0));
651 }
652 }
653 }
654
655 #[test]
656 fn test_resize() {
657 let params = AcousticParams::new(343.0, 1.0);
658 let mut grid = SimulationGrid::new(4, 4, params);
659
660 grid.resize(8, 6);
661
662 assert_eq!(grid.width, 8);
663 assert_eq!(grid.height, 6);
664 assert_eq!(grid.cell_count(), 48);
665 }
666
667 #[test]
668 fn test_energy_conservation() {
669 let params = AcousticParams::new(343.0, 1.0).with_damping(0.0);
670 let mut grid = SimulationGrid::new(32, 32, params);
671
672 for i in 0..grid.reflection_coeff.len() {
674 grid.reflection_coeff[i] = 1.0;
675 }
676
677 grid.inject_impulse(16, 16, 1.0);
678 let initial_energy = grid.total_energy();
679
680 for _ in 0..20 {
682 grid.step();
683 }
684
685 let final_energy = grid.total_energy();
686
687 assert!(
690 final_energy > 0.8 * initial_energy,
691 "Energy should be roughly conserved: initial={}, final={}",
692 initial_energy,
693 final_energy
694 );
695 }
696
697 #[test]
698 fn test_pressure_slice() {
699 let params = AcousticParams::new(343.0, 1.0);
700 let mut grid = SimulationGrid::new(4, 4, params);
701
702 grid.inject_impulse(2, 2, 0.5);
703
704 let slice = grid.pressure_slice();
705 assert_eq!(slice.len(), 16);
706 assert_eq!(slice[10], 0.5);
708 }
709}