#[derive(Debug, Clone, Copy, PartialEq)]
pub enum FluxLimiter {
Upwind,
Linear,
VanLeer,
VanAlbada,
Minmod,
SuperBee,
Muscl,
Umist,
Ospre,
LimitedLinear(f64),
}
impl FluxLimiter {
pub fn psi(&self, r: f64) -> f64 {
if !r.is_finite() {
return 0.0;
}
let val = match *self {
FluxLimiter::Upwind => 0.0,
FluxLimiter::Linear => 1.0,
FluxLimiter::VanLeer => (r + r.abs()) / (1.0 + r.abs()),
FluxLimiter::VanAlbada => {
let r = r.max(0.0);
r * (r + 1.0) / (r * r + 1.0)
}
FluxLimiter::Minmod => r.min(1.0).max(0.0),
FluxLimiter::SuperBee => (2.0 * r).min(1.0).max(r.min(2.0)).max(0.0),
FluxLimiter::Muscl => (2.0 * r).min(0.5 * r + 0.5).min(2.0).max(0.0),
FluxLimiter::Umist => (2.0 * r)
.min(0.75 * r + 0.25)
.min(0.25 * r + 0.75)
.min(2.0)
.max(0.0),
FluxLimiter::Ospre => {
let r = r.max(0.0);
1.5 * r * (r + 1.0) / (r * (r + 1.0) + 1.0)
}
FluxLimiter::LimitedLinear(k) => {
let two_by_k = 2.0 / k.max(1e-30);
(two_by_k * r).min(1.0).max(0.0)
}
};
val.max(0.0)
}
pub fn is_tvd(&self) -> bool {
!matches!(self, FluxLimiter::Upwind | FluxLimiter::Linear)
}
pub fn is_symmetric(&self) -> bool {
matches!(
self,
FluxLimiter::VanLeer
| FluxLimiter::VanAlbada
| FluxLimiter::Minmod
| FluxLimiter::SuperBee
| FluxLimiter::Muscl
| FluxLimiter::Ospre
)
}
}
#[cfg(test)]
mod tests {
use super::*;
const CLASSIC: &[FluxLimiter] = &[
FluxLimiter::VanLeer,
FluxLimiter::VanAlbada,
FluxLimiter::Minmod,
FluxLimiter::SuperBee,
FluxLimiter::Muscl,
FluxLimiter::Umist,
FluxLimiter::Ospre,
];
#[test]
fn consistency_at_r_one() {
for l in CLASSIC {
assert!((l.psi(1.0) - 1.0).abs() < 1e-12, "{l:?} psi(1) != 1");
}
assert_eq!(FluxLimiter::Linear.psi(1.0), 1.0);
assert_eq!(FluxLimiter::Upwind.psi(1.0), 0.0);
}
#[test]
fn extrema_are_clipped() {
for l in CLASSIC {
for &r in &[-3.0, -1.0, -0.1, 0.0] {
assert_eq!(l.psi(r), 0.0, "{l:?} psi({r}) != 0");
}
}
}
#[test]
fn within_sweby_tvd_envelope() {
for l in CLASSIC {
let mut r = 0.01;
while r <= 6.0 {
let p = l.psi(r);
assert!(p >= 0.0, "{l:?} psi({r}) < 0");
assert!(p <= 2.0 * r + 1e-12, "{l:?} psi({r})={p} > 2r");
assert!(p <= 2.0 + 1e-12, "{l:?} psi({r})={p} > 2");
r += 0.01;
}
}
}
#[test]
fn symmetric_limiters_satisfy_symmetry() {
for l in CLASSIC.iter().filter(|l| l.is_symmetric()) {
for &r in &[0.2, 0.5, 0.75, 1.3, 2.0, 4.0] {
let lhs = l.psi(r) / r;
let rhs = l.psi(1.0 / r);
assert!(
(lhs - rhs).abs() < 1e-10,
"{l:?} asymmetric at r={r}: {lhs} vs {rhs}"
);
}
}
}
#[test]
fn known_spot_values() {
assert!((FluxLimiter::SuperBee.psi(0.5) - 1.0).abs() < 1e-12);
assert!((FluxLimiter::SuperBee.psi(2.0) - 2.0).abs() < 1e-12);
assert!((FluxLimiter::Minmod.psi(0.5) - 0.5).abs() < 1e-12);
assert!((FluxLimiter::Minmod.psi(3.0) - 1.0).abs() < 1e-12);
assert!((FluxLimiter::VanLeer.psi(1.0) - 1.0).abs() < 1e-12);
}
#[test]
fn limited_linear_matches_upstream_form() {
for &k in &[0.25, 0.5, 1.0] {
let l = FluxLimiter::LimitedLinear(k);
for &r in &[-1.0, 0.0, 0.1, 0.5, 1.0, 3.0] {
let p = l.psi(r);
assert!(
(0.0..=1.0).contains(&p),
"limitedLinear({k}) psi({r})={p} out of [0,1]"
);
}
assert!((l.psi(1.0) - 1.0).abs() < 1e-12);
assert_eq!(l.psi(-1.0), 0.0);
}
}
#[test]
fn non_finite_r_is_safe() {
for l in CLASSIC {
assert_eq!(l.psi(f64::NAN), 0.0);
assert_eq!(l.psi(f64::INFINITY), 0.0);
}
}
}