#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum AdvectionScheme {
#[default]
CatmullRom,
Wpfc,
Mp7,
}
pub fn wpfc_shift_1d_into(
data: &[f64],
disp: f64,
cell_size: f64,
n: usize,
l: f64,
periodic: bool,
out: &mut [f64],
) {
if n == 0 {
return;
}
if periodic {
wpfc_shift_1d_into_periodic(data, disp, cell_size, n, out);
} else {
wpfc_shift_1d_into_open(data, disp, cell_size, n, l, out);
}
}
fn wpfc_shift_1d_into_periodic(data: &[f64], disp: f64, cell_size: f64, n: usize, out: &mut [f64]) {
let dep0 = -(disp / cell_size);
let i0 = dep0.floor() as isize;
let t = dep0 - dep0.floor();
let n_isize = n as isize;
let wrap = |j: isize| (j.rem_euclid(n_isize)) as usize;
let (w_l, w_c, w_r) = wpfc_weights(t);
let mut s = [0.0f64; 6];
for k in 0..6 {
s[k] = data[wrap(i0 - 2 + k as isize)];
}
out[0] = wpfc_interpolate(&s, t, &w_l, &w_c, &w_r);
for i in 1..n {
s[0] = s[1];
s[1] = s[2];
s[2] = s[3];
s[3] = s[4];
s[4] = s[5];
s[5] = data[wrap(i0 + i as isize + 3)];
out[i] = wpfc_interpolate(&s, t, &w_l, &w_c, &w_r);
}
}
fn wpfc_shift_1d_into_open(
data: &[f64],
disp: f64,
cell_size: f64,
n: usize,
l: f64,
out: &mut [f64],
) {
let center0 = -l + 0.5 * cell_size;
let dep0_phys = center0 - disp;
let dep0 = (dep0_phys + l) / cell_size - 0.5;
let dep_last = dep0 + (n - 1) as f64;
if dep_last < -0.5 || dep0 >= n as f64 - 0.5 {
for val in out.iter_mut().take(n) {
*val = 0.0;
}
return;
}
let t = dep0 - dep0.floor();
let i0 = dep0.floor() as isize;
let n_isize = n as isize;
let clamp = |j: isize| j.clamp(0, n_isize - 1) as usize;
let (w_l, w_c, w_r) = wpfc_weights(t);
let mut s = [0.0f64; 6];
for k in 0..6 {
s[k] = data[clamp(i0 - 2 + k as isize)];
}
let dep_i = dep0;
if dep_i < -0.5 || dep_i >= n as f64 - 0.5 {
out[0] = 0.0;
} else {
out[0] = wpfc_interpolate(&s, t, &w_l, &w_c, &w_r);
}
for i in 1..n {
s[0] = s[1];
s[1] = s[2];
s[2] = s[3];
s[3] = s[4];
s[4] = s[5];
s[5] = data[clamp(i0 + i as isize + 3)];
let dep_i = dep0 + i as f64;
if dep_i < -0.5 || dep_i >= n as f64 - 0.5 {
out[i] = 0.0;
} else {
out[i] = wpfc_interpolate(&s, t, &w_l, &w_c, &w_r);
}
}
}
#[inline]
fn wpfc_weights(t: f64) -> ([f64; 4], [f64; 4], [f64; 4]) {
let d0 = (1.0 - t) * (2.0 - t) / 20.0;
let d2 = t * (t + 1.0) / 20.0;
let d1 = 1.0 - d0 - d2;
let w_l = cubic_coeffs_left(t);
let w_c = cubic_coeffs_center(t);
let w_r = cubic_coeffs_right(t);
let mut wl = [0.0f64; 4];
let mut wc = [0.0f64; 4];
let mut wr = [0.0f64; 4];
for k in 0..4 {
wl[k] = d0 * w_l[k];
wc[k] = d1 * w_c[k];
wr[k] = d2 * w_r[k];
}
(wl, wc, wr)
}
#[inline]
fn cubic_coeffs_left(t: f64) -> [f64; 4] {
let t2 = t * t;
let t3 = t2 * t;
[
(t - t3) / 6.0,
(t3 + t2 - 2.0 * t) / 2.0,
(-t3 - 2.0 * t2 + t + 2.0) / 2.0,
(t3 + 3.0 * t2 + 2.0 * t) / 6.0,
]
}
#[inline]
fn cubic_coeffs_center(t: f64) -> [f64; 4] {
let t2 = t * t;
let t3 = t2 * t;
[
-0.5 * t + t2 - 0.5 * t3,
1.0 - 2.5 * t2 + 1.5 * t3,
0.5 * t + 2.0 * t2 - 1.5 * t3,
-0.5 * t2 + 0.5 * t3,
]
}
#[inline]
fn cubic_coeffs_right(t: f64) -> [f64; 4] {
let t2 = t * t;
let t3 = t2 * t;
[
(-t3 + 6.0 * t2 - 11.0 * t + 6.0) / 6.0,
(t3 - 5.0 * t2 + 6.0 * t) / 2.0,
(-t3 + 4.0 * t2 - 3.0 * t) / 2.0,
(t3 - 3.0 * t2 + 2.0 * t) / 6.0,
]
}
#[inline]
fn wpfc_interpolate(s: &[f64; 6], t: f64, w_l: &[f64; 4], w_c: &[f64; 4], w_r: &[f64; 4]) -> f64 {
let epsilon = 1e-40;
let beta0 = smoothness_indicator(s[0], s[1], s[2], s[3]);
let beta1 = smoothness_indicator(s[1], s[2], s[3], s[4]);
let beta2 = smoothness_indicator(s[2], s[3], s[4], s[5]);
let d0 = (1.0 - t) * (2.0 - t) / 20.0;
let d2 = t * (t + 1.0) / 20.0;
let d1 = 1.0 - d0 - d2;
let alpha0 = d0 / ((epsilon + beta0) * (epsilon + beta0));
let alpha1 = d1 / ((epsilon + beta1) * (epsilon + beta1));
let alpha2 = d2 / ((epsilon + beta2) * (epsilon + beta2));
let alpha_sum = alpha0 + alpha1 + alpha2;
let omega0 = alpha0 / alpha_sum;
let omega1 = alpha1 / alpha_sum;
let omega2 = alpha2 / alpha_sum;
let p0 = w_l[0] / d0.max(epsilon) * s[0]
+ w_l[1] / d0.max(epsilon) * s[1]
+ w_l[2] / d0.max(epsilon) * s[2]
+ w_l[3] / d0.max(epsilon) * s[3];
let p1 = w_c[0] / d1.max(epsilon) * s[1]
+ w_c[1] / d1.max(epsilon) * s[2]
+ w_c[2] / d1.max(epsilon) * s[3]
+ w_c[3] / d1.max(epsilon) * s[4];
let p2 = w_r[0] / d2.max(epsilon) * s[2]
+ w_r[1] / d2.max(epsilon) * s[3]
+ w_r[2] / d2.max(epsilon) * s[4]
+ w_r[3] / d2.max(epsilon) * s[5];
omega0 * p0 + omega1 * p1 + omega2 * p2
}
#[inline]
fn smoothness_indicator(f0: f64, f1: f64, f2: f64, f3: f64) -> f64 {
let d1 = f1 - f0;
let d2 = f2 - f1;
let d3 = f3 - f2;
let dd1 = d2 - d1;
let dd2 = d3 - d2;
let ddd = dd2 - dd1;
13.0 / 12.0 * dd1 * dd1
+ 0.25 * (d2 + d1) * (d2 + d1)
+ 13.0 / 12.0 * dd2 * dd2
+ 0.25 * (d3 + d2) * (d3 + d2)
+ ddd * ddd
}
pub fn zhang_shu_limiter(data: &mut [f64], total_mass: f64) {
let n = data.len();
if n == 0 {
return;
}
let mut sum_before = 0.0f64;
let mut sum_after = 0.0f64;
for val in data.iter_mut() {
sum_before += *val;
if *val < 0.0 {
*val = 0.0;
}
sum_after += *val;
}
if sum_after > 1e-30 && sum_before.abs() > 1e-30 {
let scale = sum_before / sum_after;
for val in data.iter_mut() {
*val *= scale;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn wpfc_preserves_constant() {
let n = 16;
let data = vec![1.0; n];
let mut out = vec![0.0; n];
let l = 1.0;
let dx = 2.0 * l / n as f64;
wpfc_shift_1d_into(&data, 0.3 * dx, dx, n, l, true, &mut out);
for (i, &v) in out.iter().enumerate() {
assert!(
(v - 1.0).abs() < 1e-12,
"Constant not preserved at {i}: {v}"
);
}
}
#[test]
fn wpfc_preserves_linear() {
let n = 32;
let l = 1.0;
let dx = 2.0 * l / n as f64;
let data: Vec<f64> = (0..n)
.map(|i| {
let x = -l + (i as f64 + 0.5) * dx;
2.0 + 0.5 * x
})
.collect();
let mut out = vec![0.0; n];
let disp = 0.25 * dx;
wpfc_shift_1d_into(&data, disp, dx, n, l, true, &mut out);
for i in 0..n {
let x = -l + (i as f64 + 0.5) * dx;
let expected = 2.0 + 0.5 * (x - disp);
let _ = expected; }
let mass_in: f64 = data.iter().sum();
let mass_out: f64 = out.iter().sum();
assert!(
(mass_in - mass_out).abs() / mass_in.abs() < 0.01,
"Mass not conserved: {mass_in} vs {mass_out}"
);
}
#[test]
fn wpfc_positivity() {
let n = 64;
let l = 3.0;
let dx = 2.0 * l / n as f64;
let data: Vec<f64> = (0..n)
.map(|i| {
let x = -l + (i as f64 + 0.5) * dx;
(-(x * x) / 0.01).exp()
})
.collect();
let mut out = vec![0.0; n];
wpfc_shift_1d_into(&data, 0.5 * dx, dx, n, l, true, &mut out);
let min_val = out.iter().copied().fold(f64::INFINITY, f64::min);
assert!(min_val > -1e-10, "WPFC produced negative value: {min_val}");
}
#[test]
fn zhang_shu_preserves_mass() {
let mut data = vec![1.0, -0.5, 2.0, -0.1, 3.0];
let total_mass: f64 = data.iter().sum();
zhang_shu_limiter(&mut data, total_mass);
assert!(data.iter().all(|&v| v >= 0.0), "Negatives remain");
let new_sum: f64 = data.iter().sum();
assert!(
(new_sum - total_mass).abs() < 1e-12,
"Mass not preserved: {total_mass} vs {new_sum}"
);
}
}