use super::models::{TurbulenceConstants, TurbulenceModel, TurbulenceModelType};
use crate::error::IntegrateResult;
use scirs2_core::ndarray::{Array2, Array3};
use scirs2_core::simd_ops::SimdUnifiedOps;
#[derive(Debug, Clone)]
pub struct AdvancedTurbulenceModel {
pub model_type: TurbulenceModelType,
pub constants: TurbulenceConstants,
pub wall_distance: Array3<f64>,
}
impl AdvancedTurbulenceModel {
pub fn new(modeltype: TurbulenceModelType, nx: usize, ny: usize, nz: usize) -> Self {
Self {
model_type: modeltype,
constants: TurbulenceConstants::default(),
wall_distance: Array3::ones((nx, ny, nz)),
}
}
pub fn solve_k_epsilon_simd(
&self,
velocity: &[Array3<f64>],
k: &mut Array3<f64>,
epsilon: &mut Array3<f64>,
dt: f64,
dx: f64,
dy: f64,
dz: f64,
) -> IntegrateResult<()> {
let (nx, ny, nz) = k.dim();
let strain_rate = self.calculate_strain_rate_simd(velocity, dx, dy, dz)?;
let mut production = Array3::zeros((nx, ny, nz));
for i in 0..nx {
for j in 0..ny {
for k_idx in 0..nz {
let mut s_squared: f64 = 0.0;
for ii in 0..3 {
for jj in 0..3 {
s_squared += strain_rate[[i, j, k_idx]][[ii, jj]]
* strain_rate[[i, j, k_idx]][[ii, jj]];
}
}
let mu_t = self.constants.c_mu * k[[i, j, k_idx]].powi(2)
/ epsilon[[i, j, k_idx]].max(1e-10);
production[[i, j, k_idx]] = 2.0 * mu_t * s_squared;
}
}
}
self.solve_k_equation_simd(k, &production, epsilon, dt, dx, dy, dz)?;
self.solve_epsilon_equation_simd(epsilon, &production, k, dt, dx, dy, dz)?;
Ok(())
}
fn calculate_strain_rate_simd(
&self,
velocity: &[Array3<f64>],
dx: f64,
dy: f64,
dz: f64,
) -> IntegrateResult<Array3<Array2<f64>>> {
let (nx, ny, nz) = velocity[0].dim();
let mut strain_rate = Array3::from_elem((nx, ny, nz), Array2::zeros((3, 3)));
for i in 1..nx - 1 {
for j in 1..ny - 1 {
for k in 1..nz - 1 {
let dudx =
(velocity[0][[i + 1, j, k]] - velocity[0][[i - 1, j, k]]) / (2.0 * dx);
let dvdx =
(velocity[1][[i + 1, j, k]] - velocity[1][[i - 1, j, k]]) / (2.0 * dx);
let dwdx =
(velocity[2][[i + 1, j, k]] - velocity[2][[i - 1, j, k]]) / (2.0 * dx);
let dudy =
(velocity[0][[i, j + 1, k]] - velocity[0][[i, j - 1, k]]) / (2.0 * dy);
let dvdy =
(velocity[1][[i, j + 1, k]] - velocity[1][[i, j - 1, k]]) / (2.0 * dy);
let dwdy =
(velocity[2][[i, j + 1, k]] - velocity[2][[i, j - 1, k]]) / (2.0 * dy);
let dudz =
(velocity[0][[i, j, k + 1]] - velocity[0][[i, j, k - 1]]) / (2.0 * dz);
let dvdz =
(velocity[1][[i, j, k + 1]] - velocity[1][[i, j, k - 1]]) / (2.0 * dz);
let dwdz =
(velocity[2][[i, j, k + 1]] - velocity[2][[i, j, k - 1]]) / (2.0 * dz);
strain_rate[[i, j, k]][[0, 0]] = dudx;
strain_rate[[i, j, k]][[1, 1]] = dvdy;
strain_rate[[i, j, k]][[2, 2]] = dwdz;
strain_rate[[i, j, k]][[0, 1]] = 0.5 * (dudy + dvdx);
strain_rate[[i, j, k]][[1, 0]] = strain_rate[[i, j, k]][[0, 1]];
strain_rate[[i, j, k]][[0, 2]] = 0.5 * (dudz + dwdx);
strain_rate[[i, j, k]][[2, 0]] = strain_rate[[i, j, k]][[0, 2]];
strain_rate[[i, j, k]][[1, 2]] = 0.5 * (dvdz + dwdy);
strain_rate[[i, j, k]][[2, 1]] = strain_rate[[i, j, k]][[1, 2]];
}
}
}
Ok(strain_rate)
}
fn solve_k_equation_simd(
&self,
k: &mut Array3<f64>,
production: &Array3<f64>,
epsilon: &Array3<f64>,
dt: f64,
dx: f64,
dy: f64,
dz: f64,
) -> IntegrateResult<()> {
let (nx, ny, nz) = k.dim();
let mut k_new = k.clone();
for i in 1..nx - 1 {
for j in 1..ny - 1 {
for k_idx in 1..nz - 1 {
let d2k_dx2 = (k[[i + 1, j, k_idx]] - 2.0 * k[[i, j, k_idx]]
+ k[[i - 1, j, k_idx]])
/ (dx * dx);
let d2k_dy2 = (k[[i, j + 1, k_idx]] - 2.0 * k[[i, j, k_idx]]
+ k[[i, j - 1, k_idx]])
/ (dy * dy);
let d2k_dz2 = (k[[i, j, k_idx + 1]] - 2.0 * k[[i, j, k_idx]]
+ k[[i, j, k_idx - 1]])
/ (dz * dz);
let mu_t = self.constants.c_mu * k[[i, j, k_idx]].powi(2)
/ epsilon[[i, j, k_idx]].max(1e-10);
let diffusion = (mu_t / self.constants.sigma_k) * (d2k_dx2 + d2k_dy2 + d2k_dz2);
let source = production[[i, j, k_idx]] - epsilon[[i, j, k_idx]] + diffusion;
k_new[[i, j, k_idx]] = k[[i, j, k_idx]] + dt * source;
k_new[[i, j, k_idx]] = k_new[[i, j, k_idx]].max(1e-10);
}
}
}
*k = k_new;
Ok(())
}
fn solve_epsilon_equation_simd(
&self,
epsilon: &mut Array3<f64>,
production: &Array3<f64>,
k: &Array3<f64>,
dt: f64,
dx: f64,
dy: f64,
dz: f64,
) -> IntegrateResult<()> {
let (nx, ny, nz) = epsilon.dim();
let mut epsilon_new = epsilon.clone();
for i in 1..nx - 1 {
for j in 1..ny - 1 {
for k_idx in 1..nz - 1 {
let d2e_dx2 = (epsilon[[i + 1, j, k_idx]] - 2.0 * epsilon[[i, j, k_idx]]
+ epsilon[[i - 1, j, k_idx]])
/ (dx * dx);
let d2e_dy2 = (epsilon[[i, j + 1, k_idx]] - 2.0 * epsilon[[i, j, k_idx]]
+ epsilon[[i, j - 1, k_idx]])
/ (dy * dy);
let d2e_dz2 = (epsilon[[i, j, k_idx + 1]] - 2.0 * epsilon[[i, j, k_idx]]
+ epsilon[[i, j, k_idx - 1]])
/ (dz * dz);
let mu_t = self.constants.c_mu * k[[i, j, k_idx]].powi(2)
/ epsilon[[i, j, k_idx]].max(1e-10);
let diffusion =
(mu_t / self.constants.sigma_epsilon) * (d2e_dx2 + d2e_dy2 + d2e_dz2);
let time_scale = k[[i, j, k_idx]] / epsilon[[i, j, k_idx]].max(1e-10);
let production_term =
self.constants.c_1 * production[[i, j, k_idx]] / time_scale;
let dissipation_term = self.constants.c_2 * epsilon[[i, j, k_idx]] / time_scale;
let source = production_term - dissipation_term + diffusion;
epsilon_new[[i, j, k_idx]] = epsilon[[i, j, k_idx]] + dt * source;
epsilon_new[[i, j, k_idx]] = epsilon_new[[i, j, k_idx]].max(1e-10);
}
}
}
*epsilon = epsilon_new;
Ok(())
}
pub fn calculate_turbulent_viscosity(
&self,
k: &Array3<f64>,
epsilon: &Array3<f64>,
) -> Array3<f64> {
let (nx, ny, nz) = k.dim();
let mut mu_t = Array3::zeros((nx, ny, nz));
for i in 0..nx {
for j in 0..ny {
for k_idx in 0..nz {
mu_t[[i, j, k_idx]] = self.constants.c_mu * k[[i, j, k_idx]].powi(2)
/ epsilon[[i, j, k_idx]].max(1e-10);
}
}
}
mu_t
}
}
impl TurbulenceModel for AdvancedTurbulenceModel {
fn model_type(&self) -> TurbulenceModelType {
self.model_type
}
fn constants(&self) -> &TurbulenceConstants {
&self.constants
}
fn compute_turbulent_viscosity(
&self,
k: &Array3<f64>,
epsilon_or_omega: &Array3<f64>,
) -> IntegrateResult<Array3<f64>> {
Ok(self.calculate_turbulent_viscosity(k, epsilon_or_omega))
}
fn compute_production_term(
&self,
velocity: &[Array3<f64>],
k: &Array3<f64>,
epsilon_or_omega: &Array3<f64>,
dx: f64,
dy: f64,
dz: f64,
) -> IntegrateResult<Array3<f64>> {
let (nx, ny, nz) = k.dim();
let strain_rate = self.calculate_strain_rate_simd(velocity, dx, dy, dz)?;
let mut production = Array3::zeros((nx, ny, nz));
for i in 0..nx {
for j in 0..ny {
for k_idx in 0..nz {
let mut s_squared: f64 = 0.0;
for ii in 0..3 {
for jj in 0..3 {
s_squared += strain_rate[[i, j, k_idx]][[ii, jj]]
* strain_rate[[i, j, k_idx]][[ii, jj]];
}
}
let mu_t = self.constants.c_mu * k[[i, j, k_idx]].powi(2)
/ epsilon_or_omega[[i, j, k_idx]].max(1e-10);
production[[i, j, k_idx]] = 2.0 * mu_t * s_squared;
}
}
}
Ok(production)
}
fn apply_turbulence_boundary_conditions(
&self,
k: &mut Array3<f64>,
epsilon_or_omega: &mut Array3<f64>,
) -> IntegrateResult<()> {
let (nx, ny, nz) = k.dim();
for i in 0..nx {
for j in 0..ny {
k[[i, j, 0]] = 0.0;
k[[i, j, nz - 1]] = 0.0;
epsilon_or_omega[[i, j, 0]] = 1e6;
epsilon_or_omega[[i, j, nz - 1]] = 1e6;
}
}
for i in 0..nx {
for k_idx in 0..nz {
k[[i, 0, k_idx]] = 0.0;
k[[i, ny - 1, k_idx]] = 0.0;
epsilon_or_omega[[i, 0, k_idx]] = 1e6;
epsilon_or_omega[[i, ny - 1, k_idx]] = 1e6;
}
}
for j in 0..ny {
for k_idx in 0..nz {
k[[0, j, k_idx]] = 0.0;
k[[nx - 1, j, k_idx]] = 0.0;
epsilon_or_omega[[0, j, k_idx]] = 1e6;
epsilon_or_omega[[nx - 1, j, k_idx]] = 1e6;
}
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct SpalartAllmarasModel {
pub constants: SpalartAllmarasConstants,
pub nu_tilde: Array3<f64>,
pub vorticity: Array3<f64>,
pub wall_distance: Array3<f64>,
}
#[derive(Debug, Clone)]
pub struct SpalartAllmarasConstants {
pub cb1: f64,
pub cb2: f64,
pub sigma: f64,
pub kappa: f64,
pub cw1: f64,
pub cw2: f64,
pub cw3: f64,
pub cv1: f64,
pub cv2: f64,
}
impl Default for SpalartAllmarasConstants {
fn default() -> Self {
Self {
cb1: 0.1355,
cb2: 0.622,
sigma: 2.0 / 3.0,
kappa: 0.41,
cw1: 0.3,
cw2: 0.3,
cw3: 2.0,
cv1: 7.1,
cv2: 0.7,
}
}
}
impl SpalartAllmarasModel {
pub fn new(nx: usize, ny: usize, nz: usize) -> Self {
Self {
constants: SpalartAllmarasConstants::default(),
nu_tilde: Array3::zeros((nx, ny, nz)),
vorticity: Array3::zeros((nx, ny, nz)),
wall_distance: Array3::zeros((nx, ny, nz)),
}
}
pub fn solve_transport_equation(
&mut self,
velocity: &[Array3<f64>],
molecular_viscosity: f64,
dt: f64,
dx: f64,
dy: f64,
dz: f64,
) -> IntegrateResult<()> {
let (nx, ny, nz) = self.nu_tilde.dim();
self.compute_vorticity(velocity, dx, dy, dz)?;
for i in 1..nx - 1 {
for j in 1..ny - 1 {
for k in 1..nz - 1 {
let d = self.wall_distance[[i, j, k]];
let s_tilde = self.compute_modified_vorticity(i, j, k, d)?;
let production = self.constants.cb1 * s_tilde * self.nu_tilde[[i, j, k]];
let destruction = self.compute_destruction_term(i, j, k, d)?;
let diffusion =
self.compute_diffusion_term(i, j, k, molecular_viscosity, dx, dy, dz)?;
self.nu_tilde[[i, j, k]] += dt * (production - destruction + diffusion);
self.nu_tilde[[i, j, k]] = self.nu_tilde[[i, j, k]].max(0.0);
}
}
}
Ok(())
}
fn compute_vorticity(
&mut self,
velocity: &[Array3<f64>],
dx: f64,
dy: f64,
dz: f64,
) -> IntegrateResult<()> {
let (nx, ny, nz) = velocity[0].dim();
for i in 1..nx - 1 {
for j in 1..ny - 1 {
for k in 1..nz - 1 {
let omega_x = (velocity[2][[i, j + 1, k]] - velocity[2][[i, j - 1, k]])
/ (2.0 * dy)
- (velocity[1][[i, j, k + 1]] - velocity[1][[i, j, k - 1]]) / (2.0 * dz);
let omega_y = (velocity[0][[i, j, k + 1]] - velocity[0][[i, j, k - 1]])
/ (2.0 * dz)
- (velocity[2][[i + 1, j, k]] - velocity[2][[i - 1, j, k]]) / (2.0 * dx);
let omega_z = (velocity[1][[i + 1, j, k]] - velocity[1][[i - 1, j, k]])
/ (2.0 * dx)
- (velocity[0][[i, j + 1, k]] - velocity[0][[i, j - 1, k]]) / (2.0 * dy);
self.vorticity[[i, j, k]] =
(omega_x * omega_x + omega_y * omega_y + omega_z * omega_z).sqrt();
}
}
}
Ok(())
}
fn compute_modified_vorticity(
&self,
i: usize,
j: usize,
k: usize,
d: f64,
) -> IntegrateResult<f64> {
let nu_tilde = self.nu_tilde[[i, j, k]];
let omega = self.vorticity[[i, j, k]];
if d > 1e-12 {
let chi = nu_tilde / (d * d * omega).max(1e-12);
let fv2 = 1.0 - chi / (1.0 + chi * self.constants.cv1);
Ok(omega + nu_tilde * fv2 / (self.constants.kappa * d).powi(2))
} else {
Ok(omega)
}
}
fn compute_destruction_term(
&self,
i: usize,
j: usize,
k: usize,
d: f64,
) -> IntegrateResult<f64> {
let nu_tilde = self.nu_tilde[[i, j, k]];
if d > 1e-12 {
let r = nu_tilde.min(10.0)
/ ((self.vorticity[[i, j, k]] * self.constants.kappa * d).max(1e-12));
let g = r + self.constants.cw2 * (r.powi(6) - r);
let fw = g
* ((1.0 + self.constants.cw3.powi(6)) / (g.powi(6) + self.constants.cw3.powi(6)))
.powf(1.0 / 6.0);
Ok(self.constants.cw1 * fw * (nu_tilde / d).powi(2))
} else {
Ok(0.0)
}
}
fn compute_diffusion_term(
&self,
i: usize,
j: usize,
k: usize,
molecular_viscosity: f64,
dx: f64,
dy: f64,
dz: f64,
) -> IntegrateResult<f64> {
let d2nu_dx2 = (self.nu_tilde[[i + 1, j, k]] - 2.0 * self.nu_tilde[[i, j, k]]
+ self.nu_tilde[[i - 1, j, k]])
/ (dx * dx);
let d2nu_dy2 = (self.nu_tilde[[i, j + 1, k]] - 2.0 * self.nu_tilde[[i, j, k]]
+ self.nu_tilde[[i, j - 1, k]])
/ (dy * dy);
let d2nu_dz2 = (self.nu_tilde[[i, j, k + 1]] - 2.0 * self.nu_tilde[[i, j, k]]
+ self.nu_tilde[[i, j, k - 1]])
/ (dz * dz);
let diffusion_coefficient =
(molecular_viscosity + self.nu_tilde[[i, j, k]]) / self.constants.sigma;
Ok(diffusion_coefficient * (d2nu_dx2 + d2nu_dy2 + d2nu_dz2))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_advanced_turbulence_model_creation() {
let model = AdvancedTurbulenceModel::new(TurbulenceModelType::KEpsilon, 5, 5, 5);
assert_eq!(model.model_type, TurbulenceModelType::KEpsilon);
assert_eq!(model.wall_distance.dim(), (5, 5, 5));
}
#[test]
fn test_spalart_allmaras_model_creation() {
let model = SpalartAllmarasModel::new(8, 8, 8);
assert_eq!(model.nu_tilde.dim(), (8, 8, 8));
assert_eq!(model.vorticity.dim(), (8, 8, 8));
}
#[test]
fn test_turbulent_viscosity_calculation() {
let model = AdvancedTurbulenceModel::new(TurbulenceModelType::KEpsilon, 4, 4, 4);
let k = Array3::ones((4, 4, 4));
let epsilon = Array3::ones((4, 4, 4));
let mu_t = model.calculate_turbulent_viscosity(&k, &epsilon);
assert_eq!(mu_t.dim(), (4, 4, 4));
assert!(mu_t[[1, 1, 1]] > 0.0);
}
#[test]
fn test_spalart_allmaras_constants() {
let constants = SpalartAllmarasConstants::default();
assert_eq!(constants.cb1, 0.1355);
assert_eq!(constants.kappa, 0.41);
}
}