use super::interpolate;
use crate::fields::boundary::bc::{BoundaryCondition, PatchField};
use crate::fields::field::Field;
use crate::fields::surface_field::SurfaceScalarField;
use crate::fields::vol_field::VolVectorField;
pub fn ddt_corr(
u_old: &VolVectorField,
phi_old: &SurfaceScalarField,
dt: f64,
) -> SurfaceScalarField {
const SMALL: f64 = 1e-37;
let mesh = &u_old.mesh;
let u_f = interpolate(u_old);
let r_dt = 1.0 / dt;
let internal = Field::from_fn(mesh.n_internal_faces, |f| {
let u_dot_sf = u_f.internal[f].dot(mesh.face_area_vectors[f]);
let phi_corr = phi_old.internal[f] - u_dot_sf;
let coeff = 1.0 - (phi_corr.abs() / (phi_old.internal[f].abs() + SMALL)).min(1.0);
coeff * phi_corr * r_dt
});
let boundary = mesh
.patches
.iter()
.map(|p| PatchField {
bc: BoundaryCondition::ZeroGradient,
values: Field::zeros(p.size),
})
.collect();
SurfaceScalarField::new("ddtCorr", phi_old.mesh.clone(), internal, boundary)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::fields::boundary::bc::PatchField;
use crate::fields::field::Field;
use crate::fields::surface_field::SurfaceScalarField;
use crate::fields::vol_field::VolVectorField;
use crate::fv_operators::fvc::flux;
use crate::mesh::fv_mesh::{BoundaryPatch, FvMeshBuilder, PatchKind};
use crate::primitives::Vector3;
use std::sync::Arc;
fn unit_mesh() -> Arc<crate::mesh::fv_mesh::FvMesh> {
Arc::new(
FvMeshBuilder::new()
.n_cells(2)
.n_internal_faces(1)
.owner(vec![0, 1, 0])
.neighbour(vec![1])
.patches(vec![
BoundaryPatch::new("right", 1, 1, PatchKind::Wall),
BoundaryPatch::new("left", 2, 1, PatchKind::Wall),
])
.cell_volumes(vec![1.0, 1.0])
.cell_centres(vec![
Vector3::new(0.25, 0.0, 0.0),
Vector3::new(0.75, 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),
])
.face_centres(vec![
Vector3::new(0.5, 0.0, 0.0),
Vector3::new(1.0, 0.0, 0.0),
Vector3::new(0.0, 0.0, 0.0),
])
.build()
.unwrap(),
)
}
#[test]
fn consistent_phi_gives_zero_correction() {
let m = unit_mesh();
let u = VolVectorField::uniform("U", m.clone(), Vector3::new(1.0, 0.0, 0.0));
let phi = flux(&u);
let corr = ddt_corr(&u, &phi, 0.1);
assert!(corr.internal[0].abs() < 1e-12);
assert!(corr.boundary[0].values[0].abs() < 1e-12);
}
#[test]
fn nonzero_correction_for_inconsistent_phi() {
let m = unit_mesh();
let u = VolVectorField::uniform("U", m.clone(), Vector3::new(1.0, 0.0, 0.0));
let bnd: Vec<_> = m
.patches
.iter()
.map(|p| PatchField {
bc: BoundaryCondition::ZeroGradient,
values: Field::uniform(p.size, 2.0),
})
.collect();
let phi_old = SurfaceScalarField::new("phi", m.clone(), Field::uniform(1, 2.0), bnd);
let corr = ddt_corr(&u, &phi_old, 1.0);
assert!((corr.internal[0] - 0.5).abs() < 1e-12);
}
#[test]
fn coeff_limits_large_correction_to_zero() {
let m = unit_mesh();
let u = VolVectorField::uniform("U", m.clone(), Vector3::new(1.0, 0.0, 0.0));
let bnd: Vec<_> = m
.patches
.iter()
.map(|p| PatchField {
bc: BoundaryCondition::ZeroGradient,
values: Field::zeros(p.size),
})
.collect();
let phi_old = SurfaceScalarField::new("phi", m.clone(), Field::zeros(1), bnd);
let corr = ddt_corr(&u, &phi_old, 1.0);
assert!(
corr.internal[0].abs() < 1e-12,
"coeff must damp the correction to ~0"
);
}
}