Initial commit
This commit is contained in:
@@ -0,0 +1,402 @@
|
||||
//! Turbulence modeling for CFD
|
||||
//!
|
||||
//! This module provides various turbulence models for simulating turbulent flows:
|
||||
//!
|
||||
//! - **RANS Models**: k-ε (standard, RNG, realizable), k-ω, k-ω SST
|
||||
//! - **LES Models**: Smagorinsky, Dynamic Smagorinsky, WALE
|
||||
//! - **Wall Functions**: Log-law, enhanced wall treatment
|
||||
//! - **Transition Models**: γ-Reθ, k-kL-ω
|
||||
|
||||
pub mod k_epsilon;
|
||||
/// GPU-accelerated turbulence models
|
||||
#[cfg(feature = "cuda")]
|
||||
pub mod k_epsilon_gpu;
|
||||
pub mod smagorinsky;
|
||||
#[cfg(feature = "cuda")]
|
||||
pub mod smagorinsky_gpu;
|
||||
pub mod transition;
|
||||
pub mod wall_functions;
|
||||
|
||||
pub use k_epsilon::{KEpsilonConstants, KEpsilonModel, KEpsilonVariant};
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use k_epsilon_gpu::KEpsilonGpuModel;
|
||||
pub use smagorinsky::{SmagorinskyConstants, SmagorinskyModel};
|
||||
pub use wall_functions::{EnhancedWallTreatment, LogLawWallFunction, WallFunction};
|
||||
|
||||
use crate::error::CfdResult;
|
||||
use nalgebra::{DVector, Vector3};
|
||||
|
||||
/// Trait for turbulence models
|
||||
pub trait TurbulenceModel {
|
||||
/// Calculate turbulent viscosity
|
||||
fn turbulent_viscosity(&self, state: &TurbulenceState) -> CfdResult<DVector<f64>>;
|
||||
|
||||
/// Calculate production terms
|
||||
fn production_terms(&self, state: &TurbulenceState) -> CfdResult<TurbulenceProduction>;
|
||||
|
||||
/// Update turbulence quantities
|
||||
fn update(&mut self, state: &TurbulenceState, dt: f64) -> CfdResult<()>;
|
||||
|
||||
/// Get model name
|
||||
fn name(&self) -> &str;
|
||||
|
||||
/// Check if model is RANS-based
|
||||
fn is_rans(&self) -> bool;
|
||||
|
||||
/// Check if model is LES-based
|
||||
fn is_les(&self) -> bool;
|
||||
}
|
||||
|
||||
/// Turbulence state containing flow variables
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TurbulenceState {
|
||||
/// Velocity field [u, v, w]
|
||||
pub velocity: Vec<Vector3<f64>>,
|
||||
/// Velocity gradients ∇u
|
||||
pub velocity_gradients: Vec<[[f64; 3]; 3]>,
|
||||
/// Pressure field
|
||||
pub pressure: DVector<f64>,
|
||||
/// Turbulent kinetic energy (for RANS models)
|
||||
pub turbulent_ke: Option<DVector<f64>>,
|
||||
/// Turbulent dissipation rate (for k-ε models)
|
||||
pub epsilon: Option<DVector<f64>>,
|
||||
/// Specific dissipation rate (for k-ω models)
|
||||
pub omega: Option<DVector<f64>>,
|
||||
/// Distance to wall
|
||||
pub wall_distance: DVector<f64>,
|
||||
/// Cell volumes
|
||||
pub cell_volumes: DVector<f64>,
|
||||
/// Dynamic viscosity
|
||||
pub molecular_viscosity: f64,
|
||||
/// Density
|
||||
pub density: f64,
|
||||
}
|
||||
|
||||
impl TurbulenceState {
|
||||
/// Create new turbulence state
|
||||
#[must_use]
|
||||
pub fn new(n_cells: usize) -> Self {
|
||||
Self {
|
||||
velocity: vec![Vector3::zeros(); n_cells],
|
||||
velocity_gradients: vec![[[0.0; 3]; 3]; n_cells],
|
||||
pressure: DVector::zeros(n_cells),
|
||||
turbulent_ke: None,
|
||||
epsilon: None,
|
||||
omega: None,
|
||||
wall_distance: DVector::zeros(n_cells),
|
||||
cell_volumes: DVector::from_element(n_cells, 1.0),
|
||||
molecular_viscosity: 1e-5,
|
||||
density: 1.0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize k-ε model variables
|
||||
pub fn initialize_k_epsilon(&mut self, k_init: f64, epsilon_init: f64) {
|
||||
let n_cells = self.velocity.len();
|
||||
self.turbulent_ke = Some(DVector::from_element(n_cells, k_init));
|
||||
self.epsilon = Some(DVector::from_element(n_cells, epsilon_init));
|
||||
}
|
||||
|
||||
/// Initialize k-ω model variables
|
||||
pub fn initialize_k_omega(&mut self, k_init: f64, omega_init: f64) {
|
||||
let n_cells = self.velocity.len();
|
||||
self.turbulent_ke = Some(DVector::from_element(n_cells, k_init));
|
||||
self.omega = Some(DVector::from_element(n_cells, omega_init));
|
||||
}
|
||||
|
||||
/// Calculate strain rate magnitude
|
||||
#[must_use]
|
||||
pub fn strain_rate_magnitude(&self, cell_idx: usize) -> f64 {
|
||||
if cell_idx >= self.velocity_gradients.len() {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
let grad = &self.velocity_gradients[cell_idx];
|
||||
let mut s_mag = 0.0;
|
||||
|
||||
// S_ij = 0.5 * (∂u_i/∂x_j + ∂u_j/∂x_i)
|
||||
// For strain rate magnitude: |S| = sqrt(2 * S_ij * S_ij)
|
||||
// Sum over all components (symmetric tensor)
|
||||
for i in 0..3 {
|
||||
for j in 0..3 {
|
||||
let s_ij = 0.5 * (grad[i][j] + grad[j][i]);
|
||||
s_mag += s_ij * s_ij;
|
||||
}
|
||||
}
|
||||
|
||||
// The factor of 2 accounts for the definition |S| = sqrt(2*Sij*Sij)
|
||||
(2.0 * s_mag).sqrt()
|
||||
}
|
||||
|
||||
/// Calculate vorticity magnitude
|
||||
#[must_use]
|
||||
pub fn vorticity_magnitude(&self, cell_idx: usize) -> f64 {
|
||||
if cell_idx >= self.velocity_gradients.len() {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
let grad = &self.velocity_gradients[cell_idx];
|
||||
let mut omega_mag = 0.0;
|
||||
|
||||
// Ω_ij = 0.5 * (∂u_i/∂x_j - ∂u_j/∂x_i)
|
||||
// For antisymmetric tensor, sum over all components
|
||||
for i in 0..3 {
|
||||
for j in 0..3 {
|
||||
let omega_ij = 0.5 * (grad[i][j] - grad[j][i]);
|
||||
omega_mag += omega_ij * omega_ij;
|
||||
}
|
||||
}
|
||||
|
||||
// The factor of 2 accounts for the definition |Ω| = sqrt(2*Ωij*Ωij)
|
||||
(2.0 * omega_mag).sqrt()
|
||||
}
|
||||
}
|
||||
|
||||
/// Production terms for turbulence models
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TurbulenceProduction {
|
||||
/// Production of turbulent kinetic energy
|
||||
pub pk: DVector<f64>,
|
||||
/// Production of dissipation
|
||||
pub pe: Option<DVector<f64>>,
|
||||
/// Production of specific dissipation
|
||||
pub pw: Option<DVector<f64>>,
|
||||
}
|
||||
|
||||
impl TurbulenceProduction {
|
||||
/// Create new production terms
|
||||
#[must_use]
|
||||
pub fn new(n_cells: usize) -> Self {
|
||||
Self {
|
||||
pk: DVector::zeros(n_cells),
|
||||
pe: None,
|
||||
pw: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize for k-ε model
|
||||
#[must_use]
|
||||
pub fn for_k_epsilon(n_cells: usize) -> Self {
|
||||
Self {
|
||||
pk: DVector::zeros(n_cells),
|
||||
pe: Some(DVector::zeros(n_cells)),
|
||||
pw: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize for k-ω model
|
||||
#[must_use]
|
||||
pub fn for_k_omega(n_cells: usize) -> Self {
|
||||
Self {
|
||||
pk: DVector::zeros(n_cells),
|
||||
pe: None,
|
||||
pw: Some(DVector::zeros(n_cells)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Turbulence intensity calculation utilities
|
||||
pub struct TurbulenceIntensity;
|
||||
|
||||
impl TurbulenceIntensity {
|
||||
/// Calculate turbulence intensity from velocity fluctuations
|
||||
#[must_use]
|
||||
pub fn from_fluctuations(u_rms: f64, v_rms: f64, w_rms: f64, u_mean: f64) -> f64 {
|
||||
let turbulent_ke = 0.5 * (u_rms * u_rms + v_rms * v_rms + w_rms * w_rms);
|
||||
turbulent_ke.sqrt() / u_mean.abs().max(1e-10)
|
||||
}
|
||||
|
||||
/// Estimate turbulence intensity for external flows
|
||||
#[must_use]
|
||||
pub fn external_flow_estimate(reynolds_number: f64) -> f64 {
|
||||
// Empirical correlation for external flows
|
||||
0.16 * reynolds_number.powf(-1.0 / 8.0)
|
||||
}
|
||||
|
||||
/// Estimate turbulence intensity for internal flows
|
||||
#[must_use]
|
||||
pub fn internal_flow_estimate(reynolds_number: f64) -> f64 {
|
||||
// Empirical correlation for pipe flows
|
||||
0.16 * reynolds_number.powf(-1.0 / 8.0).min(0.1)
|
||||
}
|
||||
|
||||
/// Calculate turbulent length scale
|
||||
#[must_use]
|
||||
pub fn turbulent_length_scale(characteristic_length: f64, turbulence_intensity: f64) -> f64 {
|
||||
// Empirical estimate
|
||||
0.07 * characteristic_length * (1.0 + 10.0 * turbulence_intensity)
|
||||
}
|
||||
}
|
||||
|
||||
/// Reynolds number utilities for turbulence modeling
|
||||
pub struct ReynoldsNumber;
|
||||
|
||||
impl ReynoldsNumber {
|
||||
/// Calculate turbulent Reynolds number
|
||||
#[must_use]
|
||||
pub fn turbulent(k: f64, epsilon: f64, nu: f64) -> f64 {
|
||||
k * k / (epsilon * nu).max(1e-15)
|
||||
}
|
||||
|
||||
/// Calculate wall Reynolds number y+
|
||||
#[must_use]
|
||||
pub fn y_plus(y: f64, u_tau: f64, nu: f64) -> f64 {
|
||||
y * u_tau / nu
|
||||
}
|
||||
|
||||
/// Calculate friction velocity
|
||||
#[must_use]
|
||||
pub fn friction_velocity(wall_shear_stress: f64, density: f64) -> f64 {
|
||||
(wall_shear_stress / density).sqrt()
|
||||
}
|
||||
|
||||
/// Calculate wall shear stress from velocity gradient
|
||||
#[must_use]
|
||||
pub fn wall_shear_stress(du_dy: f64, mu: f64) -> f64 {
|
||||
mu * du_dy
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use approx::assert_relative_eq;
|
||||
|
||||
#[test]
|
||||
fn test_turbulence_state_creation() {
|
||||
let state = TurbulenceState::new(10);
|
||||
assert_eq!(state.velocity.len(), 10);
|
||||
assert_eq!(state.pressure.len(), 10);
|
||||
assert!(state.turbulent_ke.is_none());
|
||||
assert!(state.epsilon.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_turbulence_state_k_epsilon_init() {
|
||||
let mut state = TurbulenceState::new(5);
|
||||
state.initialize_k_epsilon(1.0, 0.1);
|
||||
|
||||
assert!(state.turbulent_ke.is_some());
|
||||
assert!(state.epsilon.is_some());
|
||||
assert_eq!(state.turbulent_ke.as_ref().unwrap().len(), 5);
|
||||
assert_relative_eq!(
|
||||
state.turbulent_ke.as_ref().unwrap()[0],
|
||||
1.0,
|
||||
epsilon = 1e-10
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_turbulence_state_k_omega_init() {
|
||||
let mut state = TurbulenceState::new(5);
|
||||
state.initialize_k_omega(1.0, 10.0);
|
||||
|
||||
assert!(state.turbulent_ke.is_some());
|
||||
assert!(state.omega.is_some());
|
||||
assert_eq!(state.omega.as_ref().unwrap().len(), 5);
|
||||
assert_relative_eq!(state.omega.as_ref().unwrap()[0], 10.0, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_strain_rate_magnitude() {
|
||||
let mut state = TurbulenceState::new(1);
|
||||
// Set up simple shear flow: du/dy = 1, others = 0
|
||||
state.velocity_gradients[0][0][1] = 1.0; // du/dy = 1
|
||||
|
||||
let s_mag = state.strain_rate_magnitude(0);
|
||||
// S_01 = S_10 = 0.5 * (1 + 0) = 0.5, all others = 0
|
||||
// Sum of S_ij^2 = 2 * 0.5^2 = 0.5
|
||||
// |S| = sqrt(2 * 0.5) = 1.0
|
||||
assert_relative_eq!(s_mag, 1.0, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vorticity_magnitude() {
|
||||
let mut state = TurbulenceState::new(1);
|
||||
// Set up rotation: du/dy = 1, dv/dx = -1
|
||||
state.velocity_gradients[0][0][1] = 1.0; // du/dy = 1
|
||||
state.velocity_gradients[0][1][0] = -1.0; // dv/dx = -1
|
||||
|
||||
let omega_mag = state.vorticity_magnitude(0);
|
||||
// Ω_01 = 0.5 * (1 - (-1)) = 1, Ω_10 = 0.5 * ((-1) - 1) = -1
|
||||
// Sum of Ω_ij^2 = 1^2 + (-1)^2 = 2
|
||||
// |Ω| = sqrt(2 * 2) = 2
|
||||
assert_relative_eq!(omega_mag, 2.0, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_turbulence_production_creation() {
|
||||
let prod = TurbulenceProduction::new(5);
|
||||
assert_eq!(prod.pk.len(), 5);
|
||||
assert!(prod.pe.is_none());
|
||||
assert!(prod.pw.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_turbulence_production_k_epsilon() {
|
||||
let prod = TurbulenceProduction::for_k_epsilon(5);
|
||||
assert_eq!(prod.pk.len(), 5);
|
||||
assert!(prod.pe.is_some());
|
||||
assert!(prod.pw.is_none());
|
||||
assert_eq!(prod.pe.as_ref().unwrap().len(), 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_turbulence_production_k_omega() {
|
||||
let prod = TurbulenceProduction::for_k_omega(5);
|
||||
assert_eq!(prod.pk.len(), 5);
|
||||
assert!(prod.pe.is_none());
|
||||
assert!(prod.pw.is_some());
|
||||
assert_eq!(prod.pw.as_ref().unwrap().len(), 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_turbulence_intensity_from_fluctuations() {
|
||||
let ti = TurbulenceIntensity::from_fluctuations(1.0, 1.0, 1.0, 10.0);
|
||||
// k = 0.5 * (1 + 1 + 1) = 1.5
|
||||
// TI = sqrt(k) / U = sqrt(1.5) / 10
|
||||
assert_relative_eq!(ti, 1.5_f64.sqrt() / 10.0, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_turbulence_intensity_estimates() {
|
||||
let re = 1e6;
|
||||
let ti_ext = TurbulenceIntensity::external_flow_estimate(re);
|
||||
let ti_int = TurbulenceIntensity::internal_flow_estimate(re);
|
||||
|
||||
assert!(ti_ext > 0.0);
|
||||
assert!(ti_int > 0.0);
|
||||
assert!(ti_ext < 1.0);
|
||||
assert!(ti_int < 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_turbulent_length_scale() {
|
||||
let l_scale = TurbulenceIntensity::turbulent_length_scale(1.0, 0.05);
|
||||
assert!(l_scale > 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_reynolds_number_turbulent() {
|
||||
let re_t = ReynoldsNumber::turbulent(1.0, 1.0, 1e-6);
|
||||
assert_relative_eq!(re_t, 1e6, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_reynolds_number_y_plus() {
|
||||
let y_plus = ReynoldsNumber::y_plus(1e-5, 0.1, 1e-6);
|
||||
assert_relative_eq!(y_plus, 1.0, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_friction_velocity() {
|
||||
let u_tau = ReynoldsNumber::friction_velocity(0.1, 1.0);
|
||||
assert_relative_eq!(u_tau, 0.1_f64.sqrt(), epsilon = 1e-10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wall_shear_stress() {
|
||||
let tau_w = ReynoldsNumber::wall_shear_stress(100.0, 1e-3);
|
||||
assert_relative_eq!(tau_w, 0.1, epsilon = 1e-10);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user