Initial commit
This commit is contained in:
@@ -0,0 +1,783 @@
|
||||
//! SIMPLE (Semi-Implicit Method for Pressure Linked Equations) algorithm
|
||||
//!
|
||||
//! The SIMPLE algorithm is a widely-used iterative solution method for
|
||||
//! the incompressible Navier-Stokes equations. It uses a pressure-velocity
|
||||
//! coupling approach to handle the incompressibility constraint.
|
||||
//!
|
||||
//! Algorithm steps:
|
||||
//! 1. Solve momentum equations with guessed pressure field → u*, v*
|
||||
//! 2. Solve pressure correction equation → p'
|
||||
//! 3. Correct velocities and pressure
|
||||
//! 4. Check convergence and iterate
|
||||
|
||||
use super::{BoundaryConditions, FlowField, IncompressibleSolver, SolverResult};
|
||||
use crate::turbulence::{KEpsilonModel, KEpsilonVariant, TurbulenceModel, TurbulenceState};
|
||||
use crate::{CfdConfig, CfdError, CfdResult};
|
||||
use async_trait::async_trait;
|
||||
use nalgebra::{DMatrix, DVector, Vector3};
|
||||
use std::time::Instant;
|
||||
|
||||
/// Parameters for SIMPLE algorithm
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SimpleParameters {
|
||||
/// Under-relaxation factor for pressure (typically 0.2-0.8)
|
||||
pub pressure_relaxation: f64,
|
||||
/// Under-relaxation factor for velocity (typically 0.5-0.8)
|
||||
pub velocity_relaxation: f64,
|
||||
/// Maximum number of iterations
|
||||
pub max_iterations: usize,
|
||||
/// Convergence tolerance for residuals
|
||||
pub tolerance: f64,
|
||||
/// Time step for transient problems
|
||||
pub time_step: f64,
|
||||
/// Maximum Courant number for stability
|
||||
pub max_courant: f64,
|
||||
/// Enable turbulence modeling
|
||||
pub use_turbulence: bool,
|
||||
}
|
||||
|
||||
impl SimpleParameters {
|
||||
/// Create new SIMPLE parameters with default values
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Set pressure under-relaxation factor
|
||||
#[must_use]
|
||||
pub fn with_pressure_relaxation(mut self, factor: f64) -> Self {
|
||||
self.pressure_relaxation = factor.max(0.0); // Ensure non-negative
|
||||
self
|
||||
}
|
||||
|
||||
/// Set velocity under-relaxation factor
|
||||
#[must_use]
|
||||
pub fn with_velocity_relaxation(mut self, factor: f64) -> Self {
|
||||
self.velocity_relaxation = factor.max(0.0);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set maximum iterations
|
||||
#[must_use]
|
||||
pub fn with_max_iterations(mut self, max_iter: usize) -> Self {
|
||||
self.max_iterations = max_iter;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set convergence tolerance
|
||||
#[must_use]
|
||||
pub fn with_tolerance(mut self, tol: f64) -> Self {
|
||||
self.tolerance = tol.abs();
|
||||
self
|
||||
}
|
||||
|
||||
/// Set time step
|
||||
#[must_use]
|
||||
pub fn with_time_step(mut self, dt: f64) -> Self {
|
||||
self.time_step = dt.abs();
|
||||
self
|
||||
}
|
||||
|
||||
/// Validate parameters
|
||||
pub fn validate(&self) -> CfdResult<()> {
|
||||
if self.pressure_relaxation <= 0.0 || self.pressure_relaxation > 1.0 {
|
||||
return Err(CfdError::invalid_parameter(
|
||||
"Pressure relaxation factor must be in (0, 1]",
|
||||
));
|
||||
}
|
||||
|
||||
if self.velocity_relaxation <= 0.0 || self.velocity_relaxation > 1.0 {
|
||||
return Err(CfdError::invalid_parameter(
|
||||
"Velocity relaxation factor must be in (0, 1]",
|
||||
));
|
||||
}
|
||||
|
||||
if self.tolerance <= 0.0 {
|
||||
return Err(CfdError::invalid_parameter("Tolerance must be positive"));
|
||||
}
|
||||
|
||||
if self.time_step <= 0.0 {
|
||||
return Err(CfdError::invalid_parameter("Time step must be positive"));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SimpleParameters {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
pressure_relaxation: 0.3,
|
||||
velocity_relaxation: 0.7,
|
||||
max_iterations: 1000,
|
||||
tolerance: 1e-6,
|
||||
time_step: 0.001,
|
||||
max_courant: 1.0,
|
||||
use_turbulence: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Result of SIMPLE algorithm execution
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SimpleResult {
|
||||
/// Base solver result information
|
||||
pub solver_result: SolverResult,
|
||||
/// Pressure correction iterations per SIMPLE iteration
|
||||
pub pressure_iterations: Vec<usize>,
|
||||
/// Final mass residual
|
||||
pub mass_residual: f64,
|
||||
/// Final momentum residual
|
||||
pub momentum_residual: f64,
|
||||
}
|
||||
|
||||
/// SIMPLE algorithm implementation
|
||||
pub struct SimpleSolver {
|
||||
/// CFD configuration
|
||||
config: CfdConfig,
|
||||
/// SIMPLE parameters
|
||||
parameters: SimpleParameters,
|
||||
/// Linear algebra workspace
|
||||
workspace: LinearAlgebraWorkspace,
|
||||
/// Turbulence model (optional)
|
||||
turbulence_model: Option<KEpsilonModel>,
|
||||
}
|
||||
|
||||
/// Workspace for linear algebra operations
|
||||
struct LinearAlgebraWorkspace {
|
||||
/// Matrix for pressure correction equation
|
||||
pressure_matrix: Option<DMatrix<f64>>,
|
||||
/// RHS vector for pressure correction
|
||||
pressure_rhs: Option<DVector<f64>>,
|
||||
/// Solution vector for pressure correction
|
||||
pressure_solution: Option<DVector<f64>>,
|
||||
/// Momentum equation coefficients
|
||||
momentum_coefficients: Option<MomentumCoefficients>,
|
||||
}
|
||||
|
||||
/// Coefficients for momentum equations discretization
|
||||
#[derive(Debug, Clone)]
|
||||
struct MomentumCoefficients {
|
||||
/// Central coefficient (diagonal)
|
||||
pub ap: DMatrix<f64>,
|
||||
/// East neighbor coefficient
|
||||
pub ae: DMatrix<f64>,
|
||||
/// West neighbor coefficient
|
||||
pub aw: DMatrix<f64>,
|
||||
/// North neighbor coefficient
|
||||
pub an: DMatrix<f64>,
|
||||
/// South neighbor coefficient
|
||||
pub as_: DMatrix<f64>,
|
||||
/// Source term
|
||||
pub su: DMatrix<f64>,
|
||||
}
|
||||
|
||||
impl SimpleSolver {
|
||||
/// Create new SIMPLE solver
|
||||
pub fn new(config: CfdConfig, parameters: SimpleParameters) -> CfdResult<Self> {
|
||||
config.validate()?;
|
||||
parameters.validate()?;
|
||||
|
||||
// Initialize turbulence model if enabled (will be properly sized when flow field is available)
|
||||
let turbulence_model = if parameters.use_turbulence {
|
||||
None // Will be initialized when flow field dimensions are known
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
config,
|
||||
parameters,
|
||||
workspace: LinearAlgebraWorkspace {
|
||||
pressure_matrix: None,
|
||||
pressure_rhs: None,
|
||||
pressure_solution: None,
|
||||
momentum_coefficients: None,
|
||||
},
|
||||
turbulence_model,
|
||||
})
|
||||
}
|
||||
|
||||
/// Solve one SIMPLE iteration
|
||||
pub async fn solve_simple_iteration(
|
||||
&mut self,
|
||||
flow_field: &mut FlowField,
|
||||
boundary_conditions: &BoundaryConditions,
|
||||
dt: f64,
|
||||
) -> CfdResult<(f64, f64)> {
|
||||
// Step 1: Solve momentum equations with current pressure field
|
||||
self.momentum_prediction_step(flow_field, dt).await?;
|
||||
|
||||
// Step 2: Solve pressure correction equation
|
||||
let mass_residual = self.pressure_correction_step(flow_field).await?;
|
||||
|
||||
// Step 3: Correct velocities
|
||||
self.velocity_correction_step(flow_field).await?;
|
||||
|
||||
// Step 4: Update pressure field
|
||||
self.pressure_update_step(flow_field).await?;
|
||||
|
||||
// Step 5: Solve turbulence equations if enabled
|
||||
if self.parameters.use_turbulence {
|
||||
self.solve_turbulence(flow_field, dt).await?;
|
||||
}
|
||||
|
||||
// Step 6: Apply boundary conditions
|
||||
flow_field.apply_boundary_conditions(boundary_conditions)?;
|
||||
|
||||
// Step 7: Apply under-relaxation
|
||||
flow_field.apply_velocity_relaxation(self.parameters.velocity_relaxation)?;
|
||||
flow_field.apply_pressure_relaxation(self.parameters.pressure_relaxation)?;
|
||||
|
||||
// Compute momentum residual
|
||||
let momentum_residual = flow_field.compute_velocity_residual();
|
||||
|
||||
Ok((mass_residual, momentum_residual))
|
||||
}
|
||||
|
||||
/// Momentum prediction step: solve momentum equations with current pressure
|
||||
pub async fn momentum_prediction_step(
|
||||
&self,
|
||||
flow_field: &mut FlowField,
|
||||
dt: f64,
|
||||
) -> CfdResult<()> {
|
||||
let (_nx, _ny, dx, dy) = flow_field.grid_info();
|
||||
let rho = self.config.density;
|
||||
let mu = self.config.viscosity;
|
||||
|
||||
// Copy current velocities to old values for time derivatives
|
||||
flow_field.update_old_values();
|
||||
|
||||
// Solve u-momentum equation
|
||||
self.solve_u_momentum(flow_field, dt, rho, mu, dx, dy)
|
||||
.await?;
|
||||
|
||||
// Solve v-momentum equation
|
||||
self.solve_v_momentum(flow_field, dt, rho, mu, dx, dy)
|
||||
.await?;
|
||||
|
||||
// Store predicted velocities
|
||||
flow_field.copy_to_starred();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Solve u-momentum equation
|
||||
async fn solve_u_momentum(
|
||||
&self,
|
||||
flow_field: &mut FlowField,
|
||||
dt: f64,
|
||||
rho: f64,
|
||||
mu: f64,
|
||||
dx: f64,
|
||||
dy: f64,
|
||||
) -> CfdResult<()> {
|
||||
let (nx, ny, _, _) = flow_field.grid_info();
|
||||
|
||||
// For each u-velocity point (face-centered)
|
||||
for j in 1..ny - 1 {
|
||||
for i in 1..nx {
|
||||
// u goes from 1 to nx-1 for interior
|
||||
// Discretize u-momentum equation at (i, j)
|
||||
let coeffs =
|
||||
self.compute_u_momentum_coefficients(flow_field, i, j, dt, rho, mu, dx, dy)?;
|
||||
|
||||
// Solve for new u velocity using Gauss-Seidel
|
||||
let u_new = (coeffs.source
|
||||
+ coeffs.east * flow_field.u[(j, i + 1)]
|
||||
+ coeffs.west * flow_field.u[(j, i - 1)]
|
||||
+ coeffs.north * flow_field.u[(j + 1, i)]
|
||||
+ coeffs.south * flow_field.u[(j - 1, i)])
|
||||
/ coeffs.center;
|
||||
|
||||
flow_field.u[(j, i)] = u_new;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Solve v-momentum equation
|
||||
async fn solve_v_momentum(
|
||||
&self,
|
||||
flow_field: &mut FlowField,
|
||||
dt: f64,
|
||||
rho: f64,
|
||||
mu: f64,
|
||||
dx: f64,
|
||||
dy: f64,
|
||||
) -> CfdResult<()> {
|
||||
let (nx, ny, _, _) = flow_field.grid_info();
|
||||
|
||||
// For each v-velocity point (face-centered)
|
||||
for j in 1..ny {
|
||||
// v goes from 1 to ny-1 for interior
|
||||
for i in 1..nx - 1 {
|
||||
// Discretize v-momentum equation at (i, j)
|
||||
let coeffs =
|
||||
self.compute_v_momentum_coefficients(flow_field, i, j, dt, rho, mu, dx, dy)?;
|
||||
|
||||
// Solve for new v velocity using Gauss-Seidel
|
||||
let v_new = (coeffs.source
|
||||
+ coeffs.east * flow_field.v[(j, i + 1)]
|
||||
+ coeffs.west * flow_field.v[(j, i - 1)]
|
||||
+ coeffs.north * flow_field.v[(j + 1, i)]
|
||||
+ coeffs.south * flow_field.v[(j - 1, i)])
|
||||
/ coeffs.center;
|
||||
|
||||
flow_field.v[(j, i)] = v_new;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Pressure correction step: solve pressure Poisson equation
|
||||
pub async fn pressure_correction_step(&self, flow_field: &mut FlowField) -> CfdResult<f64> {
|
||||
let (nx, ny, dx, dy) = flow_field.grid_info();
|
||||
let rho = self.config.density;
|
||||
|
||||
// Setup pressure correction equation: ∇²p' = ρ/Δt * ∇·u*
|
||||
for j in 1..ny - 1 {
|
||||
for i in 1..nx - 1 {
|
||||
// Compute mass source (continuity equation residual)
|
||||
let mass_source = self.compute_mass_source(flow_field, i, j, dx, dy, rho)?;
|
||||
flow_field.sp[(j, i)] = mass_source;
|
||||
}
|
||||
}
|
||||
|
||||
// Solve pressure correction equation using Gauss-Seidel
|
||||
let mut max_residual = 0.0;
|
||||
for _iteration in 0..100 {
|
||||
// Inner pressure correction iterations
|
||||
let mut residual = 0.0;
|
||||
|
||||
for j in 1..ny - 1 {
|
||||
for i in 1..nx - 1 {
|
||||
let coeffs = self.compute_pressure_coefficients(dx, dy)?;
|
||||
|
||||
let p_new = (flow_field.sp[(j, i)]
|
||||
+ coeffs.east * flow_field.p_prime[(j, i + 1)]
|
||||
+ coeffs.west * flow_field.p_prime[(j, i - 1)]
|
||||
+ coeffs.north * flow_field.p_prime[(j + 1, i)]
|
||||
+ coeffs.south * flow_field.p_prime[(j - 1, i)])
|
||||
/ coeffs.center;
|
||||
|
||||
let correction = p_new - flow_field.p_prime[(j, i)];
|
||||
residual += correction * correction;
|
||||
flow_field.p_prime[(j, i)] = p_new;
|
||||
}
|
||||
}
|
||||
|
||||
residual = residual.sqrt();
|
||||
max_residual = residual;
|
||||
|
||||
if residual < 1e-8 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(max_residual)
|
||||
}
|
||||
|
||||
/// Velocity correction step: correct velocities with pressure correction
|
||||
pub async fn velocity_correction_step(&self, flow_field: &mut FlowField) -> CfdResult<()> {
|
||||
let (nx, ny, dx, dy) = flow_field.grid_info();
|
||||
let rho = self.config.density;
|
||||
|
||||
// Correct u-velocities
|
||||
for j in 1..ny - 1 {
|
||||
for i in 1..nx {
|
||||
if i > 0 && i < nx {
|
||||
let dp_dx = (flow_field.p_prime[(j, i)] - flow_field.p_prime[(j, i - 1)]) / dx;
|
||||
let ap_u =
|
||||
self.compute_u_momentum_center_coefficient(flow_field, i, j, dx, dy, rho)?;
|
||||
flow_field.u[(j, i)] = flow_field.u_star[(j, i)] - (dx * dy / ap_u) * dp_dx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Correct v-velocities
|
||||
for j in 1..ny {
|
||||
for i in 1..nx - 1 {
|
||||
if j > 0 && j < ny {
|
||||
let dp_dy = (flow_field.p_prime[(j, i)] - flow_field.p_prime[(j - 1, i)]) / dy;
|
||||
let ap_v =
|
||||
self.compute_v_momentum_center_coefficient(flow_field, i, j, dx, dy, rho)?;
|
||||
flow_field.v[(j, i)] = flow_field.v_star[(j, i)] - (dx * dy / ap_v) * dp_dy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Pressure update step: add pressure correction to pressure
|
||||
pub async fn pressure_update_step(&self, flow_field: &mut FlowField) -> CfdResult<()> {
|
||||
let (nx, ny, _, _) = flow_field.grid_info();
|
||||
|
||||
for j in 0..ny {
|
||||
for i in 0..nx {
|
||||
flow_field.p[(j, i)] +=
|
||||
self.parameters.pressure_relaxation * flow_field.p_prime[(j, i)];
|
||||
flow_field.p_prime[(j, i)] = 0.0; // Reset pressure correction
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Solve turbulence model equations
|
||||
async fn solve_turbulence(&mut self, flow_field: &FlowField, dt: f64) -> CfdResult<()> {
|
||||
if self.parameters.use_turbulence {
|
||||
// Initialize turbulence model if not already done
|
||||
let (nx, ny, dx, dy) = flow_field.grid_info();
|
||||
let n_cells = nx * ny;
|
||||
|
||||
if self.turbulence_model.is_none() {
|
||||
self.turbulence_model =
|
||||
Some(KEpsilonModel::new(KEpsilonVariant::Standard, n_cells));
|
||||
}
|
||||
|
||||
let turbulence_model = self.turbulence_model.as_mut().unwrap();
|
||||
|
||||
// Convert velocity field to Vector3 format
|
||||
let mut velocity_vec = Vec::with_capacity(n_cells);
|
||||
for j in 0..ny {
|
||||
for i in 0..nx {
|
||||
let u = if i < nx && j < ny {
|
||||
flow_field.u[(j, i)]
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
let v = if i < nx && j < ny {
|
||||
flow_field.v[(j, i)]
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
velocity_vec.push(Vector3::new(u, v, 0.0)); // 2D case, w=0
|
||||
}
|
||||
}
|
||||
|
||||
// Simplified velocity gradients (zero for now)
|
||||
let velocity_gradients = vec![[[0.0; 3]; 3]; n_cells];
|
||||
|
||||
// Create pressure vector
|
||||
let mut pressure = DVector::zeros(n_cells);
|
||||
for j in 0..ny {
|
||||
for i in 0..nx {
|
||||
if i < nx && j < ny {
|
||||
pressure[j * nx + i] = flow_field.p[(j, i)];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let turbulence_state = TurbulenceState {
|
||||
velocity: velocity_vec,
|
||||
velocity_gradients,
|
||||
pressure,
|
||||
turbulent_ke: None, // Will be initialized by model
|
||||
epsilon: None, // Will be initialized by model
|
||||
omega: None,
|
||||
wall_distance: DVector::from_element(n_cells, 1.0), // Simplified
|
||||
cell_volumes: DVector::from_element(n_cells, dx * dy), // 2D cell volume
|
||||
molecular_viscosity: self.config.viscosity / self.config.density, // kinematic viscosity
|
||||
density: self.config.density,
|
||||
};
|
||||
|
||||
// Update turbulence model with new state
|
||||
turbulence_model.update(&turbulence_state, dt)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Compute effective viscosity (molecular + turbulent)
|
||||
fn compute_effective_viscosity(
|
||||
&self,
|
||||
flow_field: &FlowField,
|
||||
i: usize,
|
||||
j: usize,
|
||||
mu: f64,
|
||||
) -> f64 {
|
||||
if let Some(ref turbulence_model) = self.turbulence_model {
|
||||
// Get turbulent viscosity from the model
|
||||
let (nx, _ny, _, _) = flow_field.grid_info();
|
||||
let cell_idx = j * nx + i;
|
||||
if cell_idx < turbulence_model.nu_t.len() {
|
||||
let nu_t = turbulence_model.nu_t[cell_idx]; // kinematic turbulent viscosity
|
||||
let mu_t = nu_t * self.config.density; // convert to dynamic viscosity
|
||||
mu + mu_t
|
||||
} else {
|
||||
mu
|
||||
}
|
||||
} else {
|
||||
mu
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute coefficients for u-momentum equation
|
||||
fn compute_u_momentum_coefficients(
|
||||
&self,
|
||||
flow_field: &FlowField,
|
||||
i: usize,
|
||||
j: usize,
|
||||
dt: f64,
|
||||
rho: f64,
|
||||
mu: f64,
|
||||
dx: f64,
|
||||
dy: f64,
|
||||
) -> CfdResult<MomentumEquationCoeffs> {
|
||||
// Compute effective viscosity (molecular + turbulent)
|
||||
let mu_eff = self.compute_effective_viscosity(flow_field, i, j, mu);
|
||||
|
||||
// Diffusion coefficients using effective viscosity
|
||||
let gamma_e = mu_eff / dx;
|
||||
let gamma_w = mu_eff / dx;
|
||||
let gamma_n = mu_eff / dy;
|
||||
let gamma_s = mu_eff / dy;
|
||||
|
||||
// Convection coefficients (using upwind)
|
||||
let (u_center, v_center) = flow_field.get_velocity_at(i, j)?;
|
||||
let fe = rho * u_center * dy; // East face mass flux
|
||||
let fw = rho * u_center * dy; // West face mass flux
|
||||
let fn_ = rho * v_center * dx; // North face mass flux
|
||||
let fs = rho * v_center * dx; // South face mass flux
|
||||
|
||||
// Compute coefficients with upwind scheme
|
||||
let ae = gamma_e + f64::max(-fe, 0.0);
|
||||
let aw = gamma_w + f64::max(fw, 0.0);
|
||||
let an = gamma_n + f64::max(-fn_, 0.0);
|
||||
let as_ = gamma_s + f64::max(fs, 0.0);
|
||||
|
||||
// Time derivative coefficient
|
||||
let ap0 = rho * dx * dy / dt;
|
||||
|
||||
// Central coefficient
|
||||
let ap = ae + aw + an + as_ + ap0;
|
||||
|
||||
// Source term (pressure gradient + old time step)
|
||||
let pressure_gradient = -(flow_field.p[(j, i)] - flow_field.p[(j, i - 1)]) * dy;
|
||||
let time_term = ap0 * flow_field.u_old[(j, i)];
|
||||
let source = pressure_gradient + time_term;
|
||||
|
||||
Ok(MomentumEquationCoeffs {
|
||||
center: ap,
|
||||
east: ae,
|
||||
west: aw,
|
||||
north: an,
|
||||
south: as_,
|
||||
source,
|
||||
})
|
||||
}
|
||||
|
||||
/// Compute coefficients for v-momentum equation
|
||||
fn compute_v_momentum_coefficients(
|
||||
&self,
|
||||
flow_field: &FlowField,
|
||||
i: usize,
|
||||
j: usize,
|
||||
dt: f64,
|
||||
rho: f64,
|
||||
mu: f64,
|
||||
dx: f64,
|
||||
dy: f64,
|
||||
) -> CfdResult<MomentumEquationCoeffs> {
|
||||
// Compute effective viscosity (molecular + turbulent)
|
||||
let mu_eff = self.compute_effective_viscosity(flow_field, i, j, mu);
|
||||
|
||||
// Similar to u-momentum but for v-component
|
||||
let gamma_e = mu_eff / dx;
|
||||
let gamma_w = mu_eff / dx;
|
||||
let gamma_n = mu_eff / dy;
|
||||
let gamma_s = mu_eff / dy;
|
||||
|
||||
let (u_center, v_center) = flow_field.get_velocity_at(i, j)?;
|
||||
let fe = rho * u_center * dy;
|
||||
let fw = rho * u_center * dy;
|
||||
let fn_ = rho * v_center * dx;
|
||||
let fs = rho * v_center * dx;
|
||||
|
||||
let ae = gamma_e + f64::max(-fe, 0.0);
|
||||
let aw = gamma_w + f64::max(fw, 0.0);
|
||||
let an = gamma_n + f64::max(-fn_, 0.0);
|
||||
let as_ = gamma_s + f64::max(fs, 0.0);
|
||||
|
||||
let ap0 = rho * dx * dy / dt;
|
||||
let ap = ae + aw + an + as_ + ap0;
|
||||
|
||||
// Pressure gradient in y-direction
|
||||
let pressure_gradient = -(flow_field.p[(j, i)] - flow_field.p[(j - 1, i)]) * dx;
|
||||
let time_term = ap0 * flow_field.v_old[(j, i)];
|
||||
let source = pressure_gradient + time_term;
|
||||
|
||||
Ok(MomentumEquationCoeffs {
|
||||
center: ap,
|
||||
east: ae,
|
||||
west: aw,
|
||||
north: an,
|
||||
south: as_,
|
||||
source,
|
||||
})
|
||||
}
|
||||
|
||||
/// Compute mass source term for pressure correction equation
|
||||
fn compute_mass_source(
|
||||
&self,
|
||||
flow_field: &FlowField,
|
||||
i: usize,
|
||||
j: usize,
|
||||
dx: f64,
|
||||
dy: f64,
|
||||
rho: f64,
|
||||
) -> CfdResult<f64> {
|
||||
// Mass source = ρ * ∇·u*
|
||||
let u_e = flow_field.u_star[(j, i + 1)];
|
||||
let u_w = flow_field.u_star[(j, i)];
|
||||
let v_n = flow_field.v_star[(j + 1, i)];
|
||||
let v_s = flow_field.v_star[(j, i)];
|
||||
|
||||
let mass_flux_imbalance = rho * ((u_e - u_w) * dy + (v_n - v_s) * dx);
|
||||
|
||||
Ok(-mass_flux_imbalance) // Negative because we want ∇²p' = -∇·u*
|
||||
}
|
||||
|
||||
/// Compute coefficients for pressure correction equation
|
||||
fn compute_pressure_coefficients(&self, dx: f64, dy: f64) -> CfdResult<MomentumEquationCoeffs> {
|
||||
// Pressure correction equation: ∇²p' = S
|
||||
// Standard 5-point stencil with unit coefficients
|
||||
let ae = 1.0 / (dx * dx);
|
||||
let aw = 1.0 / (dx * dx);
|
||||
let an = 1.0 / (dy * dy);
|
||||
let as_ = 1.0 / (dy * dy);
|
||||
let ap = ae + aw + an + as_;
|
||||
|
||||
Ok(MomentumEquationCoeffs {
|
||||
center: ap,
|
||||
east: ae,
|
||||
west: aw,
|
||||
north: an,
|
||||
south: as_,
|
||||
source: 0.0, // Source is set separately
|
||||
})
|
||||
}
|
||||
|
||||
/// Compute center coefficient for u-momentum equation
|
||||
fn compute_u_momentum_center_coefficient(
|
||||
&self,
|
||||
_flow_field: &FlowField,
|
||||
_i: usize,
|
||||
_j: usize,
|
||||
dx: f64,
|
||||
dy: f64,
|
||||
rho: f64,
|
||||
) -> CfdResult<f64> {
|
||||
// Simplified calculation for velocity correction
|
||||
// This would be the diagonal coefficient from momentum discretization
|
||||
Ok(rho * dx * dy / self.parameters.time_step)
|
||||
}
|
||||
|
||||
/// Compute center coefficient for v-momentum equation
|
||||
fn compute_v_momentum_center_coefficient(
|
||||
&self,
|
||||
_flow_field: &FlowField,
|
||||
_i: usize,
|
||||
_j: usize,
|
||||
dx: f64,
|
||||
dy: f64,
|
||||
rho: f64,
|
||||
) -> CfdResult<f64> {
|
||||
Ok(rho * dx * dy / self.parameters.time_step)
|
||||
}
|
||||
}
|
||||
|
||||
/// Coefficients for momentum equation discretization
|
||||
#[derive(Debug, Clone)]
|
||||
struct MomentumEquationCoeffs {
|
||||
pub center: f64,
|
||||
pub east: f64,
|
||||
pub west: f64,
|
||||
pub north: f64,
|
||||
pub south: f64,
|
||||
pub source: f64,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl IncompressibleSolver for SimpleSolver {
|
||||
type Parameters = SimpleParameters;
|
||||
type Result = SimpleResult;
|
||||
|
||||
fn new(config: CfdConfig, params: Self::Parameters) -> CfdResult<Self> {
|
||||
Self::new(config, params)
|
||||
}
|
||||
|
||||
async fn solve_time_step(
|
||||
&mut self,
|
||||
flow_field: &mut FlowField,
|
||||
boundary_conditions: &BoundaryConditions,
|
||||
dt: f64,
|
||||
) -> CfdResult<Self::Result> {
|
||||
let start_time = Instant::now();
|
||||
let mut residual_history = Vec::new();
|
||||
let pressure_iterations = Vec::new();
|
||||
|
||||
for iteration in 0..self.parameters.max_iterations {
|
||||
let (mass_residual, momentum_residual) = self
|
||||
.solve_simple_iteration(flow_field, boundary_conditions, dt)
|
||||
.await?;
|
||||
|
||||
let total_residual =
|
||||
(mass_residual * mass_residual + momentum_residual * momentum_residual).sqrt();
|
||||
residual_history.push(total_residual);
|
||||
|
||||
if total_residual < self.parameters.tolerance {
|
||||
let solve_time = start_time.elapsed();
|
||||
|
||||
return Ok(SimpleResult {
|
||||
solver_result: SolverResult {
|
||||
converged: true,
|
||||
iterations: iteration + 1,
|
||||
final_residual: total_residual,
|
||||
residual_history,
|
||||
solve_time,
|
||||
},
|
||||
pressure_iterations,
|
||||
mass_residual,
|
||||
momentum_residual,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Did not converge
|
||||
let solve_time = start_time.elapsed();
|
||||
Ok(SimpleResult {
|
||||
solver_result: SolverResult {
|
||||
converged: false,
|
||||
iterations: self.parameters.max_iterations,
|
||||
final_residual: residual_history.last().copied().unwrap_or(f64::INFINITY),
|
||||
residual_history,
|
||||
solve_time,
|
||||
},
|
||||
pressure_iterations,
|
||||
mass_residual: f64::INFINITY,
|
||||
momentum_residual: f64::INFINITY,
|
||||
})
|
||||
}
|
||||
|
||||
async fn solve(
|
||||
&mut self,
|
||||
flow_field: &mut FlowField,
|
||||
boundary_conditions: &BoundaryConditions,
|
||||
) -> CfdResult<Self::Result> {
|
||||
// For steady-state solve, use default time step
|
||||
self.solve_time_step(flow_field, boundary_conditions, self.parameters.time_step)
|
||||
.await
|
||||
}
|
||||
|
||||
fn config(&self) -> &CfdConfig {
|
||||
&self.config
|
||||
}
|
||||
|
||||
fn parameters(&self) -> &Self::Parameters {
|
||||
&self.parameters
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user