use crate::fields::boundary::bc::BoundaryCondition;
use crate::fields::surface_field::SurfaceScalarField;
use crate::fields::vol_field::VolScalarField;
use crate::ldu_matrix::fv_matrix::FvMatrix;
use crate::mesh::fv_mesh::PatchKind;
use crate::primitives::Vector3;
const NON_ORTH_FLOOR: f64 = 0.05;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum DeltaCoeff {
#[default]
Orthogonal,
NonOrthogonal,
}
impl DeltaCoeff {
#[must_use]
pub fn interior_delta(self, mesh: &crate::mesh::fv_mesh::FvMesh, face: usize) -> f64 {
interior_delta(
self,
mesh.face_area_vectors[face],
mesh.cell_centres[mesh.neighbour[face]] - mesh.cell_centres[mesh.owner[face]],
mesh.face_areas[face],
)
}
#[must_use]
pub fn boundary_delta(self, mesh: &crate::mesh::fv_mesh::FvMesh, face: usize) -> f64 {
boundary_delta(
self,
mesh.face_area_vectors[face],
mesh.face_centres[face] - mesh.cell_centres[mesh.owner[face]],
mesh.face_areas[face],
)
}
}
fn interior_delta(mode: DeltaCoeff, sf: Vector3, d: Vector3, area: f64) -> f64 {
match mode {
DeltaCoeff::Orthogonal => d.mag(),
DeltaCoeff::NonOrthogonal => {
if area < 1.0e-300 {
return d.mag();
}
let n_hat = sf * (1.0 / area);
n_hat.dot(d).max(NON_ORTH_FLOOR * d.mag())
}
}
}
fn boundary_delta(mode: DeltaCoeff, sf: Vector3, d: Vector3, area: f64) -> f64 {
match mode {
DeltaCoeff::Orthogonal => d.mag(),
DeltaCoeff::NonOrthogonal => {
if area < 1.0e-300 {
return d.mag();
}
let n_hat = sf * (1.0 / area);
let p = n_hat.dot(d);
p.max(NON_ORTH_FLOOR * p.abs())
}
}
}
pub fn laplacian(gamma: &SurfaceScalarField, phi: &VolScalarField) -> FvMatrix {
laplacian_with_delta(gamma, phi, DeltaCoeff::Orthogonal)
}
pub fn laplacian_with_delta(
gamma: &SurfaceScalarField,
phi: &VolScalarField,
mode: DeltaCoeff,
) -> FvMatrix {
let mesh = phi.mesh.clone();
let mut mat = FvMatrix::new(mesh.clone());
for f in 0..mesh.n_internal_faces {
let o = mesh.owner[f];
let n = mesh.neighbour[f];
let delta = interior_delta(
mode,
mesh.face_area_vectors[f],
mesh.cell_centres[n] - mesh.cell_centres[o],
mesh.face_areas[f],
);
if delta < 1e-300 {
continue;
}
let coeff = gamma.internal[f] * mesh.face_areas[f] / delta;
mat.ldu.diag[o] += coeff;
mat.ldu.diag[n] += coeff;
mat.ldu.upper[f] = -coeff;
mat.ldu.lower[f] = -coeff;
}
for (pi, patch) in mesh.patches.iter().enumerate() {
if patch.kind == PatchKind::Cyclic || patch.kind == PatchKind::CyclicAmi {
continue;
}
for fi in 0..patch.size {
let gf = patch.start + fi;
let owner = mesh.owner[gf];
let d = boundary_delta(
mode,
mesh.face_area_vectors[gf],
mesh.face_centres[gf] - mesh.cell_centres[owner],
mesh.face_areas[gf],
);
if d < 1e-300 {
continue;
}
let coeff = gamma.boundary[pi].values[fi] * mesh.face_areas[gf] / d;
match &phi.boundary[pi].bc {
BoundaryCondition::ZeroGradient | BoundaryCondition::Symmetry => {}
BoundaryCondition::FixedValue(v) => {
mat.ldu.diag[owner] += coeff;
mat.source[owner] += coeff * v;
}
BoundaryCondition::FixedField(ff) => {
mat.ldu.diag[owner] += coeff;
mat.source[owner] += coeff * ff[fi];
}
BoundaryCondition::NoSlip => {
mat.ldu.diag[owner] += coeff;
}
BoundaryCondition::FixedGradient(g) => {
mat.source[owner] += coeff * d * g;
}
BoundaryCondition::Mixed {
value_fraction,
ref_value,
ref_grad,
} => {
let w = *value_fraction;
mat.ldu.diag[owner] += w * coeff;
mat.source[owner] += w * coeff * ref_value + (1.0 - w) * coeff * d * ref_grad;
}
BoundaryCondition::MixedField {
value_fraction,
ref_value,
ref_grad,
} => {
let w = value_fraction[fi];
mat.ldu.diag[owner] += w * coeff;
mat.source[owner] +=
w * coeff * ref_value[fi] + (1.0 - w) * coeff * d * ref_grad[fi];
}
BoundaryCondition::FixedFluxPressure { gradient } => {
mat.source[owner] += coeff * d * gradient;
}
BoundaryCondition::TotalPressure { .. } => {
mat.ldu.diag[owner] += coeff;
mat.source[owner] += coeff * phi.boundary[pi].values[fi];
}
_ => {}
}
}
}
for (i, cc) in mesh.cyclic_couplings.iter().enumerate() {
let cf = mesh.cyclic_coupling_face(i);
let o = cc.owner;
let nb = cc.neighbour;
let d_a = (mesh.face_centres[cc.face_a] - mesh.cell_centres[o]).mag();
let d_b = (mesh.face_centres[cc.face_b] - mesh.cell_centres[nb]).mag();
let delta = d_a + d_b;
if delta < 1e-300 {
continue;
}
let gamma_seam = gamma.boundary[cc.patch_a].values[cc.local];
let coeff = gamma_seam * mesh.face_areas[cc.face_a] / delta;
mat.ldu.diag[o] += coeff;
mat.ldu.diag[nb] += coeff;
mat.ldu.upper[cf] = -coeff;
mat.ldu.lower[cf] = -coeff;
}
let mut cf = mesh.ami_ldu_start();
for coupling in &mesh.ami_couplings {
let o = coupling.target_cell;
let d_t = (mesh.face_centres[coupling.target_face] - mesh.cell_centres[o]).mag();
let gamma_seam = gamma.boundary[coupling.target_patch].values[coupling.local];
for w in &coupling.weights {
let nb = w.source_cell;
let d_s = (mesh.face_centres[w.source_face] - mesh.cell_centres[nb]).mag();
let delta = d_t + d_s;
if delta < 1e-300 {
cf += 1;
continue;
}
let coeff = gamma_seam * w.overlap_area / delta;
mat.ldu.diag[o] += coeff;
mat.ldu.diag[nb] += coeff;
mat.ldu.upper[cf] = -coeff;
mat.ldu.lower[cf] = -coeff;
cf += 1;
}
}
mat
}
#[cfg(test)]
mod tests {
use petir::mathf::RealMath;
use super::*;
use crate::fields::boundary::bc::{BoundaryCondition, PatchField};
use crate::fields::field::Field;
use crate::fields::surface_field::SurfaceScalarField;
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![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(),
)
}
fn uniform_gamma(m: Arc<crate::mesh::fv_mesh::FvMesh>, val: f64) -> SurfaceScalarField {
let _n_faces = m.owner.len();
let internal = Field::uniform(m.n_internal_faces, val);
let boundary = m
.patches
.iter()
.map(|p| PatchField {
bc: BoundaryCondition::ZeroGradient,
values: Field::uniform(p.size, val),
})
.collect();
SurfaceScalarField::new("gamma", m, internal, boundary)
}
#[test]
fn laplacian_symmetric_matrix() {
let m = unit_mesh();
let gamma = uniform_gamma(m.clone(), 1.0);
let phi = VolScalarField::uniform("T", m.clone(), 0.0);
let mat = laplacian(&gamma, &phi);
assert!((mat.ldu.upper[0] - (-2.0)).abs() < 1e-10);
assert!((mat.ldu.lower[0] - (-2.0)).abs() < 1e-10);
}
#[test]
fn laplacian_solves_linear_dirichlet() {
let m = unit_mesh();
let gamma = uniform_gamma(m.clone(), 1.0);
let t_bc = vec![
PatchField {
bc: BoundaryCondition::FixedValue(1.0),
values: Field::new(vec![0.0]),
},
PatchField {
bc: BoundaryCondition::FixedValue(0.0),
values: Field::new(vec![0.0]),
},
];
let phi = VolScalarField::new("T", m.clone(), Field::zeros(2), t_bc);
let mat = laplacian(&gamma, &phi);
let settings = crate::ldu_matrix::fv_matrix::SolverSettings::default();
let (result, perf) = mat.solve("T", settings);
assert!(perf.converged, "Gauss-Seidel did not converge");
assert!(
(result.internal[0] - 0.25).abs() < 1e-6,
"T[0] = {}",
result.internal[0]
);
assert!(
(result.internal[1] - 0.75).abs() < 1e-6,
"T[1] = {}",
result.internal[1]
);
}
#[test]
fn vv_mixed_reduces_to_fixed_value() {
let m = unit_mesh();
let gamma = uniform_gamma(m.clone(), 1.0);
let bc_fv = vec![
PatchField {
bc: BoundaryCondition::FixedValue(2.0),
values: Field::new(vec![0.0]),
},
PatchField {
bc: BoundaryCondition::ZeroGradient,
values: Field::new(vec![0.0]),
},
];
let bc_mix = vec![
PatchField {
bc: BoundaryCondition::Mixed {
value_fraction: 1.0,
ref_value: 2.0,
ref_grad: 99.0, },
values: Field::new(vec![0.0]),
},
PatchField {
bc: BoundaryCondition::ZeroGradient,
values: Field::new(vec![0.0]),
},
];
let a = laplacian(
&gamma,
&VolScalarField::new("T", m.clone(), Field::zeros(2), bc_fv),
);
let b = laplacian(
&gamma,
&VolScalarField::new("T", m.clone(), Field::zeros(2), bc_mix),
);
for i in 0..2 {
assert!((a.ldu.diag[i] - b.ldu.diag[i]).abs() < 1e-12);
assert!((a.source[i] - b.source[i]).abs() < 1e-12);
}
}
#[test]
fn vv_mixed_reduces_to_fixed_gradient() {
let m = unit_mesh();
let gamma = uniform_gamma(m.clone(), 1.0);
let bc_fg = vec![
PatchField {
bc: BoundaryCondition::FixedGradient(0.5),
values: Field::new(vec![0.0]),
},
PatchField {
bc: BoundaryCondition::ZeroGradient,
values: Field::new(vec![0.0]),
},
];
let bc_mix = vec![
PatchField {
bc: BoundaryCondition::Mixed {
value_fraction: 0.0,
ref_value: 99.0, ref_grad: 0.5,
},
values: Field::new(vec![0.0]),
},
PatchField {
bc: BoundaryCondition::ZeroGradient,
values: Field::new(vec![0.0]),
},
];
let a = laplacian(
&gamma,
&VolScalarField::new("T", m.clone(), Field::zeros(2), bc_fg),
);
let b = laplacian(
&gamma,
&VolScalarField::new("T", m.clone(), Field::zeros(2), bc_mix),
);
for i in 0..2 {
assert!((a.ldu.diag[i] - b.ldu.diag[i]).abs() < 1e-12);
assert!((a.source[i] - b.source[i]).abs() < 1e-12);
}
}
#[test]
fn vv_fixed_gradient_linear_profile() {
let m = unit_mesh();
let gamma = uniform_gamma(m.clone(), 1.0);
let g = 2.0;
let t_bc = vec![
PatchField {
bc: BoundaryCondition::FixedGradient(g), values: Field::new(vec![0.0]),
},
PatchField {
bc: BoundaryCondition::FixedValue(0.0), values: Field::new(vec![0.0]),
},
];
let phi = VolScalarField::new("T", m.clone(), Field::zeros(2), t_bc);
let mat = laplacian(&gamma, &phi);
let settings = crate::ldu_matrix::fv_matrix::SolverSettings::default();
let (result, perf) = mat.solve("T", settings);
assert!(perf.converged);
assert!(
(result.internal[0] - 0.5).abs() < 1e-6,
"T[0]={}",
result.internal[0]
);
assert!(
(result.internal[1] - 1.5).abs() < 1e-6,
"T[1]={}",
result.internal[1]
);
}
#[test]
fn vv_robin_convective_analytic() {
let m = unit_mesh();
let gamma = uniform_gamma(m.clone(), 1.0);
let w = 0.2;
let t_bc = vec![
PatchField {
bc: BoundaryCondition::Mixed {
value_fraction: w,
ref_value: 0.0, ref_grad: 0.0,
},
values: Field::new(vec![0.0]),
},
PatchField {
bc: BoundaryCondition::FixedValue(100.0), values: Field::new(vec![0.0]),
},
];
let phi = VolScalarField::new("T", m.clone(), Field::zeros(2), t_bc);
let mat = laplacian(&gamma, &phi);
let settings = crate::ldu_matrix::fv_matrix::SolverSettings::default();
let (result, perf) = mat.solve("T", settings);
assert!(perf.converged);
assert!(
(result.internal[0] - 87.5).abs() < 1e-4,
"T[0]={}",
result.internal[0]
);
assert!(
(result.internal[1] - 62.5).abs() < 1e-4,
"T[1]={}",
result.internal[1]
);
}
fn ring_mesh(n: usize, h: f64, area: f64) -> Arc<crate::mesh::fv_mesh::FvMesh> {
use std::f64::consts::PI;
let r = h / (2.0 * (PI / n as f64).r_sin());
let owner: Vec<usize> = (0..n).collect();
let neighbour: Vec<usize> = (0..n).map(|f| (f + 1) % n).collect();
let cell_centres: Vec<Vector3> = (0..n)
.map(|i| {
let th = 2.0 * PI * i as f64 / n as f64;
Vector3::new(r * th.r_cos(), r * th.r_sin(), 0.0)
})
.collect();
let face_centres: Vec<Vector3> = (0..n)
.map(|f| (cell_centres[f] + cell_centres[(f + 1) % n]) * 0.5)
.collect();
let face_area_vectors: Vec<Vector3> =
(0..n).map(|_| Vector3::new(area, 0.0, 0.0)).collect();
Arc::new(
FvMeshBuilder::new()
.n_cells(n)
.n_internal_faces(n)
.owner(owner)
.neighbour(neighbour)
.patches(vec![]) .cell_volumes(vec![h * area; n])
.cell_centres(cell_centres)
.face_area_vectors(face_area_vectors)
.face_centres(face_centres)
.build()
.expect("ring mesh valid"),
)
}
fn uniform_gamma_any(m: Arc<crate::mesh::fv_mesh::FvMesh>, val: f64) -> SurfaceScalarField {
let internal = Field::uniform(m.n_internal_faces, val);
let boundary = m
.patches
.iter()
.map(|p| PatchField {
bc: BoundaryCondition::ZeroGradient,
values: Field::uniform(p.size, val),
})
.collect();
SurfaceScalarField::new("gamma", m, internal, boundary)
}
#[test]
fn vv_cyclic_diffusion_matches_ring_mesh() {
let n = 4;
let h = 0.25;
let area = 1.0;
let cyc = Arc::new(crate::mesh::fv_mesh::FvMesh::periodic_1d(
n,
h * n as f64,
area,
));
let ring = ring_mesh(n, h, area);
let g_cyc = uniform_gamma_any(cyc.clone(), 1.0);
let g_ring = uniform_gamma_any(ring.clone(), 1.0);
let t_cyc = VolScalarField::uniform("T", cyc.clone(), 0.0);
let t_ring = VolScalarField::uniform("T", ring.clone(), 0.0);
let m_cyc = laplacian(&g_cyc, &t_cyc);
let m_ring = laplacian(&g_ring, &t_ring);
let probes = [
vec![1.0, 0.0, 0.0, 0.0],
vec![0.0, 1.0, 0.0, 0.0],
vec![1.0, 2.0, 3.0, 4.0],
];
for x in &probes {
let yc = m_cyc.ldu.multiply(x);
let yr = m_ring.ldu.multiply(x);
for c in 0..n {
assert!(
(yc[c] - yr[c]).abs() < 1e-12,
"A·x mismatch at cell {c}: cyc={} ring={}",
yc[c],
yr[c]
);
}
}
for c in 0..n {
assert!(
(m_cyc.ldu.diag[c] - 8.0).abs() < 1e-12,
"diag[{c}]={}",
m_cyc.ldu.diag[c]
);
}
let s = [3.0, -1.0, -1.0, -1.0];
let mut a_cyc = m_cyc;
let mut a_ring = m_ring;
for c in 0..n {
a_cyc.source[c] += s[c];
a_ring.source[c] += s[c];
}
a_cyc.set_reference(0, 0.0);
a_ring.set_reference(0, 0.0);
let settings = crate::ldu_matrix::fv_matrix::SolverSettings::default();
let (tc, pc) = a_cyc.solve("T", settings);
let (tr, pr) = a_ring.solve("T", settings);
assert!(pc.converged && pr.converged);
let mut max_diff = 0.0_f64;
for c in 0..n {
max_diff = max_diff.max((tc.internal[c] - tr.internal[c]).abs());
}
assert!(
max_diff < 1e-8,
"cyclic vs ring field disagreement: {max_diff}"
);
}
#[test]
fn vv_ami_matching_diffusion_equals_cyclic() {
let n = 3usize;
let (lx, ly, depth) = (1.0, 1.0, 1.0);
let dy = ly / n as f64;
let area = dy * depth;
let ring = Arc::new(crate::mesh::fv_mesh::FvMesh::periodic_ring_ami(
n, n, lx, ly, depth,
));
let g_ring = uniform_gamma_any(ring.clone(), 1.0);
let t_ring = VolScalarField::uniform("T", ring.clone(), 0.0);
let a_ring = laplacian(&g_ring, &t_ring);
let ref_mesh = Arc::new(crate::mesh::fv_mesh::FvMesh::periodic_1d(2, lx, area));
let g_ref = uniform_gamma_any(ref_mesh.clone(), 1.0);
let t_ref = VolScalarField::uniform("T", ref_mesh.clone(), 0.0);
let a_ref = laplacian(&g_ref, &t_ref);
let n_cells = ring.n_cells; for i in 0..n {
let a_cell = i; let b_cell = n + i; for (probe_ring_cell, ref_local) in [(a_cell, 0usize), (b_cell, 1usize)] {
let mut x = vec![0.0; n_cells];
x[probe_ring_cell] = 1.0;
let y = a_ring.ldu.multiply(&x);
let mut xr = vec![0.0; 2];
xr[ref_local] = 1.0;
let yr = a_ref.ldu.multiply(&xr);
assert!(
(y[a_cell] - yr[0]).abs() < 1e-12,
"lane {i} A-row mismatch: {} vs {}",
y[a_cell],
yr[0]
);
assert!(
(y[b_cell] - yr[1]).abs() < 1e-12,
"lane {i} B-row mismatch: {} vs {}",
y[b_cell],
yr[1]
);
for (c, &yc) in y.iter().enumerate() {
if c != a_cell && c != b_cell {
assert!(yc.abs() < 1e-12, "off-lane leakage at cell {c}: {yc}");
}
}
}
}
}
#[test]
fn vv_ami_nonconformal_diffusion_conserves() {
let ring = Arc::new(crate::mesh::fv_mesh::FvMesh::periodic_ring_ami(
2, 4, 1.0, 1.0, 1.0,
));
let gamma = uniform_gamma_any(ring.clone(), 1.0);
let t = VolScalarField::uniform("T", ring.clone(), 0.0);
let a = laplacian(&gamma, &t);
let ones = vec![1.0; ring.n_cells];
for (c, &y) in a.ldu.multiply(&ones).iter().enumerate() {
assert!(y.abs() < 1e-12, "A·1 not conserved at cell {c}: {y}");
}
for cc in &ring.ami_couplings {
assert!(
(cc.weight_sum() - 1.0).abs() < 1e-14,
"target {} weight_sum {}",
cc.target_face,
cc.weight_sum()
);
}
}
fn sheared_mesh(dy: f64) -> 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, dy, 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_areas(vec![1.0, 1.0, 1.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()
.expect("sheared mesh"),
)
}
#[test]
fn the_two_delta_schemes_agree_on_an_orthogonal_mesh() {
let mesh = sheared_mesh(0.0);
let gamma = uniform_gamma(mesh.clone(), 1.0);
let phi = VolScalarField::uniform("phi", mesh.clone(), 0.0);
let a = laplacian_with_delta(&gamma, &phi, DeltaCoeff::Orthogonal);
let b = laplacian_with_delta(&gamma, &phi, DeltaCoeff::NonOrthogonal);
for c in 0..mesh.n_cells {
assert_eq!(a.ldu.diag[c], b.ldu.diag[c], "diagonal differs at cell {c}");
}
assert_eq!(a.ldu.upper[0], b.ldu.upper[0]);
}
#[test]
fn the_non_orthogonal_delta_is_larger_by_one_over_cos_theta() {
let dy = 0.5; let mesh = sheared_mesh(dy);
let d = mesh.cell_centres[1] - mesh.cell_centres[0];
let cos_theta = 0.5 / d.mag();
assert!((cos_theta - 1.0 / 2.0_f64.sqrt()).abs() < 1e-12);
assert!((DeltaCoeff::Orthogonal.interior_delta(&mesh, 0) - d.mag()).abs() < 1e-14);
assert!((DeltaCoeff::NonOrthogonal.interior_delta(&mesh, 0) - 0.5).abs() < 1e-14);
let gamma = uniform_gamma(mesh.clone(), 1.0);
let phi = VolScalarField::uniform("phi", mesh.clone(), 0.0);
let a = laplacian_with_delta(&gamma, &phi, DeltaCoeff::Orthogonal);
let b = laplacian_with_delta(&gamma, &phi, DeltaCoeff::NonOrthogonal);
assert!(
(b.ldu.upper[0] / a.ldu.upper[0] - 1.0 / cos_theta).abs() < 1e-12,
"expected the conductance ratio to be 1/cos(theta) = {:.6}, got {:.6}",
1.0 / cos_theta,
b.ldu.upper[0] / a.ldu.upper[0]
);
}
#[test]
fn the_boundary_delta_is_the_patch_normal_projection() {
for dy in [0.0, 0.25, 0.5, 1.0] {
let mesh = sheared_mesh(dy);
let got = DeltaCoeff::NonOrthogonal.boundary_delta(&mesh, 2);
assert!(
(got - 0.25).abs() < 1e-14,
"dy = {dy}: boundary delta {got}, expected 0.25"
);
}
let mesh = sheared_mesh(0.5);
assert!((DeltaCoeff::NonOrthogonal.boundary_delta(&mesh, 1) - 0.25).abs() < 1e-14);
let euclid = (mesh.face_centres[1] - mesh.cell_centres[1]).mag();
assert!((DeltaCoeff::Orthogonal.boundary_delta(&mesh, 1) - euclid).abs() < 1e-14);
assert!(euclid > 0.5, "the Euclidean distance should have grown");
}
#[test]
fn the_default_laplacian_is_the_orthogonal_scheme() {
let mesh = sheared_mesh(0.5);
let gamma = uniform_gamma(mesh.clone(), 2.0);
let phi = VolScalarField::uniform("phi", mesh.clone(), 0.0);
let a = laplacian(&gamma, &phi);
let b = laplacian_with_delta(&gamma, &phi, DeltaCoeff::Orthogonal);
for c in 0..mesh.n_cells {
assert_eq!(a.ldu.diag[c], b.ldu.diag[c]);
}
assert_eq!(DeltaCoeff::default(), DeltaCoeff::Orthogonal);
}
}