#![allow(dead_code)]
use crate::math::Fix128;
pub const SMAGORINSKY_CS: Fix128 = Fix128 {
hi: 0,
lo: 0x2B85_1EB8_51EB_851F, };
pub(crate) const KE_C_MU: Fix128 = Fix128 {
hi: 0,
lo: 0x1707_5F6F_D21F_F2E5, };
pub(crate) const KE_SIGMA_K: Fix128 = Fix128::ONE;
pub(crate) const KE_SIGMA_EPS: Fix128 = Fix128 {
hi: 1,
lo: 0x4CCC_CCCC_CCCC_CCCD,
};
pub(crate) const KE_C1_EPS: Fix128 = Fix128 {
hi: 1,
lo: 0x70A3_D70A_3D70_A3D7,
};
pub(crate) const KE_C2_EPS: Fix128 = Fix128 {
hi: 1,
lo: 0xEB85_1EB8_51EB_851F,
};
pub(crate) const KW_BETA_STAR: Fix128 = KE_C_MU;
pub(crate) const KW_BETA: Fix128 = Fix128 {
hi: 0,
lo: 0x1333_3333_3333_3333,
};
#[must_use]
pub fn smagorinsky_eddy_viscosity(filter_width_m: Fix128, strain_rate_magnitude: Fix128) -> Fix128 {
let cs_delta = SMAGORINSKY_CS * filter_width_m;
cs_delta * cs_delta * strain_rate_magnitude
}
#[must_use]
pub fn strain_rate_magnitude(
s11: Fix128,
s22: Fix128,
s33: Fix128,
s12: Fix128,
s13: Fix128,
s23: Fix128,
) -> Fix128 {
let diag = s11 * s11 + s22 * s22 + s33 * s33;
let off = Fix128::from_int(2) * (s12 * s12 + s13 * s13 + s23 * s23);
(Fix128::from_int(2) * (diag + off)).sqrt()
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub(crate) struct KEpsilonState {
pub(crate) k: Fix128,
pub(crate) epsilon: Fix128,
}
#[allow(dead_code)] impl KEpsilonState {
#[must_use]
pub(crate) fn eddy_viscosity(&self) -> Fix128 {
if self.epsilon.is_zero() {
return Fix128::ZERO;
}
KE_C_MU * self.k * self.k / self.epsilon
}
pub(crate) fn advance_k(&mut self, production_k: Fix128, dt_s: Fix128) {
self.k = self.k + (production_k - self.epsilon) * dt_s;
if self.k < Fix128::ZERO {
self.k = Fix128::ZERO;
}
}
pub(crate) fn advance_epsilon(&mut self, production_k: Fix128, dt_s: Fix128) {
if self.k.is_zero() {
return;
}
let factor = self.epsilon / self.k;
let d_eps = factor * (KE_C1_EPS * production_k - KE_C2_EPS * self.epsilon);
self.epsilon = self.epsilon + d_eps * dt_s;
if self.epsilon < Fix128::ZERO {
self.epsilon = Fix128::ZERO;
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub(crate) struct KOmegaState {
pub(crate) k: Fix128,
pub(crate) omega: Fix128,
}
#[allow(dead_code)] impl KOmegaState {
#[must_use]
pub(crate) fn eddy_viscosity(&self) -> Fix128 {
if self.omega.is_zero() {
return Fix128::ZERO;
}
self.k / self.omega
}
#[must_use]
pub(crate) fn from_k_epsilon(state: &KEpsilonState) -> Self {
let omega = if state.k.is_zero() {
Fix128::ZERO
} else {
state.epsilon / (KW_BETA_STAR * state.k)
};
Self { k: state.k, omega }
}
}
#[must_use]
pub(crate) fn dynamic_smagorinsky_cs(strain_grid: Fix128, strain_test: Fix128) -> Fix128 {
let min_cs = Fix128::from_ratio(5, 100);
let max_cs = Fix128::from_ratio(25, 100);
if strain_grid.is_zero() || strain_test.is_zero() {
return SMAGORINSKY_CS;
}
let ratio = strain_test / strain_grid;
let unclamped = SMAGORINSKY_CS * ratio;
if unclamped < min_cs {
min_cs
} else if unclamped > max_cs {
max_cs
} else {
unclamped
}
}
pub(crate) const VON_KARMAN: Fix128 = Fix128 {
hi: 0,
lo: 0x68F5_C28F_5C28_F5C3,
};
pub(crate) const LOG_LAW_B: Fix128 = Fix128 {
hi: 5,
lo: 0x8000_0000_0000_0000,
};
pub(crate) const Y_PLUS_TRANSITION: Fix128 = Fix128 { hi: 11, lo: 0 };
#[must_use]
pub(crate) fn y_plus(
density: Fix128,
u_tau: Fix128,
y_m: Fix128,
dynamic_viscosity_pas: Fix128,
) -> Fix128 {
if dynamic_viscosity_pas.is_zero() {
return Fix128::ZERO;
}
density * u_tau * y_m / dynamic_viscosity_pas
}
#[must_use]
fn ln_fix(x: Fix128) -> Fix128 {
if x <= Fix128::ZERO {
return Fix128::ZERO;
}
let ln2 = Fix128 {
hi: 0,
lo: 0xB172_17F7_D1CF_79AC,
};
let mut m = x;
let mut k: i64 = 0;
while m >= Fix128::from_int(2) {
m = m.half();
k += 1;
}
while m < Fix128::ONE {
m = m.double();
k -= 1;
}
let t = (m - Fix128::ONE) / (m + Fix128::ONE);
let t2 = t * t;
let mut term = t;
let mut sum = Fix128::ZERO;
for n in 0..16u32 {
let denom = Fix128::from_int(i64::from(2 * n + 1));
sum = sum + term / denom;
term = term * t2;
}
Fix128::from_int(k) * ln2 + sum.double()
}
#[must_use]
pub(crate) fn u_plus(y_plus_val: Fix128) -> Fix128 {
if y_plus_val <= Fix128::ZERO {
return Fix128::ZERO;
}
if y_plus_val < Y_PLUS_TRANSITION {
return y_plus_val;
}
let ln_yp = ln_fix(y_plus_val);
ln_yp / VON_KARMAN + LOG_LAW_B
}
#[must_use]
pub(crate) fn wall_k_epsilon(u_tau_m_per_s: Fix128, y_m: Fix128) -> (Fix128, Fix128) {
if y_m.is_zero() || u_tau_m_per_s.is_zero() {
return (Fix128::ZERO, Fix128::ZERO);
}
let sqrt_cmu = KE_C_MU.sqrt();
let k = if sqrt_cmu.is_zero() {
Fix128::ZERO
} else {
u_tau_m_per_s * u_tau_m_per_s / sqrt_cmu
};
let u_tau_cube = u_tau_m_per_s * u_tau_m_per_s * u_tau_m_per_s;
let eps = u_tau_cube / (VON_KARMAN * y_m);
(k, eps)
}
#[cfg(test)]
mod tests {
use super::*;
fn approx_eq(a: Fix128, b: Fix128, tol: Fix128) -> bool {
let d = if a > b { a - b } else { b - a };
d <= tol
}
#[test]
fn smagorinsky_cs_constant() {
assert!(approx_eq(
SMAGORINSKY_CS,
Fix128::from_ratio(17, 100),
Fix128::from_ratio(1, 1000)
));
}
#[test]
fn ke_c_mu_constant() {
assert!(approx_eq(
KE_C_MU,
Fix128::from_ratio(9, 100),
Fix128::from_ratio(1, 1000)
));
}
#[test]
fn smagorinsky_zero_strain_zero_viscosity() {
let nu = smagorinsky_eddy_viscosity(Fix128::from_ratio(1, 100), Fix128::ZERO);
assert_eq!(nu, Fix128::ZERO);
}
#[test]
fn smagorinsky_scales_with_delta_squared() {
let s = Fix128::from_int(10);
let nu1 = smagorinsky_eddy_viscosity(Fix128::from_ratio(1, 100), s);
let nu2 = smagorinsky_eddy_viscosity(Fix128::from_ratio(2, 100), s);
let ratio = nu2 / nu1;
assert!(approx_eq(
ratio,
Fix128::from_int(4),
Fix128::from_ratio(1, 100)
));
}
#[test]
fn strain_rate_isotropic_diagonal() {
let s = strain_rate_magnitude(
Fix128::ONE,
Fix128::ZERO,
Fix128::ZERO,
Fix128::ZERO,
Fix128::ZERO,
Fix128::ZERO,
);
let expected = Fix128::from_int(2).sqrt();
assert!(approx_eq(s, expected, Fix128::from_ratio(1, 100)));
}
#[test]
fn strain_rate_zero_tensor() {
let s = strain_rate_magnitude(
Fix128::ZERO,
Fix128::ZERO,
Fix128::ZERO,
Fix128::ZERO,
Fix128::ZERO,
Fix128::ZERO,
);
assert_eq!(s, Fix128::ZERO);
}
#[test]
fn ke_eddy_viscosity_formula() {
let st = KEpsilonState {
k: Fix128::ONE,
epsilon: Fix128::from_ratio(9, 100),
};
let nu = st.eddy_viscosity();
assert!(approx_eq(nu, Fix128::ONE, Fix128::from_ratio(1, 100)));
}
#[test]
fn ke_zero_epsilon_zero_viscosity() {
let st = KEpsilonState {
k: Fix128::ONE,
epsilon: Fix128::ZERO,
};
assert_eq!(st.eddy_viscosity(), Fix128::ZERO);
}
#[test]
fn ke_k_transport_reduces_k_no_production() {
let mut st = KEpsilonState {
k: Fix128::from_int(10),
epsilon: Fix128::from_int(1),
};
st.advance_k(Fix128::ZERO, Fix128::from_ratio(1, 10));
assert!(st.k < Fix128::from_int(10));
assert!(st.k > Fix128::from_int(9));
}
#[test]
fn ke_k_never_goes_negative() {
let mut st = KEpsilonState {
k: Fix128::ONE,
epsilon: Fix128::from_int(100),
};
st.advance_k(Fix128::ZERO, Fix128::ONE);
assert!(st.k >= Fix128::ZERO);
}
#[test]
fn ke_epsilon_transport_zero_k_stays() {
let mut st = KEpsilonState {
k: Fix128::ZERO,
epsilon: Fix128::from_int(1),
};
let eps_before = st.epsilon;
st.advance_epsilon(Fix128::ONE, Fix128::ONE);
assert_eq!(st.epsilon, eps_before);
}
#[test]
fn kw_from_ke_conversion() {
let ke = KEpsilonState {
k: Fix128::ONE,
epsilon: Fix128::from_ratio(9, 100),
};
let kw = KOmegaState::from_k_epsilon(&ke);
assert_eq!(kw.k, ke.k);
assert!(approx_eq(kw.omega, Fix128::ONE, Fix128::from_ratio(1, 100)));
}
#[test]
fn kw_eddy_viscosity_reduces_when_omega_high() {
let low = KOmegaState {
k: Fix128::ONE,
omega: Fix128::ONE,
};
let high = KOmegaState {
k: Fix128::ONE,
omega: Fix128::from_int(10),
};
assert!(low.eddy_viscosity() > high.eddy_viscosity());
}
#[test]
fn kw_zero_omega_zero_viscosity() {
let st = KOmegaState {
k: Fix128::ONE,
omega: Fix128::ZERO,
};
assert_eq!(st.eddy_viscosity(), Fix128::ZERO);
}
#[test]
fn von_karman_and_b_constants() {
assert!(approx_eq(
VON_KARMAN,
Fix128::from_ratio(41, 100),
Fix128::from_ratio(1, 100)
));
assert!(approx_eq(
LOG_LAW_B,
Fix128::from_ratio(55, 10),
Fix128::from_ratio(1, 100)
));
}
#[test]
fn y_plus_zero_viscosity_returns_zero() {
let v = y_plus(
Fix128::from_int(1000),
Fix128::ONE,
Fix128::from_ratio(1, 100),
Fix128::ZERO,
);
assert_eq!(v, Fix128::ZERO);
}
#[test]
fn u_plus_sublayer_linear() {
let up = u_plus(Fix128::from_int(5));
assert_eq!(up, Fix128::from_int(5));
}
#[test]
fn u_plus_log_region_increases_slowly() {
let up = u_plus(Fix128::from_int(100));
assert!(approx_eq(
up,
Fix128::from_ratio(167, 10),
Fix128::from_int(1)
));
}
#[test]
fn u_plus_zero_returns_zero() {
assert_eq!(u_plus(Fix128::ZERO), Fix128::ZERO);
}
#[test]
fn wall_k_epsilon_positive() {
let (k, eps) = wall_k_epsilon(Fix128::from_ratio(5, 10), Fix128::from_ratio(1, 100));
assert!(k > Fix128::ZERO);
assert!(eps > Fix128::ZERO);
}
#[test]
fn dynamic_cs_default_when_strain_zero() {
let c = dynamic_smagorinsky_cs(Fix128::ZERO, Fix128::from_int(1));
assert_eq!(c, SMAGORINSKY_CS);
}
#[test]
fn dynamic_cs_clamped_to_range() {
let c = dynamic_smagorinsky_cs(Fix128::ONE, Fix128::from_int(100));
assert!(c <= Fix128::from_ratio(25, 100));
let c2 = dynamic_smagorinsky_cs(Fix128::from_int(100), Fix128::ONE);
assert!(c2 >= Fix128::from_ratio(5, 100));
}
#[test]
fn wall_k_epsilon_zero_y_returns_zero() {
let (k, eps) = wall_k_epsilon(Fix128::ONE, Fix128::ZERO);
assert_eq!(k, Fix128::ZERO);
assert_eq!(eps, Fix128::ZERO);
}
}