use super::{grad, interpolate};
use crate::fields::boundary::bc::{BoundaryCondition, PatchField};
use crate::fields::field::Field;
use crate::fields::surface_field::SurfaceScalarField;
use crate::fields::vol_field::VolScalarField;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Limiter {
Upwind,
Linear,
VanLeer,
Minmod,
}
impl Limiter {
#[inline]
fn lambda(self, r: f64) -> f64 {
match self {
Limiter::Upwind => 0.0,
Limiter::Linear => 1.0,
Limiter::VanLeer => (r + r.abs()) / (1.0 + r.abs()),
Limiter::Minmod => 0.0_f64.max(r.min(1.0)),
}
}
}
pub fn reconstruct_pos_neg(
field: &VolScalarField,
limiter: Limiter,
) -> (SurfaceScalarField, SurfaceScalarField) {
let mesh = field.mesh.clone();
let grad_c = grad(field); let lin = interpolate(field); let phi = field.internal.as_slice();
let gc = grad_c.internal.as_slice();
const TINY: f64 = 1e-30;
let mut pos = vec![0.0_f64; mesh.n_internal_faces];
let mut neg = vec![0.0_f64; mesh.n_internal_faces];
for f in 0..mesh.n_internal_faces {
let o = mesh.owner[f];
let nb = mesh.neighbour[f];
let gradf = phi[nb] - phi[o];
if gradf.abs() < TINY {
pos[f] = phi[o];
neg[f] = phi[nb];
continue;
}
let d = mesh.cell_centres[nb] - mesh.cell_centres[o];
let phi_lin = lin.internal[f];
let r_pos = 2.0 * d.dot(gc[o]) / gradf - 1.0;
let r_neg = 2.0 * d.dot(gc[nb]) / gradf - 1.0;
pos[f] = phi[o] + limiter.lambda(r_pos) * (phi_lin - phi[o]);
neg[f] = phi[nb] + limiter.lambda(r_neg) * (phi_lin - phi[nb]);
}
let boundary = || -> Vec<PatchField<f64>> {
lin.boundary
.iter()
.map(|lb| PatchField {
bc: BoundaryCondition::ZeroGradient,
values: lb.values.clone(),
})
.collect()
};
let pos_field = SurfaceScalarField::new(
format!("{}_pos", field.name),
mesh.clone(),
Field::new(pos),
boundary(),
);
let neg_field = SurfaceScalarField::new(
format!("{}_neg", field.name),
mesh,
Field::new(neg),
boundary(),
);
(pos_field, neg_field)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mesh::fv_mesh::{BoundaryPatch, FvMeshBuilder, PatchKind};
use crate::primitives::Vector3;
use std::sync::Arc;
fn line_mesh() -> Arc<crate::mesh::fv_mesh::FvMesh> {
Arc::new(
FvMeshBuilder::new()
.n_cells(3)
.n_internal_faces(2)
.owner(vec![0, 1, 0, 2])
.neighbour(vec![1, 2])
.patches(vec![
BoundaryPatch::new("left", 2, 1, PatchKind::Patch),
BoundaryPatch::new("right", 3, 1, PatchKind::Patch),
])
.cell_volumes(vec![1.0, 1.0, 1.0])
.cell_centres(vec![
Vector3::new(0.5, 0.0, 0.0),
Vector3::new(1.5, 0.0, 0.0),
Vector3::new(2.5, 0.0, 0.0),
])
.face_area_vectors(vec![
Vector3::new(1.0, 0.0, 0.0),
Vector3::new(1.0, 0.0, 0.0),
Vector3::new(-1.0, 0.0, 0.0),
Vector3::new(1.0, 0.0, 0.0),
])
.face_centres(vec![
Vector3::new(1.0, 0.0, 0.0),
Vector3::new(2.0, 0.0, 0.0),
Vector3::new(0.0, 0.0, 0.0),
Vector3::new(3.0, 0.0, 0.0),
])
.build()
.unwrap(),
)
}
#[test]
fn linear_field_reconstructs_to_central_value() {
let m = line_mesh();
let mut f = VolScalarField::zeros("phi", m.clone());
f.internal = Field::new(vec![0.5, 1.5, 2.5]); f.boundary[0].bc = BoundaryCondition::FixedValue(0.0); f.boundary[0].values = Field::new(vec![0.0]);
f.boundary[1].bc = BoundaryCondition::FixedValue(3.0); f.boundary[1].values = Field::new(vec![3.0]);
let (pos, neg) = reconstruct_pos_neg(&f, Limiter::VanLeer);
assert!(
(pos.internal[0] - 1.0).abs() < 1e-9,
"pos = {}",
pos.internal[0]
);
assert!(
(neg.internal[0] - 1.0).abs() < 1e-9,
"neg = {}",
neg.internal[0]
);
}
#[test]
fn upwind_limiter_returns_cell_values() {
let m = line_mesh();
let mut f = VolScalarField::zeros("phi", m.clone());
f.internal = Field::new(vec![1.0, 5.0, 9.0]);
let (pos, neg) = reconstruct_pos_neg(&f, Limiter::Upwind);
assert!((pos.internal[0] - 1.0).abs() < 1e-12); assert!((neg.internal[0] - 5.0).abs() < 1e-12); }
#[test]
fn vanleer_clamps_at_discontinuity() {
let m = line_mesh();
let mut f = VolScalarField::zeros("phi", m.clone());
f.internal = Field::new(vec![0.0, 0.0, 1.0]);
let (pos, neg) = reconstruct_pos_neg(&f, Limiter::VanLeer);
for f in 0..2 {
assert!(pos.internal[f] >= -1e-12 && pos.internal[f] <= 1.0 + 1e-12);
assert!(neg.internal[f] >= -1e-12 && neg.internal[f] <= 1.0 + 1e-12);
}
}
}