use std::sync::Arc;
use crate::fields::vol_field::{VolScalarField, VolVectorField};
use crate::ldu_matrix::fv_vector_matrix::FvVectorMatrix;
use crate::mesh::fv_mesh::FvMesh;
pub fn d2dt2(
u: &VolVectorField,
u_old: &VolVectorField,
u_oldold: &VolVectorField,
dt: f64,
mesh: Arc<FvMesh>,
) -> FvVectorMatrix {
let n = mesh.n_cells;
let r_dt2 = 1.0 / (dt * dt);
let mut mat = FvVectorMatrix::new(mesh.clone());
for c in 0..n {
let coeff = mesh.cell_volumes[c] * r_dt2;
mat.ldu.diag[c] += coeff;
mat.source[c] += (u_old.internal[c] * 2.0 - u_oldold.internal[c]) * coeff;
}
let _ = u; mat
}
pub fn d2dt2_coeff(
rho: &VolScalarField,
u: &VolVectorField,
u_old: &VolVectorField,
u_oldold: &VolVectorField,
dt: f64,
mesh: Arc<FvMesh>,
) -> FvVectorMatrix {
let n = mesh.n_cells;
let r_dt2 = 1.0 / (dt * dt);
let mut mat = FvVectorMatrix::new(mesh.clone());
for c in 0..n {
let coeff = rho.internal[c] * mesh.cell_volumes[c] * r_dt2;
mat.ldu.diag[c] += coeff;
mat.source[c] += (u_old.internal[c] * 2.0 - u_oldold.internal[c]) * coeff;
}
let _ = u;
mat
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ldu_matrix::fv_matrix::SolverSettings;
use crate::mesh::fv_mesh::{BoundaryPatch, FvMeshBuilder, PatchKind};
use crate::primitives::Vector3;
fn unit_mesh() -> Arc<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 d2dt2_of_quadratic_returns_constant_acceleration() {
let m = unit_mesh();
let dt = 0.1_f64;
let t = 0.7_f64;
let a = Vector3::new(1.0, 1.0, 1.0);
let b = Vector3::new(2.0, -1.0, 3.0);
let c = Vector3::new(0.5, 4.0, -2.0);
let quad = |tt: f64| a + b * tt + c * (tt * tt);
let u = VolVectorField::uniform("U", m.clone(), quad(t));
let u_old = VolVectorField::uniform("Uo", m.clone(), quad(t - dt));
let u_oldold = VolVectorField::uniform("Uoo", m.clone(), quad(t - 2.0 * dt));
let mat = d2dt2(&u, &u_old, &u_oldold, dt, m.clone());
let expected = c * 2.0; for cell in 0..2 {
let resid = (u.internal[cell] * mat.ldu.diag[cell] - mat.source[cell])
* (1.0 / m.cell_volumes[cell]);
assert!(
(resid.x - expected.x).abs() < 1e-9,
"cell {cell} x = {}",
resid.x
);
assert!(
(resid.y - expected.y).abs() < 1e-9,
"cell {cell} y = {}",
resid.y
);
assert!(
(resid.z - expected.z).abs() < 1e-9,
"cell {cell} z = {}",
resid.z
);
}
}
#[test]
fn d2dt2_no_spatial_terms_continues_linearly() {
let m = unit_mesh();
let u = VolVectorField::zero("U", m.clone());
let u_old = VolVectorField::uniform("Uo", m.clone(), Vector3::new(1.0, 2.0, 3.0));
let u_oldold = VolVectorField::uniform("Uoo", m.clone(), Vector3::new(0.0, 1.0, 2.0));
let mat = d2dt2(&u, &u_old, &u_oldold, 0.05, m.clone());
let (u_new, perf) = mat.solve("U", SolverSettings::default());
assert!(perf.converged, "residual = {}", perf.final_residual);
assert!((u_new.internal[0].x - 2.0).abs() < 1e-7);
assert!((u_new.internal[0].y - 3.0).abs() < 1e-7);
assert!((u_new.internal[0].z - 4.0).abs() < 1e-7);
}
#[test]
fn d2dt2_coeff_scales_diagonal_by_rho() {
let m = unit_mesh();
let rho = VolScalarField::uniform("rho", m.clone(), 3.0);
let u = VolVectorField::zero("U", m.clone());
let u_old = VolVectorField::uniform("Uo", m.clone(), Vector3::new(5.0, 0.0, 0.0));
let u_oldold = VolVectorField::uniform("Uoo", m.clone(), Vector3::new(1.0, 0.0, 0.0));
let mat = d2dt2_coeff(&rho, &u, &u_old, &u_oldold, 0.5, m);
assert!(
(mat.ldu.diag[0] - 12.0).abs() < 1e-12,
"diag = {}",
mat.ldu.diag[0]
);
assert!(
(mat.source[0].x - 108.0).abs() < 1e-12,
"source.x = {}",
mat.source[0].x
);
}
}