use crate::grid::Grid2D;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Waveform {
Ricker { freq: f32 },
Sine { freq: f32 },
}
impl Waveform {
pub fn eval(&self, t: f64) -> f32 {
match *self {
Waveform::Ricker { freq } => {
let f = freq as f64;
let a = (std::f64::consts::PI * f * (t - 1.0 / f)).powi(2);
((1.0 - 2.0 * a) * (-a).exp()) as f32
}
Waveform::Sine { freq } => (2.0 * std::f64::consts::PI * freq as f64 * t).sin() as f32,
}
}
}
#[derive(Debug, Clone)]
struct Source {
x: usize,
y: usize,
waveform: Waveform,
amplitude: f32,
}
pub struct AcousticSolver2D {
pub p: Grid2D,
pub vx: Grid2D,
pub vy: Grid2D,
rho: Grid2D,
inv_rho: Grid2D,
bulk_modulus: Grid2D,
damp: Grid2D,
sigma_x: Grid2D,
sigma_y: Grid2D,
zero_row: Vec<f32>,
sources: Vec<Source>,
receivers: Vec<(usize, usize)>,
traces: Vec<Vec<f32>>,
time: f64,
}
impl AcousticSolver2D {
pub fn new(nx: usize, ny: usize, dx: f32, dy: f32, default_rho: f32, default_c: f32) -> Self {
assert!(nx >= 2 && ny >= 2, "la grille doit faire au moins 2x2");
assert!(
default_rho > 0.0 && default_c > 0.0,
"rho et c doivent être > 0"
);
let mut rho = Grid2D::new(nx, ny, dx, dy);
rho.fill(default_rho);
let mut inv_rho = Grid2D::new(nx, ny, dx, dy);
inv_rho.fill(1.0 / default_rho);
let mut bulk_modulus = Grid2D::new(nx, ny, dx, dy);
bulk_modulus.fill(default_rho * default_c * default_c);
let mut damp = Grid2D::new(nx, ny, dx, dy);
damp.fill(1.0);
let sigma_x = Grid2D::new(nx, ny, dx, dy);
let sigma_y = Grid2D::new(nx, ny, dx, dy);
Self {
p: Grid2D::new(nx, ny, dx, dy),
vx: Grid2D::new(nx, ny, dx, dy),
vy: Grid2D::new(nx, ny, dx, dy),
rho,
inv_rho,
bulk_modulus,
damp,
sigma_x, sigma_y, zero_row: vec![0.0; nx],
sources: Vec::new(),
receivers: Vec::new(),
traces: Vec::new(),
time: 0.0,
}
}
pub fn rho(&self) -> &Grid2D {
&self.rho
}
pub fn set_pml_boundary(&mut self, width: usize, max_sigma: f32) {
let (nx, ny) = (self.p.nx, self.p.ny);
for y in 0..ny {
for x in 0..nx {
let dist_x_left = if x < width { width - x } else { 0 };
let dist_x_right = if x >= nx - width {
x - (nx - 1 - width)
} else {
0
};
let dist_x = dist_x_left.max(dist_x_right) as f32;
if width > 0 && dist_x > 0.0 {
let t = dist_x / width as f32;
let val = max_sigma * t * t; self.sigma_x.set(x, y, val);
} else {
self.sigma_x.set(x, y, 0.0);
}
let dist_y_top = if y < width { width - y } else { 0 };
let dist_y_bottom = if y >= ny - width {
y - (ny - 1 - width)
} else {
0
};
let dist_y = dist_y_top.max(dist_y_bottom) as f32;
if width > 0 && dist_y > 0.0 {
let t = dist_y / width as f32;
let val = max_sigma * t * t;
self.sigma_y.set(x, y, val);
} else {
self.sigma_y.set(x, y, 0.0);
}
}
}
}
pub fn bulk_modulus(&self) -> &Grid2D {
&self.bulk_modulus
}
pub fn set_medium(&mut self, x: usize, y: usize, rho: f32, c: f32) {
assert!(rho > 0.0 && c > 0.0, "rho et c doivent être > 0");
self.rho.set(x, y, rho);
self.inv_rho.set(x, y, 1.0 / rho);
self.bulk_modulus.set(x, y, rho * c * c);
}
pub fn set_medium_rect(
&mut self,
x0: usize,
y0: usize,
x1: usize,
y1: usize,
rho: f32,
c: f32,
) {
for y in y0..y1.min(self.p.ny) {
for x in x0..x1.min(self.p.nx) {
self.set_medium(x, y, rho, c);
}
}
}
pub fn c_max(&self) -> f32 {
self.bulk_modulus
.as_slice()
.iter()
.zip(self.inv_rho.as_slice())
.map(|(k, ir)| (k * ir).sqrt())
.fold(0.0, f32::max)
}
pub fn max_stable_dt(&self) -> f32 {
let inv = (1.0 / (self.p.dx * self.p.dx) + 1.0 / (self.p.dy * self.p.dy)).sqrt();
1.0 / (self.c_max() * inv)
}
pub fn stable_dt(&self, safety: f32) -> f32 {
safety * self.max_stable_dt()
}
pub fn set_absorbing_boundary(&mut self, width: usize, max_damping: f32) {
assert!(
(0.0..1.0).contains(&max_damping),
"max_damping doit être dans [0, 1)"
);
let (nx, ny) = (self.p.nx, self.p.ny);
for y in 0..ny {
for x in 0..nx {
let d = x.min(nx - 1 - x).min(y).min(ny - 1 - y);
let f = if width > 0 && d < width {
let t = (width - d) as f32 / width as f32;
1.0 - max_damping * t * t
} else {
1.0
};
self.damp.set(x, y, f);
}
}
}
pub fn add_source(&mut self, x: usize, y: usize, waveform: Waveform, amplitude: f32) {
assert!(x < self.p.nx && y < self.p.ny, "source hors grille");
self.sources.push(Source {
x,
y,
waveform,
amplitude,
});
}
pub fn inject_source(&mut self, x: usize, y: usize, value: f32) {
let current = self.p.get(x, y);
self.p.set(x, y, current + value);
}
pub fn add_receiver(&mut self, x: usize, y: usize) -> usize {
assert!(x < self.p.nx && y < self.p.ny, "récepteur hors grille");
self.receivers.push((x, y));
self.traces.push(Vec::new());
self.receivers.len() - 1
}
pub fn receiver(&self, id: usize) -> &[f32] {
&self.traces[id]
}
pub fn time(&self) -> f64 {
self.time
}
pub fn step(&mut self, dt: f32) {
let nx = self.p.nx;
let ny = self.p.ny;
let inv_dx = 1.0 / self.p.dx;
let inv_dy = 1.0 / self.p.dy;
for s in &self.sources {
let idx = self.p.index(s.x, s.y);
self.p.as_mut_slice()[idx] += s.amplitude * s.waveform.eval(self.time);
}
{
let p = self.p.as_slice();
let inv_rho = self.inv_rho.as_slice();
let sig_x = self.sigma_x.as_slice();
let sig_y = self.sigma_y.as_slice();
let vel = |y: usize, vxr: &mut [f32], vyr: &mut [f32]| {
let o = y * nx;
let prow = &p[o..o + nx];
let irow = &inv_rho[o..o + nx];
let sx_row = &sig_x[o..o + nx];
let vxr = &mut vxr[..nx];
for i in 0..nx - 1 {
let damping_x = 1.0 / (1.0 + 0.5 * dt * sx_row[i]);
let dp_dx = (prow[i + 1] - prow[i]) * inv_dx;
vxr[i] = (vxr[i] - dt * irow[i] * dp_dx) * damping_x;
}
if y + 1 < ny {
let pn = &p[o + nx..o + 2 * nx];
let sy_next = &sig_y[o + nx..o + 2 * nx];
let vyr = &mut vyr[..nx];
for i in 0..nx {
let damping_y = 1.0 / (1.0 + 0.5 * dt * sy_next[i]);
let dp_dy = (pn[i] - prow[i]) * inv_dy;
vyr[i] = (vyr[i] - dt * irow[i] * dp_dy) * damping_y;
}
}
};
for_each_row2(self.vx.as_mut_slice(), self.vy.as_mut_slice(), nx, vel);
}
{
let vx = self.vx.as_slice();
let vy = self.vy.as_slice();
let bulk = self.bulk_modulus.as_slice();
let sig_x = self.sigma_x.as_slice();
let sig_y = self.sigma_y.as_slice();
let zeros = &self.zero_row[..];
let prs = |y: usize, prow: &mut [f32]| {
let o = y * nx;
let vxr = &vx[o..o + nx];
let vyr = &vy[o..o + nx];
let vyp: &[f32] = if y > 0 { &vy[o - nx..o] } else { zeros };
let kr = &bulk[o..o + nx];
let sx_row = &sig_x[o..o + nx];
let sy_row = &sig_y[o..o + nx];
let prow = &mut prow[..nx];
let div0 = vxr[0] * inv_dx + (vyr[0] - vyp[0]) * inv_dy;
let damping_0 = 1.0 / (1.0 + 0.5 * dt * (sx_row[0] + sy_row[0]));
prow[0] = (prow[0] - dt * kr[0] * div0) * damping_0;
for i in 1..nx {
let div = (vxr[i] - vxr[i - 1]) * inv_dx + (vyr[i] - vyp[i]) * inv_dy;
let damping = 1.0 / (1.0 + 0.5 * dt * (sx_row[i] + sy_row[i]));
prow[i] = (prow[i] - dt * kr[i] * div) * damping;
}
};
for_each_row1(self.p.as_mut_slice(), nx, prs);
}
for (&(x, y), trace) in self.receivers.iter().zip(self.traces.iter_mut()) {
trace.push(self.p.get(x, y));
}
self.time += dt as f64;
}
}
#[cfg(feature = "rayon")]
fn for_each_row1<F>(a: &mut [f32], nx: usize, f: F)
where
F: Fn(usize, &mut [f32]) + Sync + Send,
{
use rayon::prelude::*;
a.par_chunks_mut(nx).enumerate().for_each(|(y, r)| f(y, r));
}
#[cfg(not(feature = "rayon"))]
fn for_each_row1<F>(a: &mut [f32], nx: usize, f: F)
where
F: Fn(usize, &mut [f32]) + Sync + Send,
{
for (y, r) in a.chunks_mut(nx).enumerate() {
f(y, r);
}
}
#[cfg(feature = "rayon")]
fn for_each_row2<F>(a: &mut [f32], b: &mut [f32], nx: usize, f: F)
where
F: Fn(usize, &mut [f32], &mut [f32]) + Sync + Send,
{
use rayon::prelude::*;
a.par_chunks_mut(nx)
.zip(b.par_chunks_mut(nx))
.enumerate()
.for_each(|(y, (ra, rb))| f(y, ra, rb));
}
#[cfg(not(feature = "rayon"))]
fn for_each_row2<F>(a: &mut [f32], b: &mut [f32], nx: usize, f: F)
where
F: Fn(usize, &mut [f32], &mut [f32]) + Sync + Send,
{
for (y, (ra, rb)) in a.chunks_mut(nx).zip(b.chunks_mut(nx)).enumerate() {
f(y, ra, rb);
}
}