use super::euler_1d::EulerState;
pub trait SlopeLimiter: Send + Sync {
fn limit(
&self,
modal_coeffs: &mut [EulerState],
left_avg: EulerState,
right_avg: EulerState,
h: f64,
);
}
pub struct MinmodTvbLimiter {
pub m_constant: f64,
}
impl MinmodTvbLimiter {
pub fn new(m_constant: f64) -> Self {
Self { m_constant }
}
}
fn minmod(a: f64, b: f64, c: f64) -> f64 {
if a > 0.0 && b > 0.0 && c > 0.0 {
a.min(b).min(c)
} else if a < 0.0 && b < 0.0 && c < 0.0 {
a.max(b).max(c)
} else {
0.0
}
}
fn minmod_tvb(a: f64, b: f64, c: f64, m: f64, h: f64) -> f64 {
if a.abs() <= m * h * h {
a
} else {
minmod(a, b, c)
}
}
fn limit_component(
slope: f64,
left_avg: f64,
cell_avg: f64,
right_avg: f64,
m: f64,
h: f64,
) -> f64 {
let fwd = right_avg - cell_avg;
let bwd = cell_avg - left_avg;
minmod_tvb(slope, fwd, bwd, m, h)
}
impl SlopeLimiter for MinmodTvbLimiter {
fn limit(
&self,
modal_coeffs: &mut [EulerState],
left_avg: EulerState,
right_avg: EulerState,
h: f64,
) {
if modal_coeffs.is_empty() {
return;
}
let cell_avg = modal_coeffs[0];
if modal_coeffs.len() == 1 {
return; }
let slope = modal_coeffs[1];
let new_rho = limit_component(
slope.rho,
left_avg.rho,
cell_avg.rho,
right_avg.rho,
self.m_constant,
h,
);
let new_rho_u = limit_component(
slope.rho_u,
left_avg.rho_u,
cell_avg.rho_u,
right_avg.rho_u,
self.m_constant,
h,
);
let new_energy = limit_component(
slope.energy,
left_avg.energy,
cell_avg.energy,
right_avg.energy,
self.m_constant,
h,
);
let limited = (new_rho - slope.rho).abs() > f64::EPSILON
|| (new_rho_u - slope.rho_u).abs() > f64::EPSILON
|| (new_energy - slope.energy).abs() > f64::EPSILON;
if limited {
modal_coeffs[1] = EulerState {
rho: new_rho,
rho_u: new_rho_u,
energy: new_energy,
};
for coeff in modal_coeffs[2..].iter_mut() {
*coeff = EulerState::zero();
}
}
}
}
pub trait PerssonPeraireIndicator: Send + Sync {
fn evaluate(&self, modal_coeffs: &[f64]) -> f64;
}
pub struct StandardPerssonPeraire {
pub kappa: f64,
}
impl StandardPerssonPeraire {
pub fn new(kappa: f64) -> Self {
Self { kappa }
}
pub fn threshold(&self, p: usize) -> f64 {
if p == 0 {
f64::NEG_INFINITY
} else {
-self.kappa * (p as f64).log10()
}
}
}
impl PerssonPeraireIndicator for StandardPerssonPeraire {
fn evaluate(&self, modal_coeffs: &[f64]) -> f64 {
let p = modal_coeffs.len().saturating_sub(1);
if p == 0 {
return f64::NEG_INFINITY; }
let norm_u_sq: f64 = modal_coeffs.iter().map(|c| c * c).sum();
if norm_u_sq < f64::EPSILON {
return f64::NEG_INFINITY;
}
let highest_mode_sq = modal_coeffs[p] * modal_coeffs[p];
(highest_mode_sq / norm_u_sq).log10()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn es(rho: f64, rho_u: f64, energy: f64) -> EulerState {
EulerState { rho, rho_u, energy }
}
#[test]
fn test_minmod_basic() {
assert!((minmod(1.0, 2.0, 3.0) - 1.0).abs() < 1e-14);
assert!((minmod(-1.0, -2.0, -3.0) + 1.0).abs() < 1e-14);
assert!(minmod(1.0, -1.0, 0.5).abs() < 1e-14); }
#[test]
fn test_minmod_tvb_passthrough_small_slope() {
let a = 0.001;
let h = 0.1;
let m = 10.0; let result = minmod_tvb(a, -1.0, 1.0, m, h); assert!((result - a).abs() < 1e-14);
}
#[test]
fn test_limiter_leaves_low_order_smooth_solution() {
let limiter = MinmodTvbLimiter::new(0.0); let mut modal = vec![es(1.0, 0.5, 3.0), es(0.1, 0.05, 0.3)];
let left = es(0.9, 0.45, 2.7);
let right = es(1.1, 0.55, 3.3);
let slope_before = modal[1];
limiter.limit(&mut modal, left, right, 1.0);
assert!((modal[1].rho - slope_before.rho).abs() < 1e-10);
}
#[test]
fn test_limiter_zeros_out_oscillatory_high_modes() {
let limiter = MinmodTvbLimiter::new(0.0);
let mut modal = vec![
es(1.0, 1.0, 3.0), es(1.0, 1.0, 1.0), es(0.5, 0.5, 0.5), ];
let same = es(1.0, 1.0, 3.0);
limiter.limit(&mut modal, same, same, 1.0);
assert!(modal[1].rho.abs() < 1e-10);
assert!(modal[2].rho.abs() < 1e-10);
}
#[test]
fn test_persson_peraire_smooth_element_negative_indicator() {
let indicator = StandardPerssonPeraire::new(4.0);
let modal = vec![1.0, 0.01, 0.001]; let s = indicator.evaluate(&modal);
assert!(
s < -3.0,
"smooth element should have very negative indicator: s={s}"
);
}
#[test]
fn test_persson_peraire_troubled_element_positive_indicator() {
let indicator = StandardPerssonPeraire::new(4.0);
let modal = vec![0.001, 0.001, 1.0]; let s = indicator.evaluate(&modal);
assert!(
s > -3.0,
"troubled element should have indicator above threshold: s={s}"
);
}
#[test]
fn test_persson_peraire_threshold() {
let indicator = StandardPerssonPeraire::new(4.0);
assert!((indicator.threshold(1) - 0.0).abs() < 1e-14);
assert!((indicator.threshold(10) - (-4.0)).abs() < 1e-14);
assert_eq!(indicator.threshold(0), f64::NEG_INFINITY);
}
#[test]
fn test_limiter_p0_element_unchanged() {
let limiter = MinmodTvbLimiter::new(0.0);
let mut modal = vec![es(1.0, 0.5, 3.0)]; let left = es(0.5, 0.25, 1.5);
let right = es(1.5, 0.75, 4.5);
let avg_before = modal[0];
limiter.limit(&mut modal, left, right, 1.0);
assert!((modal[0].rho - avg_before.rho).abs() < 1e-14);
}
#[test]
fn test_limiter_empty_unchanged() {
let limiter = MinmodTvbLimiter::new(1.0);
let mut modal: Vec<EulerState> = vec![];
limiter.limit(&mut modal, EulerState::zero(), EulerState::zero(), 1.0);
assert!(modal.is_empty());
}
}