use std::ops::{Add, Mul};
use crate::fields::boundary::bc::{BoundaryCondition, PatchField};
use crate::fields::field::Field;
use crate::fields::surface_field::SurfaceField;
use crate::fields::vol_field::VolField;
use crate::mesh::fv_mesh::{FvMesh, PatchKind};
#[inline]
fn linear_weight(mesh: &FvMesh, f: usize) -> f64 {
let co = mesh.cell_centres[mesh.owner[f]];
let cn = mesh.cell_centres[mesh.neighbour[f]];
let denom = (cn - co).mag();
if denom < 1e-300 {
return 0.5;
}
(cn - mesh.face_centres[f]).mag() / denom
}
pub fn interpolate<T>(vol: &VolField<T>) -> SurfaceField<T>
where
T: Clone + Default + Add<Output = T> + Mul<f64, Output = T>,
{
let mesh = &vol.mesh;
let internal = Field::from_fn(mesh.n_internal_faces, |f| {
let w = linear_weight(mesh, f);
vol.internal[mesh.owner[f]].clone() * w
+ vol.internal[mesh.neighbour[f]].clone() * (1.0 - w)
});
let boundary = mesh
.patches
.iter()
.zip(vol.boundary.iter())
.map(|(patch, bc_patch)| {
let values = Field::from_fn(patch.size, |fi| {
let gf = patch.start + fi;
let owner = mesh.owner[gf];
if patch.kind == PatchKind::Cyclic {
if let Some(pf) = mesh.cyclic_partner_face(gf) {
let paired = mesh.owner[pf];
let d_a = (mesh.face_centres[gf] - mesh.cell_centres[owner]).mag();
let d_b = (mesh.face_centres[pf] - mesh.cell_centres[paired]).mag();
let denom = d_a + d_b;
let w = if denom < 1e-300 { 0.5 } else { d_b / denom };
return vol.internal[owner].clone() * w
+ vol.internal[paired].clone() * (1.0 - w);
}
}
match &bc_patch.bc {
BoundaryCondition::ZeroGradient
| BoundaryCondition::Symmetry
| BoundaryCondition::Slip
| BoundaryCondition::Wedge
| BoundaryCondition::InletOutlet { .. }
| BoundaryCondition::OutletInlet { .. }
| BoundaryCondition::Freestream { .. }
| BoundaryCondition::PressureInletOutletVelocity => vol.internal[owner].clone(),
BoundaryCondition::FixedValue(v) => v.clone(),
BoundaryCondition::FixedField(ff) => ff[fi].clone(),
BoundaryCondition::Calculated(ff) => ff[fi].clone(),
BoundaryCondition::TotalPressure { .. }
| BoundaryCondition::FlowRateInletVelocity { .. } => {
bc_patch.values[fi].clone()
}
BoundaryCondition::NoSlip => T::default(),
BoundaryCondition::FixedGradient(g)
| BoundaryCondition::FixedFluxPressure { gradient: g } => {
let delta = (mesh.face_centres[gf] - mesh.cell_centres[owner]).mag();
vol.internal[owner].clone() + g.clone() * delta
}
BoundaryCondition::Mixed {
value_fraction,
ref_value,
ref_grad,
} => {
let delta = (mesh.face_centres[gf] - mesh.cell_centres[owner]).mag();
let w = *value_fraction;
ref_value.clone() * w
+ (vol.internal[owner].clone() + ref_grad.clone() * delta) * (1.0 - w)
}
BoundaryCondition::MixedField {
value_fraction,
ref_value,
ref_grad,
} => {
let delta = (mesh.face_centres[gf] - mesh.cell_centres[owner]).mag();
let w = value_fraction[fi];
ref_value[fi].clone() * w
+ (vol.internal[owner].clone() + ref_grad[fi].clone() * delta)
* (1.0 - w)
}
BoundaryCondition::Empty => T::default(),
}
});
PatchField {
bc: BoundaryCondition::ZeroGradient,
values,
}
})
.collect();
SurfaceField::new(
crate::fv_operators::naming::derived_name("interpolate", &vol.name),
vol.mesh.clone(),
internal,
boundary,
)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::fields::vol_field::VolScalarField;
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 uniform_field_interpolates_exactly() {
let m = unit_mesh();
let p = VolScalarField::uniform("p", m, 5.0);
let p_f = interpolate(&p);
assert!((p_f.internal[0] - 5.0).abs() < 1e-12);
assert!((p_f.boundary[0].values[0] - 5.0).abs() < 1e-12);
}
#[test]
fn linear_field_midpoint_weight() {
let m = unit_mesh();
let mut p = VolScalarField::zeros("p", m.clone());
p.internal[1] = 1.0;
let p_f = interpolate(&p);
assert!((p_f.internal[0] - 0.5).abs() < 1e-12);
}
#[test]
fn vv_cyclic_interpolates_across_seam() {
let m = Arc::new(crate::mesh::fv_mesh::FvMesh::periodic_1d(4, 1.0, 1.0));
let mut p = VolScalarField::zeros("p", m.clone());
p.internal[0] = 0.0;
p.internal[1] = 1.0;
p.internal[2] = 2.0;
p.internal[3] = 3.0;
let p_f = interpolate(&p);
assert!(
(p_f.boundary[0].values[0] - 1.5).abs() < 1e-12,
"left={}",
p_f.boundary[0].values[0]
);
assert!(
(p_f.boundary[1].values[0] - 1.5).abs() < 1e-12,
"right={}",
p_f.boundary[1].values[0]
);
}
}