pub fn mp7_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 {
mp7_shift_1d_into_periodic(data, disp, cell_size, n, out);
} else {
mp7_shift_1d_into_open(data, disp, cell_size, n, l, out);
}
}
fn mp7_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 = lagrange7_weights(t);
let mut s = [0.0f64; 8];
for k in 0..8 {
s[k] = data[wrap(i0 - 3 + k as isize)];
}
let p7 = w[0] * s[0]
+ w[1] * s[1]
+ w[2] * s[2]
+ w[3] * s[3]
+ w[4] * s[4]
+ w[5] * s[5]
+ w[6] * s[6]
+ w[7] * s[7];
out[0] = mp_limit(&s, p7);
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] = s[6];
s[6] = s[7];
s[7] = data[wrap(i0 + i as isize + 4)];
let p7 = w[0] * s[0]
+ w[1] * s[1]
+ w[2] * s[2]
+ w[3] * s[3]
+ w[4] * s[4]
+ w[5] * s[5]
+ w[6] * s[6]
+ w[7] * s[7];
out[i] = mp_limit(&s, p7);
}
}
fn mp7_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 = lagrange7_weights(t);
let mut s = [0.0f64; 8];
for k in 0..8 {
s[k] = data[clamp(i0 - 3 + k as isize)];
}
let dep_i = dep0;
if dep_i < -0.5 || dep_i >= n as f64 - 0.5 {
out[0] = 0.0;
} else {
let p7 = w[0] * s[0]
+ w[1] * s[1]
+ w[2] * s[2]
+ w[3] * s[3]
+ w[4] * s[4]
+ w[5] * s[5]
+ w[6] * s[6]
+ w[7] * s[7];
out[0] = mp_limit(&s, p7);
}
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] = s[6];
s[6] = s[7];
s[7] = data[clamp(i0 + i as isize + 4)];
let dep_i = dep0 + i as f64;
if dep_i < -0.5 || dep_i >= n as f64 - 0.5 {
out[i] = 0.0;
} else {
let p7 = w[0] * s[0]
+ w[1] * s[1]
+ w[2] * s[2]
+ w[3] * s[3]
+ w[4] * s[4]
+ w[5] * s[5]
+ w[6] * s[6]
+ w[7] * s[7];
out[i] = mp_limit(&s, p7);
}
}
}
#[inline]
fn lagrange7_weights(t: f64) -> [f64; 8] {
const NODES: [f64; 8] = [-3.0, -2.0, -1.0, 0.0, 1.0, 2.0, 3.0, 4.0];
let tm = [
t - NODES[0], t - NODES[1], t - NODES[2], t - NODES[3], t - NODES[4], t - NODES[5], t - NODES[6], t - NODES[7], ];
const INV_DENOM: [f64; 8] = [
1.0 / -5040.0,
1.0 / 720.0,
1.0 / -240.0,
1.0 / 144.0,
1.0 / -144.0,
1.0 / 240.0,
1.0 / -720.0,
1.0 / 5040.0,
];
[
INV_DENOM[0] * tm[1] * tm[2] * tm[3] * tm[4] * tm[5] * tm[6] * tm[7],
INV_DENOM[1] * tm[0] * tm[2] * tm[3] * tm[4] * tm[5] * tm[6] * tm[7],
INV_DENOM[2] * tm[0] * tm[1] * tm[3] * tm[4] * tm[5] * tm[6] * tm[7],
INV_DENOM[3] * tm[0] * tm[1] * tm[2] * tm[4] * tm[5] * tm[6] * tm[7],
INV_DENOM[4] * tm[0] * tm[1] * tm[2] * tm[3] * tm[5] * tm[6] * tm[7],
INV_DENOM[5] * tm[0] * tm[1] * tm[2] * tm[3] * tm[4] * tm[6] * tm[7],
INV_DENOM[6] * tm[0] * tm[1] * tm[2] * tm[3] * tm[4] * tm[5] * tm[7],
INV_DENOM[7] * tm[0] * tm[1] * tm[2] * tm[3] * tm[4] * tm[5] * tm[6],
]
}
#[inline]
fn mp_limit(stencil: &[f64; 8], poly_val: f64) -> f64 {
let f_min = stencil[2].min(stencil[3]).min(stencil[4]).min(stencil[5]);
let f_max = stencil[2].max(stencil[3]).max(stencil[4]).max(stencil[5]);
poly_val.clamp(f_min, f_max)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mp7_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;
mp7_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 mp7_preserves_linear() {
let n = 32;
let l = 1.0;
let dx = 2.0 * l / n as f64;
let disp = 0.25 * dx;
let data: Vec<f64> = (0..n).map(|_| 3.5).collect();
let mut out = vec![0.0; n];
mp7_shift_1d_into(&data, disp, dx, n, l, true, &mut out);
let mass_in: f64 = data.iter().sum();
let mass_out: f64 = out.iter().sum();
assert!(
(mass_in - mass_out).abs() < 1e-12,
"Mass not conserved for constant: {mass_in} vs {mass_out}"
);
let data2: Vec<f64> = (0..n)
.map(|i| {
let x = -l + (i as f64 + 0.5) * dx;
2.0 + 0.3 * (std::f64::consts::PI * x / l).sin()
})
.collect();
let mut out2 = vec![0.0; n];
mp7_shift_1d_into(&data2, disp, dx, n, l, true, &mut out2);
let mass_in2: f64 = data2.iter().sum();
let mass_out2: f64 = out2.iter().sum();
assert!(
(mass_in2 - mass_out2).abs() / mass_in2.abs() < 1e-8,
"Mass not conserved for sinusoidal: {mass_in2} vs {mass_out2}"
);
}
#[test]
fn mp7_convergence_rate() {
let l = 2.0;
let disp_frac = 0.37;
let margin = 8;
let mut prev_err = f64::MAX;
let mut prev_n = 0usize;
let mut rates = Vec::new();
for &n in &[32, 64, 128, 256] {
let dx = 2.0 * l / n as f64;
let disp = disp_frac * dx;
let data: Vec<f64> = (0..n)
.map(|i| {
let x = -l + (i as f64 + 0.5) * dx;
(3.0 * x).atan()
})
.collect();
let mut out = vec![0.0; n];
mp7_shift_1d_into(&data, disp, dx, n, l, false, &mut out);
let exact: Vec<f64> = (0..n)
.map(|i| {
let x = -l + (i as f64 + 0.5) * dx;
let xs = x - disp;
(3.0 * xs).atan()
})
.collect();
let err: f64 = out[margin..n - margin]
.iter()
.zip(exact[margin..n - margin].iter())
.map(|(a, b)| (a - b).abs())
.fold(0.0f64, f64::max);
if prev_n > 0 && err > 0.0 {
let rate = (prev_err / err).ln() / (n as f64 / prev_n as f64).ln();
rates.push(rate);
}
prev_err = err;
prev_n = n;
}
let avg_rate: f64 = rates.iter().sum::<f64>() / rates.len() as f64;
assert!(
avg_rate > 5.5,
"Convergence rate too low: {avg_rate:.2} (expected ~7), rates: {rates:?}"
);
}
#[test]
fn mp7_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];
mp7_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 >= 0.0,
"MP7 limiter failed — produced negative value: {min_val}"
);
}
}