use super::models::{RANSModel as RANSModelTrait, TurbulenceConstants, TurbulenceModel};
use crate::error::IntegrateResult;
use scirs2_core::ndarray::{Array2, Array3};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RANSModel {
KEpsilon,
KOmega,
KOmegaSST,
ReynoldsStress,
}
#[derive(Debug, Clone)]
pub struct RANSSolver {
pub nx: usize,
pub ny: usize,
pub turbulence_model: RANSModel,
pub reynolds_number: f64,
pub relaxation_factor: f64,
pub constants: TurbulenceConstants,
}
#[derive(Debug, Clone)]
pub struct RANSState {
pub mean_velocity: Vec<Array2<f64>>,
pub mean_pressure: Array2<f64>,
pub turbulent_kinetic_energy: Array2<f64>,
pub dissipation_rate: Array2<f64>,
pub specific_dissipation_rate: Option<Array2<f64>>,
pub dx: f64,
pub dy: f64,
}
impl RANSSolver {
pub fn new(nx: usize, ny: usize, turbulence_model: RANSModel, reynoldsnumber: f64) -> Self {
Self {
nx,
ny,
turbulence_model,
reynolds_number: reynoldsnumber,
relaxation_factor: 0.7,
constants: TurbulenceConstants::default(),
}
}
pub fn solve_rans(
&self,
initial_state: RANSState,
max_iterations: usize,
tolerance: f64,
) -> IntegrateResult<RANSState> {
let mut state = initial_state;
for _iteration in 0..max_iterations {
let old_residual = self.compute_residual(&state)?;
state = self.update_turbulence_quantities(&state)?;
state = self.update_mean_flow(&state)?;
state = Self::apply_rans_boundary_conditions(state)?;
let new_residual = self.compute_residual(&state)?;
if (old_residual - new_residual).abs() < tolerance {
break;
}
}
Ok(state)
}
fn update_turbulence_quantities(&self, state: &RANSState) -> IntegrateResult<RANSState> {
match self.turbulence_model {
RANSModel::KEpsilon => self.update_k_epsilon(state),
RANSModel::KOmega => self.update_k_omega(state),
RANSModel::KOmegaSST => self.update_k_omega_sst(state),
RANSModel::ReynoldsStress => self.update_reynolds_stress(state),
}
}
fn update_k_epsilon(&self, state: &RANSState) -> IntegrateResult<RANSState> {
let mut new_state = state.clone();
for i in 1..(self.nx - 1) {
for j in 1..(self.ny - 1) {
let k = state.turbulent_kinetic_energy[[i, j]].max(1e-10);
let epsilon = state.dissipation_rate[[i, j]].max(1e-10);
let nu_t = self.constants.c_mu * k * k / epsilon;
let production = self.compute_production_k_epsilon(state, i, j, nu_t)?;
let dk_dt = production - epsilon;
new_state.turbulent_kinetic_energy[[i, j]] =
(k + self.relaxation_factor * dk_dt).max(1e-10);
let depsilon_dt = self.constants.c_1 * epsilon / k * production
- self.constants.c_2 * epsilon * epsilon / k;
new_state.dissipation_rate[[i, j]] =
(epsilon + self.relaxation_factor * depsilon_dt).max(1e-10);
}
}
Ok(new_state)
}
fn update_k_omega(&self, state: &RANSState) -> IntegrateResult<RANSState> {
let mut new_state = state.clone();
let beta_star = 0.09;
let alpha = 5.0 / 9.0;
let beta = 3.0 / 40.0;
for i in 1..(self.nx - 1) {
for j in 1..(self.ny - 1) {
let k = state.turbulent_kinetic_energy[[i, j]].max(1e-10);
let omega = state
.specific_dissipation_rate
.as_ref()
.map(|arr| arr[[i, j]])
.unwrap_or(state.dissipation_rate[[i, j]] / k)
.max(1e-10);
let nu_t = k / omega;
let production = self.compute_production_k_omega(state, i, j, nu_t)?;
let dk_dt = production - beta_star * k * omega;
new_state.turbulent_kinetic_energy[[i, j]] =
(k + self.relaxation_factor * dk_dt).max(1e-10);
let domega_dt = alpha * omega / k * production - beta * omega * omega;
if let Some(ref mut omega_arr) = new_state.specific_dissipation_rate {
omega_arr[[i, j]] = (omega + self.relaxation_factor * domega_dt).max(1e-10);
} else {
new_state.specific_dissipation_rate =
Some(Array2::from_elem((self.nx, self.ny), omega));
}
}
}
Ok(new_state)
}
fn update_k_omega_sst(&self, state: &RANSState) -> IntegrateResult<RANSState> {
let f1 = self.compute_sst_blending_function(state)?;
let k_omega_state = self.update_k_omega(state)?;
let k_epsilon_state = self.update_k_epsilon(state)?;
let mut new_state = state.clone();
for i in 0..self.nx {
for j in 0..self.ny {
let blend = f1[[i, j]];
new_state.turbulent_kinetic_energy[[i, j]] = blend
* k_omega_state.turbulent_kinetic_energy[[i, j]]
+ (1.0 - blend) * k_epsilon_state.turbulent_kinetic_energy[[i, j]];
new_state.dissipation_rate[[i, j]] = blend * k_omega_state.dissipation_rate[[i, j]]
+ (1.0 - blend) * k_epsilon_state.dissipation_rate[[i, j]];
}
}
Ok(new_state)
}
fn update_reynolds_stress(&self, state: &RANSState) -> IntegrateResult<RANSState> {
let mut new_state = state.clone();
for i in 1..(self.nx - 1) {
for j in 1..(self.ny - 1) {
let k = state.turbulent_kinetic_energy[[i, j]].max(1e-10);
let epsilon = state.dissipation_rate[[i, j]].max(1e-10);
let s11 = self.compute_strain_component(state, i, j, 0, 0)?;
let s12 = self.compute_strain_component(state, i, j, 0, 1)?;
let s22 = self.compute_strain_component(state, i, j, 1, 1)?;
let c1 = 1.8;
let time_scale = k / epsilon;
let tau_11 = (2.0 / 3.0) * k - c1 * time_scale * k * s11;
let tau_12 = -c1 * time_scale * k * s12;
let tau_22 = (2.0 / 3.0) * k - c1 * time_scale * k * s22;
let production = tau_11 * s11 + 2.0 * tau_12 * s12 + tau_22 * s22;
let dk_dt = production - epsilon;
new_state.turbulent_kinetic_energy[[i, j]] =
(k + self.relaxation_factor * dk_dt).max(1e-10);
let depsilon_dt = self.constants.c_1 * epsilon / k * production
- self.constants.c_2 * epsilon * epsilon / k;
new_state.dissipation_rate[[i, j]] =
(epsilon + self.relaxation_factor * depsilon_dt).max(1e-10);
}
}
Ok(new_state)
}
fn compute_production_k_epsilon(
&self,
state: &RANSState,
i: usize,
j: usize,
nu_t: f64,
) -> IntegrateResult<f64> {
let s11 = self.compute_strain_component(state, i, j, 0, 0)?;
let s12 = self.compute_strain_component(state, i, j, 0, 1)?;
let s22 = self.compute_strain_component(state, i, j, 1, 1)?;
let production = 2.0 * nu_t * (s11 * s11 + 2.0 * s12 * s12 + s22 * s22);
Ok(production)
}
fn compute_production_k_omega(
&self,
state: &RANSState,
i: usize,
j: usize,
nu_t: f64,
) -> IntegrateResult<f64> {
self.compute_production_k_epsilon(state, i, j, nu_t)
}
fn compute_strain_component(
&self,
state: &RANSState,
i: usize,
j: usize,
comp_i: usize,
comp_j: usize,
) -> IntegrateResult<f64> {
if i == 0 || i >= self.nx - 1 || j == 0 || j >= self.ny - 1 {
return Ok(0.0);
}
let dx = state.dx;
let dy = state.dy;
let strain = match (comp_i, comp_j) {
(0, 0) => {
(state.mean_velocity[0][[i + 1, j]] - state.mean_velocity[0][[i - 1, j]])
/ (2.0 * dx)
}
(1, 1) => {
(state.mean_velocity[1][[i, j + 1]] - state.mean_velocity[1][[i, j - 1]])
/ (2.0 * dy)
}
(0, 1) | (1, 0) => {
let dudy = (state.mean_velocity[0][[i, j + 1]]
- state.mean_velocity[0][[i, j - 1]])
/ (2.0 * dy);
let dvdx = (state.mean_velocity[1][[i + 1, j]]
- state.mean_velocity[1][[i - 1, j]])
/ (2.0 * dx);
0.5 * (dudy + dvdx)
}
_ => 0.0,
};
Ok(strain)
}
fn compute_sst_blending_function(&self, state: &RANSState) -> IntegrateResult<Array2<f64>> {
let mut f1 = Array2::zeros((self.nx, self.ny));
for i in 1..self.nx - 1 {
for j in 1..self.ny - 1 {
let k = state.turbulent_kinetic_energy[[i, j]].max(1e-10);
let epsilon = state.dissipation_rate[[i, j]].max(1e-10);
let omega = state
.specific_dissipation_rate
.as_ref()
.map(|arr| arr[[i, j]])
.unwrap_or(epsilon / k)
.max(1e-10);
let y_plus = ((i.min(self.nx - 1 - i) + j.min(self.ny - 1 - j)) as f64).max(1.0);
let sqrt_k = k.sqrt();
let arg1_1 = sqrt_k / (0.09 * omega * y_plus);
let arg1_2 = 500.0 / (self.reynolds_number * y_plus * y_plus * omega);
let arg1 = arg1_1.min(arg1_2);
f1[[i, j]] = (-4.0 * arg1).exp().tanh();
}
}
Ok(f1)
}
fn update_mean_flow(&self, state: &RANSState) -> IntegrateResult<RANSState> {
let mut new_state = state.clone();
for comp in 0..2 {
for i in 1..self.nx - 1 {
for j in 1..self.ny - 1 {
let k = state.turbulent_kinetic_energy[[i, j]];
let epsilon = state.dissipation_rate[[i, j]].max(1e-10);
let nu_t = self.constants.c_mu * k * k / epsilon;
let viscous_term = self.compute_viscous_term(state, i, j, comp, nu_t)?;
let pressure_gradient = self.compute_pressure_gradient(state, i, j, comp)?;
let velocity_update =
self.relaxation_factor * (viscous_term - pressure_gradient);
new_state.mean_velocity[comp][[i, j]] += velocity_update;
}
}
}
Ok(new_state)
}
fn compute_viscous_term(
&self,
state: &RANSState,
i: usize,
j: usize,
comp: usize,
nu_t: f64,
) -> IntegrateResult<f64> {
let dx = state.dx;
let dy = state.dy;
let nu_total = 1.0 / self.reynolds_number + nu_t;
let d2u_dx2 = (state.mean_velocity[comp][[i + 1, j]]
- 2.0 * state.mean_velocity[comp][[i, j]]
+ state.mean_velocity[comp][[i - 1, j]])
/ (dx * dx);
let d2u_dy2 = (state.mean_velocity[comp][[i, j + 1]]
- 2.0 * state.mean_velocity[comp][[i, j]]
+ state.mean_velocity[comp][[i, j - 1]])
/ (dy * dy);
Ok(nu_total * (d2u_dx2 + d2u_dy2))
}
fn compute_pressure_gradient(
&self,
state: &RANSState,
i: usize,
j: usize,
comp: usize,
) -> IntegrateResult<f64> {
let dx = state.dx;
let dy = state.dy;
let gradient = match comp {
0 => {
(state.mean_pressure[[i + 1, j]] - state.mean_pressure[[i - 1, j]]) / (2.0 * dx)
}
1 => {
(state.mean_pressure[[i, j + 1]] - state.mean_pressure[[i, j - 1]]) / (2.0 * dy)
}
_ => 0.0,
};
Ok(gradient)
}
fn apply_rans_boundary_conditions(mut state: RANSState) -> IntegrateResult<RANSState> {
let (nx, ny) = state.mean_velocity[0].dim();
for i in 0..nx {
state.mean_velocity[0][[i, 0]] = 0.0; state.mean_velocity[1][[i, 0]] = 0.0; state.mean_velocity[0][[i, ny - 1]] = 0.0;
state.mean_velocity[1][[i, ny - 1]] = 0.0;
state.turbulent_kinetic_energy[[i, 0]] = 0.0;
state.turbulent_kinetic_energy[[i, ny - 1]] = 0.0;
state.dissipation_rate[[i, 0]] = 1e6; state.dissipation_rate[[i, ny - 1]] = 1e6;
if let Some(ref mut omega) = state.specific_dissipation_rate {
omega[[i, 0]] = 1e6;
omega[[i, ny - 1]] = 1e6;
}
}
for j in 0..ny {
state.mean_velocity[0][[0, j]] = 0.0;
state.mean_velocity[1][[0, j]] = 0.0;
state.mean_velocity[0][[nx - 1, j]] = 0.0;
state.mean_velocity[1][[nx - 1, j]] = 0.0;
state.turbulent_kinetic_energy[[0, j]] = 0.0;
state.turbulent_kinetic_energy[[nx - 1, j]] = 0.0;
state.dissipation_rate[[0, j]] = 1e6;
state.dissipation_rate[[nx - 1, j]] = 1e6;
if let Some(ref mut omega) = state.specific_dissipation_rate {
omega[[0, j]] = 1e6;
omega[[nx - 1, j]] = 1e6;
}
}
Ok(state)
}
fn compute_residual(&self, state: &RANSState) -> IntegrateResult<f64> {
let mut residual = 0.0;
for i in 0..self.nx {
for j in 0..self.ny {
residual += state.mean_velocity[0][[i, j]].abs();
residual += state.mean_velocity[1][[i, j]].abs();
residual += state.turbulent_kinetic_energy[[i, j]].abs();
residual += state.dissipation_rate[[i, j]].abs();
}
}
Ok(residual / (self.nx * self.ny) as f64)
}
}
impl RANSState {
pub fn new(nx: usize, ny: usize, dx: f64, dy: f64) -> Self {
Self {
mean_velocity: vec![Array2::zeros((nx, ny)), Array2::zeros((nx, ny))],
mean_pressure: Array2::zeros((nx, ny)),
turbulent_kinetic_energy: Array2::from_elem((nx, ny), 1e-6),
dissipation_rate: Array2::from_elem((nx, ny), 1e-4),
specific_dissipation_rate: None,
dx,
dy,
}
}
pub fn with_omega(mut self) -> Self {
let (nx, ny) = self.mean_velocity[0].dim();
self.specific_dissipation_rate = Some(Array2::from_elem((nx, ny), 1e-3));
self
}
pub fn lid_driven_cavity(nx: usize, ny: usize, dx: f64, dy: f64, lidvelocity: f64) -> Self {
let mut state = Self::new(nx, ny, dx, dy);
for i in 0..nx {
state.mean_velocity[0][[i, ny - 1]] = lidvelocity;
}
let turbulence_intensity = 0.05_f64;
let k_inlet: f64 = 1.5 * (turbulence_intensity * lidvelocity).powi(2);
let epsilon_inlet: f64 = 0.09 * k_inlet.powf(1.5_f64) / (0.1 * dx);
state.turbulent_kinetic_energy.fill(k_inlet);
state.dissipation_rate.fill(epsilon_inlet);
state
}
}
impl TurbulenceModel for RANSSolver {
fn model_type(&self) -> super::models::TurbulenceModelType {
match self.turbulence_model {
RANSModel::KEpsilon => super::models::TurbulenceModelType::KEpsilon,
RANSModel::KOmega => super::models::TurbulenceModelType::KOmega,
RANSModel::KOmegaSST => super::models::TurbulenceModelType::KOmegaSST,
RANSModel::ReynoldsStress => super::models::TurbulenceModelType::ReynoldsStress,
}
}
fn constants(&self) -> &TurbulenceConstants {
&self.constants
}
fn compute_turbulent_viscosity(
&self,
k: &Array3<f64>,
epsilon_or_omega: &Array3<f64>,
) -> IntegrateResult<Array3<f64>> {
let (nx, ny, nz) = k.dim();
let mut mu_t = Array3::zeros((nx, ny, nz));
match self.turbulence_model {
RANSModel::KEpsilon => {
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_or_omega[[i, j, k_idx]].max(1e-10);
}
}
}
}
RANSModel::KOmega | RANSModel::KOmegaSST => {
for i in 0..nx {
for j in 0..ny {
for k_idx in 0..nz {
mu_t[[i, j, k_idx]] =
k[[i, j, k_idx]] / epsilon_or_omega[[i, j, k_idx]].max(1e-10);
}
}
}
}
RANSModel::ReynoldsStress => {
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_or_omega[[i, j, k_idx]].max(1e-10);
}
}
}
}
}
Ok(mu_t)
}
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();
Ok(Array3::zeros((nx, ny, nz)))
}
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;
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rans_solver_creation() {
let solver = RANSSolver::new(10, 10, RANSModel::KEpsilon, 1000.0);
assert_eq!(solver.nx, 10);
assert_eq!(solver.turbulence_model, RANSModel::KEpsilon);
assert_eq!(solver.reynolds_number, 1000.0);
}
#[test]
fn test_rans_state_creation() {
let state = RANSState::new(8, 8, 0.1, 0.1);
assert_eq!(state.mean_velocity.len(), 2);
assert_eq!(state.mean_velocity[0].dim(), (8, 8));
assert_eq!(state.dx, 0.1);
assert!(state.specific_dissipation_rate.is_none());
}
#[test]
fn test_rans_state_with_omega() {
let state = RANSState::new(6, 6, 0.1, 0.1).with_omega();
assert!(state.specific_dissipation_rate.is_some());
if let Some(omega) = &state.specific_dissipation_rate {
assert_eq!(omega.dim(), (6, 6));
}
}
#[test]
fn test_lid_driven_cavity_initialization() {
let state = RANSState::lid_driven_cavity(10, 10, 0.1, 0.1, 1.0);
assert_eq!(state.mean_velocity[0][[5, 9]], 1.0);
assert_eq!(state.mean_velocity[0][[0, 0]], 0.0);
assert!(state.turbulent_kinetic_energy[[5, 5]] > 0.0);
assert!(state.dissipation_rate[[5, 5]] > 0.0);
}
#[test]
fn test_rans_model_enum() {
assert_eq!(RANSModel::KEpsilon, RANSModel::KEpsilon);
assert_ne!(RANSModel::KEpsilon, RANSModel::KOmega);
}
}