use petir::mathf::RealMath;
use super::*;
use crate::mesh::FvMesh;
use std::sync::Arc;
const T_SOLID: f64 = 330.0;
const T_LIQUID: f64 = 335.0;
const LATENT_HEAT: f64 = 334_000.0;
const N_CELLS: usize = 4;
const CELL_VOLUME: f64 = 0.5;
fn mesh() -> Arc<FvMesh> {
Arc::new(FvMesh::periodic_1d(N_CELLS, 4.0, 0.5))
}
fn table() -> TemperatureTable {
TemperatureTable::two_knot(T_SOLID, T_LIQUID, 1.0, 0.0)
}
fn model() -> VofSolidificationMelting {
VofSolidificationMelting::new(
"solidZone",
"U",
"h",
CellSelection::All,
table(),
LATENT_HEAT,
N_CELLS,
)
}
fn uniform(name: &str, m: &Arc<FvMesh>, value: f64) -> VolScalarField {
VolScalarField::uniform(name, m.clone(), value)
}
#[test]
fn one_correct_from_zero_gives_exactly_the_relaxation_coefficient() {
let m = mesh();
let mut model = model();
let t = uniform("T", &m, 300.0);
let alpha_vof = uniform("alpha.liquid", &m, 1.0);
model.correct(&t, &alpha_vof);
println!(
"solid_fraction after one correct = {:?}",
model.solid_fraction()
);
assert_eq!(model.solid_fraction()[0], 0.9);
}
#[test]
fn repeated_correct_converges_geometrically_to_the_table_value() {
let m = mesh();
let mut model = model();
let t = uniform("T", &m, 300.0);
let alpha_vof = uniform("alpha.liquid", &m, 1.0);
for _ in 0..10 {
model.correct(&t, &alpha_vof);
}
let measured = model.solid_fraction()[0];
let closed_form = 1.0 - 0.1_f64.r_powi(10);
println!(
"after 10 corrects: {measured:.10}, closed form {closed_form:.10}, diff {:.10}",
measured - closed_form
);
assert!((measured - closed_form).abs() < 1e-15);
}
#[test]
fn solid_fraction_is_capped_by_the_vof_phase_fraction() {
let m = mesh();
let mut model = model();
let t = uniform("T", &m, 300.0);
let full = uniform("alpha.liquid", &m, 1.0);
for _ in 0..10 {
model.correct(&t, &full);
}
let before = model.solid_fraction()[0];
let partial = uniform("alpha.liquid", &m, 0.3);
model.correct(&t, &partial);
let after = model.solid_fraction()[0];
let uncapped = 0.9 * 0.3 * 1.0 + 0.1 * before;
println!("before = {before:.16}, uncapped blend = {uncapped:.16}, measured = {after:.16}");
assert!(
uncapped > 0.3,
"the cap must actually be binding in this test"
);
assert_eq!(after, 0.3);
}
#[test]
fn heating_above_the_liquidus_decays_the_solid_fraction() {
let m = mesh();
let mut model = model();
let cold = uniform("T", &m, 300.0);
let alpha_vof = uniform("alpha.liquid", &m, 1.0);
for _ in 0..10 {
model.correct(&cold, &alpha_vof);
}
let before = model.solid_fraction()[0];
let hot = uniform("T", &m, 400.0);
model.correct(&hot, &alpha_vof);
let after = model.solid_fraction()[0];
let closed_form = 0.1 * before;
println!("before = {before:.11}, after = {after:.11}, closed form = {closed_form:.11}");
assert!((after - closed_form).abs() < 1e-15);
}
#[test]
fn fully_fluid_cells_feel_exactly_no_drag() {
let m = mesh();
let model = model();
let rho = uniform("rho", &m, 1000.0);
let mut eqn = FvVectorMatrix::new(m.clone());
model.add_momentum_source(&rho, &mut eqn);
println!("diag = {:?}", eqn.ldu.diag);
for c in 0..N_CELLS {
assert_eq!(eqn.ldu.diag[c], 0.0);
}
}
#[test]
fn a_mostly_solid_cell_gains_a_large_positive_diagonal_drag() {
let m = mesh();
let mut model = model();
let t = uniform("T", &m, 300.0);
let alpha_vof = uniform("alpha.liquid", &m, 1.0);
model.correct(&t, &alpha_vof);
let rho_value = 1000.0;
let rho = uniform("rho", &m, rho_value);
let mut eqn = FvVectorMatrix::new(m.clone());
model.add_momentum_source(&rho, &mut eqn);
let alpha_fluid = 0.1_f64;
let closed_form = CELL_VOLUME * rho_value * 1.0e5 * (1.0 - alpha_fluid).r_powi(2)
/ (alpha_fluid.r_powi(3) + 1.0e-3);
println!(
"alpha_solid = {}, diag[0] = {}, closed form = {closed_form}",
model.solid_fraction()[0],
eqn.ldu.diag[0]
);
assert!(eqn.ldu.diag[0] > 0.0, "the drag must be a stabilising sink");
assert!((eqn.ldu.diag[0] - closed_form).abs() < 1e-9 * closed_form);
}
#[test]
fn freezing_puts_a_negative_latent_heat_source_in_the_enthalpy_equation() {
let m = mesh();
let mut model = model();
let t = uniform("T", &m, 300.0);
let alpha_vof = uniform("alpha.liquid", &m, 1.0);
model.advance_time(); model.correct(&t, &alpha_vof);
let dt = 0.5;
let rho_value = 1000.0;
let rho = uniform("rho", &m, rho_value);
let mut eqn = FvMatrix::new(m.clone());
model.add_enthalpy_source(&rho, dt, &mut eqn);
let closed_form = -CELL_VOLUME * LATENT_HEAT * rho_value * 0.9 / dt;
println!("source[0] = {}, closed form = {closed_form}", eqn.source[0]);
assert!(eqn.source[0] < 0.0, "freezing must release heat");
assert!((eqn.source[0] - closed_form).abs() < 1e-9 * closed_form.abs());
}
#[test]
fn omitting_advance_time_inflates_the_latent_heat_rate() {
let m = mesh();
let t = uniform("T", &m, 300.0);
let alpha_vof = uniform("alpha.liquid", &m, 1.0);
let dt = 0.5;
let rho = uniform("rho", &m, 1000.0);
let mut correct_model = model();
correct_model.advance_time();
correct_model.correct(&t, &alpha_vof);
correct_model.advance_time();
correct_model.correct(&t, &alpha_vof);
let mut correct_eqn = FvMatrix::new(m.clone());
correct_model.add_enthalpy_source(&rho, dt, &mut correct_eqn);
let mut stale_model = model();
stale_model.correct(&t, &alpha_vof);
stale_model.correct(&t, &alpha_vof);
let mut stale_eqn = FvMatrix::new(m.clone());
stale_model.add_enthalpy_source(&rho, dt, &mut stale_eqn);
println!(
"with advance_time: source[0] = {}, without: source[0] = {}, ratio = {}",
correct_eqn.source[0],
stale_eqn.source[0],
stale_eqn.source[0] / correct_eqn.source[0]
);
assert!(stale_eqn.source[0].abs() > correct_eqn.source[0].abs());
}
#[test]
fn a_zero_timestep_contributes_nothing_instead_of_dividing_by_zero() {
let m = mesh();
let mut model = model();
let t = uniform("T", &m, 300.0);
let alpha_vof = uniform("alpha.liquid", &m, 1.0);
model.correct(&t, &alpha_vof);
let rho = uniform("rho", &m, 1000.0);
let mut eqn = FvMatrix::new(m.clone());
model.add_enthalpy_source(&rho, 0.0, &mut eqn);
println!("source = {:?}", eqn.source);
for c in 0..N_CELLS {
assert_eq!(eqn.source[c], 0.0);
}
}