use crate::fields::vol_field::VolScalarField;
use crate::ldu_matrix::fv_matrix::FvMatrix;
pub fn ddt(phi: &VolScalarField, phi_old: &VolScalarField, dt: f64) -> FvMatrix {
let mesh = phi.mesh.clone();
let mut mat = FvMatrix::new(mesh.clone());
for c in 0..mesh.n_cells {
let coeff = mesh.cell_volumes[c] / dt;
mat.ldu.diag[c] += coeff;
mat.source[c] += coeff * phi_old.internal[c];
}
mat
}
pub fn ddt_coeff(
coeff: &VolScalarField,
phi: &VolScalarField,
phi_old: &VolScalarField,
dt: f64,
) -> FvMatrix {
let mesh = phi.mesh.clone();
let mut mat = FvMatrix::new(mesh.clone());
for c in 0..mesh.n_cells {
let c_dt = coeff.internal[c] * mesh.cell_volumes[c] / dt;
mat.ldu.diag[c] += c_dt;
mat.source[c] += c_dt * phi_old.internal[c];
}
let _ = phi;
mat
}
#[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![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 ddt_diagonal_is_volume_over_dt() {
let m = unit_mesh();
let phi = VolScalarField::uniform("p", m.clone(), 0.0);
let phi_old = VolScalarField::uniform("p_old", m.clone(), 2.0);
let mat = ddt(&phi, &phi_old, 0.1);
assert!((mat.ldu.diag[0] - 5.0).abs() < 1e-12);
assert!((mat.source[0] - 10.0).abs() < 1e-12);
}
#[test]
fn ddt_solves_to_old_when_no_spatial_terms() {
let m = unit_mesh();
let phi = VolScalarField::zeros("p", m.clone());
let phi_old = VolScalarField::uniform("p_old", m.clone(), 3.0);
let mat = ddt(&phi, &phi_old, 1.0);
let settings = crate::ldu_matrix::fv_matrix::SolverSettings::default();
let (result, perf) = mat.solve("p", settings);
assert!(perf.converged, "Gauss-Seidel did not converge");
assert!((result.internal[0] - 3.0).abs() < 1e-8);
assert!((result.internal[1] - 3.0).abs() < 1e-8);
}
#[test]
fn ddt_coeff_diagonal_is_coeff_times_volume_over_dt() {
let m = unit_mesh();
let coeff = VolScalarField::uniform("rho_cp", m.clone(), 2.0);
let phi = VolScalarField::zeros("T", m.clone());
let phi_old = VolScalarField::uniform("T_old", m.clone(), 5.0);
let mat = ddt_coeff(&coeff, &phi, &phi_old, 0.1);
assert!((mat.ldu.diag[0] - 10.0).abs() < 1e-12);
assert!((mat.ldu.diag[1] - 10.0).abs() < 1e-12);
assert!((mat.source[0] - 50.0).abs() < 1e-12);
}
#[test]
fn ddt_coeff_solves_to_old_when_no_spatial_terms() {
let m = unit_mesh();
let coeff = VolScalarField::uniform("rho_cp", m.clone(), 3.0);
let phi = VolScalarField::zeros("T", m.clone());
let phi_old = VolScalarField::uniform("T_old", m.clone(), 7.0);
let mat = ddt_coeff(&coeff, &phi, &phi_old, 1.0);
let settings = crate::ldu_matrix::fv_matrix::SolverSettings::default();
let (result, perf) = mat.solve("T", settings);
assert!(perf.converged);
assert!((result.internal[0] - 7.0).abs() < 1e-8);
assert!((result.internal[1] - 7.0).abs() < 1e-8);
}
}