Files
rustytorch/crates/specialized/rtx-cfd/src/turbulence/mod.rs
T
Omar Sobh 956e9d38e2
CI / Build (macos-latest) (push) Waiting to run
CI / Test (macos-latest) (push) Blocked by required conditions
CI / Test (ubuntu-latest) (push) Blocked by required conditions
CI / Python Bindings (maturin) (macos-latest) (push) Blocked by required conditions
CI / Python Bindings (maturin) (ubuntu-latest) (push) Blocked by required conditions
CI / WASM Build + Size Check (push) Blocked by required conditions
CI / Distributed Training Tests (push) Blocked by required conditions
CI / CI Success (push) Blocked by required conditions
CI / Format Check (push) Failing after 5s
CI / Build CPU-Only (Explicit) (push) Failing after 6s
Performance Benchmarks / Run Benchmarks (push) Failing after 7s
CI / Clippy Check (push) Failing after 16s
Documentation / Build User Guide (push) Successful in 12s
Documentation / Build API Documentation (push) Failing after 12s
CI / Build (ubuntu-latest) (push) Failing after 24s
rtx-cfd: the cuda feature builds again — the duplicate CudaKernelManager::stream removed, the legacy k-epsilon GPU model (broken since the initial commit) kept out of the build
2026-09-16 01:03:33 -05:00

406 lines
12 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 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")]
// PERF-2 (2026-09-16): the legacy k-epsilon GPU model no longer matches
// `KEpsilonConstants` and has not compiled since the initial commit; kept out
// of the `cuda` build until someone needs it.
// 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);
}
}