use super::interpolate;
use crate::fields::boundary::bc::PatchField;
use crate::fields::field::Field;
use crate::fields::vol_field::{VolTensorField, VolVectorField};
use crate::primitives::Tensor;
pub fn grad_vec(vol: &VolVectorField) -> VolTensorField {
let mesh = &vol.mesh;
let u_f = interpolate(vol);
let mut g = vec![Tensor::ZERO; mesh.n_cells];
for f in 0..mesh.n_internal_faces {
let o = mesh.owner[f];
let n = mesh.neighbour[f];
let contrib = mesh.face_area_vectors[f] * u_f.internal[f];
g[o] += contrib;
g[n] -= contrib;
}
for (pi, patch) in mesh.patches.iter().enumerate() {
for fi in 0..patch.size {
let gf = patch.start + fi;
let owner = mesh.owner[gf];
let contrib = mesh.face_area_vectors[gf] * u_f.boundary[pi].values[fi];
g[owner] += contrib;
}
}
let boundary = mesh
.patches
.iter()
.map(|p| PatchField::zero_gradient_tensor(p.size))
.collect();
VolTensorField::new(
crate::fv_operators::naming::derived_name("grad", &vol.name),
vol.mesh.clone(),
Field::from_fn(mesh.n_cells, |c| g[c] / mesh.cell_volumes[c]),
boundary,
)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::fields::boundary::bc::{BoundaryCondition, PatchField};
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![0.5, 0.5])
.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_zero_grad() {
let m = unit_mesh();
let u = VolVectorField::uniform("U", m, Vector3::new(3.0, -1.0, 2.0));
let g = grad_vec(&u);
for c in 0..2 {
let t = g.internal[c];
let max =
t.xx.abs()
.max(t.xy.abs())
.max(t.xz.abs())
.max(t.yx.abs())
.max(t.yy.abs())
.max(t.yz.abs())
.max(t.zx.abs())
.max(t.zy.abs())
.max(t.zz.abs());
assert!(max < 1e-12, "cell {c}: max |grad| = {max:e}");
}
}
#[test]
fn linear_field_gives_transpose() {
let m_mesh = unit_mesh();
let mx = Vector3::new(2.0, -3.0, 5.0);
let boundary = vec![
PatchField {
bc: BoundaryCondition::FixedValue(mx),
values: Field::new(vec![mx]),
},
PatchField {
bc: BoundaryCondition::FixedValue(Vector3::ZERO),
values: Field::new(vec![Vector3::ZERO]),
},
];
let mut u = VolVectorField::new(
"U",
m_mesh.clone(),
Field::new(vec![Vector3::ZERO; 2]),
boundary,
);
u.internal[0] = mx * 0.25;
u.internal[1] = mx * 0.75;
let g = grad_vec(&u);
for c in 0..2 {
let t = g.internal[c];
assert!((t.xx - 2.0).abs() < 1e-10, "cell {c} xx = {}", t.xx);
assert!((t.xy - (-3.0)).abs() < 1e-10, "cell {c} xy = {}", t.xy);
assert!((t.xz - 5.0).abs() < 1e-10, "cell {c} xz = {}", t.xz);
assert!(t.yx.abs() < 1e-10 && t.yy.abs() < 1e-10 && t.yz.abs() < 1e-10);
assert!(t.zx.abs() < 1e-10 && t.zy.abs() < 1e-10 && t.zz.abs() < 1e-10);
}
}
}