Initial commit
This commit is contained in:
@@ -0,0 +1,581 @@
|
||||
//! PISO (Pressure-Implicit with Splitting of Operators) algorithm
|
||||
//!
|
||||
//! The PISO algorithm is a non-iterative pressure-velocity coupling algorithm
|
||||
//! particularly well-suited for transient flow problems. It consists of one
|
||||
//! predictor step followed by two or more corrector steps.
|
||||
|
||||
use super::{BoundaryConditions, FlowField, IncompressibleSolver, SolverResult};
|
||||
use crate::{CfdConfig, CfdResult};
|
||||
use async_trait::async_trait;
|
||||
|
||||
/// Parameters for PISO algorithm
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PisoParameters {
|
||||
/// Number of corrector steps (typically 2-3)
|
||||
pub corrector_steps: usize,
|
||||
/// Time step size
|
||||
pub time_step: f64,
|
||||
/// Convergence tolerance
|
||||
pub tolerance: f64,
|
||||
}
|
||||
|
||||
impl Default for PisoParameters {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
corrector_steps: 2,
|
||||
time_step: 0.001,
|
||||
tolerance: 1e-6,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Result of PISO algorithm execution
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PisoResult {
|
||||
/// Base solver result information
|
||||
pub solver_result: SolverResult,
|
||||
/// Number of corrector steps performed
|
||||
pub corrector_steps_performed: usize,
|
||||
}
|
||||
|
||||
/// PISO algorithm implementation
|
||||
pub struct PisoSolver {
|
||||
config: CfdConfig,
|
||||
parameters: PisoParameters,
|
||||
}
|
||||
|
||||
impl PisoSolver {
|
||||
/// Create new PISO solver
|
||||
pub fn new(config: CfdConfig, parameters: PisoParameters) -> CfdResult<Self> {
|
||||
config.validate()?;
|
||||
|
||||
Ok(Self { config, parameters })
|
||||
}
|
||||
|
||||
/// Solve momentum predictor step
|
||||
/// Discretize: ∂u/∂t + ∇·(u⊗u) = -∇p^n/ρ + ν∇²u
|
||||
fn solve_momentum_predictor(
|
||||
&self,
|
||||
flow_field: &mut FlowField,
|
||||
dt: f64,
|
||||
rho: f64,
|
||||
nu: f64,
|
||||
) -> CfdResult<()> {
|
||||
let (_nx, _ny, dx, dy) = flow_field.grid_info();
|
||||
|
||||
// Solve u-momentum equation
|
||||
self.solve_u_momentum(flow_field, dt, rho, nu, dx, dy)?;
|
||||
|
||||
// Solve v-momentum equation
|
||||
self.solve_v_momentum(flow_field, dt, rho, nu, dx, dy)?;
|
||||
|
||||
// Store predicted velocities
|
||||
flow_field.copy_to_starred();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Solve u-momentum equation using finite volume method
|
||||
fn solve_u_momentum(
|
||||
&self,
|
||||
flow_field: &mut FlowField,
|
||||
dt: f64,
|
||||
rho: f64,
|
||||
nu: f64,
|
||||
dx: f64,
|
||||
dy: f64,
|
||||
) -> CfdResult<()> {
|
||||
let (nx, ny, _, _) = flow_field.grid_info();
|
||||
|
||||
// For each u-velocity control volume (i+1/2, j)
|
||||
for j in 1..(ny - 1) {
|
||||
for i in 1..nx {
|
||||
// Time derivative term: ∂u/∂t ≈ (u_new - u_old)/dt
|
||||
let time_coeff = 1.0 / dt;
|
||||
let time_source = flow_field.u_old[(j, i)] / dt;
|
||||
|
||||
// Convective terms: ∇·(u⊗u)
|
||||
// Face velocities for convection (interpolated)
|
||||
let u_east = if i < nx - 1 {
|
||||
0.5 * (flow_field.u[(j, i)] + flow_field.u[(j, i + 1)])
|
||||
} else {
|
||||
flow_field.u[(j, i)]
|
||||
};
|
||||
let u_west = if i > 1 {
|
||||
0.5 * (flow_field.u[(j, i - 1)] + flow_field.u[(j, i)])
|
||||
} else {
|
||||
flow_field.u[(j, i)]
|
||||
};
|
||||
let _u_north = if j < ny - 1 {
|
||||
0.5 * (flow_field.u[(j, i)] + flow_field.u[(j + 1, i)])
|
||||
} else {
|
||||
flow_field.u[(j, i)]
|
||||
};
|
||||
let _u_south = if j > 1 {
|
||||
0.5 * (flow_field.u[(j - 1, i)] + flow_field.u[(j, i)])
|
||||
} else {
|
||||
flow_field.u[(j, i)]
|
||||
};
|
||||
|
||||
// Transverse velocities
|
||||
let v_north = if i > 0 && i < nx && j < ny {
|
||||
0.5 * (flow_field.v[(j + 1, i - 1)] + flow_field.v[(j + 1, i)])
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
let v_south = if i > 0 && i < nx && j > 0 {
|
||||
0.5 * (flow_field.v[(j, i - 1)] + flow_field.v[(j, i)])
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
// Convective fluxes (upwind scheme)
|
||||
let conv_east = u_east
|
||||
* if u_east > 0.0 {
|
||||
flow_field.u[(j, i)]
|
||||
} else if i < nx - 1 {
|
||||
flow_field.u[(j, i + 1)]
|
||||
} else {
|
||||
flow_field.u[(j, i)]
|
||||
};
|
||||
let conv_west = u_west
|
||||
* if u_west > 0.0 {
|
||||
if i > 1 {
|
||||
flow_field.u[(j, i - 1)]
|
||||
} else {
|
||||
flow_field.u[(j, i)]
|
||||
}
|
||||
} else {
|
||||
flow_field.u[(j, i)]
|
||||
};
|
||||
let conv_north = v_north
|
||||
* if v_north > 0.0 {
|
||||
flow_field.u[(j, i)]
|
||||
} else if j < ny - 1 {
|
||||
flow_field.u[(j + 1, i)]
|
||||
} else {
|
||||
flow_field.u[(j, i)]
|
||||
};
|
||||
let conv_south = v_south
|
||||
* if v_south > 0.0 {
|
||||
if j > 1 {
|
||||
flow_field.u[(j - 1, i)]
|
||||
} else {
|
||||
flow_field.u[(j, i)]
|
||||
}
|
||||
} else {
|
||||
flow_field.u[(j, i)]
|
||||
};
|
||||
|
||||
let convection = (conv_east - conv_west) / dx + (conv_north - conv_south) / dy;
|
||||
|
||||
// Diffusive terms: ν∇²u
|
||||
let u_center = flow_field.u[(j, i)];
|
||||
let u_east_diff = if i < nx - 1 {
|
||||
flow_field.u[(j, i + 1)]
|
||||
} else {
|
||||
u_center
|
||||
};
|
||||
let u_west_diff = if i > 1 {
|
||||
flow_field.u[(j, i - 1)]
|
||||
} else {
|
||||
u_center
|
||||
};
|
||||
let u_north_diff = if j < ny - 1 {
|
||||
flow_field.u[(j + 1, i)]
|
||||
} else {
|
||||
u_center
|
||||
};
|
||||
let u_south_diff = if j > 1 {
|
||||
flow_field.u[(j - 1, i)]
|
||||
} else {
|
||||
u_center
|
||||
};
|
||||
|
||||
let diffusion_x = (u_east_diff - 2.0 * u_center + u_west_diff) / (dx * dx);
|
||||
let diffusion_y = (u_north_diff - 2.0 * u_center + u_south_diff) / (dy * dy);
|
||||
let diffusion = nu * (diffusion_x + diffusion_y);
|
||||
|
||||
// Pressure gradient: -∂p/∂x / ρ (using pressure from previous time step)
|
||||
let pressure_grad = if i < nx - 1 {
|
||||
-(flow_field.p[(j, i)] - flow_field.p[(j, i - 1)]) / (rho * dx)
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
// Source term
|
||||
let source = time_source + diffusion + pressure_grad;
|
||||
|
||||
// Solve: (1/dt + convection_coeff) * u_new = source
|
||||
let total_coeff = time_coeff;
|
||||
flow_field.u[(j, i)] = (source - convection) / total_coeff;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Solve v-momentum equation using finite volume method
|
||||
fn solve_v_momentum(
|
||||
&self,
|
||||
flow_field: &mut FlowField,
|
||||
dt: f64,
|
||||
rho: f64,
|
||||
nu: f64,
|
||||
dx: f64,
|
||||
dy: f64,
|
||||
) -> CfdResult<()> {
|
||||
let (nx, ny, _, _) = flow_field.grid_info();
|
||||
|
||||
// For each v-velocity control volume (i, j+1/2)
|
||||
for j in 1..ny {
|
||||
for i in 1..(nx - 1) {
|
||||
// Time derivative term
|
||||
let time_coeff = 1.0 / dt;
|
||||
let time_source = flow_field.v_old[(j, i)] / dt;
|
||||
|
||||
// Convective terms
|
||||
let _v_east = if i < nx - 1 {
|
||||
0.5 * (flow_field.v[(j, i)] + flow_field.v[(j, i + 1)])
|
||||
} else {
|
||||
flow_field.v[(j, i)]
|
||||
};
|
||||
let _v_west = if i > 1 {
|
||||
0.5 * (flow_field.v[(j, i - 1)] + flow_field.v[(j, i)])
|
||||
} else {
|
||||
flow_field.v[(j, i)]
|
||||
};
|
||||
let v_north = if j < ny - 1 {
|
||||
0.5 * (flow_field.v[(j, i)] + flow_field.v[(j + 1, i)])
|
||||
} else {
|
||||
flow_field.v[(j, i)]
|
||||
};
|
||||
let v_south = if j > 1 {
|
||||
0.5 * (flow_field.v[(j - 1, i)] + flow_field.v[(j, i)])
|
||||
} else {
|
||||
flow_field.v[(j, i)]
|
||||
};
|
||||
|
||||
// Transverse velocities
|
||||
let u_east = if j > 0 && j < ny && i < nx - 1 {
|
||||
0.5 * (flow_field.u[(j - 1, i + 1)] + flow_field.u[(j, i + 1)])
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
let u_west = if j > 0 && j < ny && i > 0 {
|
||||
0.5 * (flow_field.u[(j - 1, i)] + flow_field.u[(j, i)])
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
// Convective fluxes (upwind)
|
||||
let conv_east = u_east
|
||||
* if u_east > 0.0 {
|
||||
flow_field.v[(j, i)]
|
||||
} else if i < nx - 1 {
|
||||
flow_field.v[(j, i + 1)]
|
||||
} else {
|
||||
flow_field.v[(j, i)]
|
||||
};
|
||||
let conv_west = u_west
|
||||
* if u_west > 0.0 {
|
||||
if i > 1 {
|
||||
flow_field.v[(j, i - 1)]
|
||||
} else {
|
||||
flow_field.v[(j, i)]
|
||||
}
|
||||
} else {
|
||||
flow_field.v[(j, i)]
|
||||
};
|
||||
let conv_north = v_north
|
||||
* if v_north > 0.0 {
|
||||
flow_field.v[(j, i)]
|
||||
} else if j < ny - 1 {
|
||||
flow_field.v[(j + 1, i)]
|
||||
} else {
|
||||
flow_field.v[(j, i)]
|
||||
};
|
||||
let conv_south = v_south
|
||||
* if v_south > 0.0 {
|
||||
if j > 1 {
|
||||
flow_field.v[(j - 1, i)]
|
||||
} else {
|
||||
flow_field.v[(j, i)]
|
||||
}
|
||||
} else {
|
||||
flow_field.v[(j, i)]
|
||||
};
|
||||
|
||||
let convection = (conv_east - conv_west) / dx + (conv_north - conv_south) / dy;
|
||||
|
||||
// Diffusive terms
|
||||
let v_center = flow_field.v[(j, i)];
|
||||
let v_east_diff = if i < nx - 1 {
|
||||
flow_field.v[(j, i + 1)]
|
||||
} else {
|
||||
v_center
|
||||
};
|
||||
let v_west_diff = if i > 1 {
|
||||
flow_field.v[(j, i - 1)]
|
||||
} else {
|
||||
v_center
|
||||
};
|
||||
let v_north_diff = if j < ny - 1 {
|
||||
flow_field.v[(j + 1, i)]
|
||||
} else {
|
||||
v_center
|
||||
};
|
||||
let v_south_diff = if j > 1 {
|
||||
flow_field.v[(j - 1, i)]
|
||||
} else {
|
||||
v_center
|
||||
};
|
||||
|
||||
let diffusion_x = (v_east_diff - 2.0 * v_center + v_west_diff) / (dx * dx);
|
||||
let diffusion_y = (v_north_diff - 2.0 * v_center + v_south_diff) / (dy * dy);
|
||||
let diffusion = nu * (diffusion_x + diffusion_y);
|
||||
|
||||
// Pressure gradient: -∂p/∂y / ρ
|
||||
let pressure_grad = if j < ny - 1 {
|
||||
-(flow_field.p[(j, i)] - flow_field.p[(j - 1, i)]) / (rho * dy)
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
let source = time_source + diffusion + pressure_grad;
|
||||
|
||||
let total_coeff = time_coeff;
|
||||
flow_field.v[(j, i)] = (source - convection) / total_coeff;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Solve pressure correction equation
|
||||
/// ∇²p' = ρ∇·u*/dt
|
||||
fn solve_pressure_correction(
|
||||
&self,
|
||||
flow_field: &mut FlowField,
|
||||
dt: f64,
|
||||
rho: f64,
|
||||
dx: f64,
|
||||
dy: f64,
|
||||
) -> CfdResult<f64> {
|
||||
let (nx, ny, _, _) = flow_field.grid_info();
|
||||
|
||||
// Reset pressure correction
|
||||
flow_field.p_prime.fill(0.0);
|
||||
|
||||
// Iterative solution using Gauss-Seidel
|
||||
let mut max_residual = 0.0;
|
||||
|
||||
for _iter in 0..100 {
|
||||
// Inner iterations for pressure correction
|
||||
let mut residual: f64 = 0.0;
|
||||
|
||||
for j in 1..(ny - 1) {
|
||||
for i in 1..(nx - 1) {
|
||||
// Compute mass imbalance (divergence of velocity)
|
||||
let mass_imbalance =
|
||||
((flow_field.u_star[(j, i + 1)] - flow_field.u_star[(j, i)]) / dx
|
||||
+ (flow_field.v_star[(j + 1, i)] - flow_field.v_star[(j, i)]) / dy)
|
||||
* rho
|
||||
/ dt;
|
||||
|
||||
// Coefficients for pressure correction equation
|
||||
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_;
|
||||
|
||||
// Neighboring pressure corrections
|
||||
let p_east = if i < nx - 2 {
|
||||
flow_field.p_prime[(j, i + 1)]
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
let p_west = if i > 1 {
|
||||
flow_field.p_prime[(j, i - 1)]
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
let p_north = if j < ny - 2 {
|
||||
flow_field.p_prime[(j + 1, i)]
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
let p_south = if j > 1 {
|
||||
flow_field.p_prime[(j - 1, i)]
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
// Gauss-Seidel update
|
||||
let p_prime_new =
|
||||
(ae * p_east + aw * p_west + an * p_north + as_ * p_south + mass_imbalance)
|
||||
/ ap;
|
||||
|
||||
let correction_residual = (p_prime_new - flow_field.p_prime[(j, i)]).abs();
|
||||
residual = residual.max(correction_residual);
|
||||
|
||||
flow_field.p_prime[(j, i)] = p_prime_new;
|
||||
}
|
||||
}
|
||||
|
||||
max_residual = residual;
|
||||
|
||||
// Check inner convergence
|
||||
if residual < 1e-8 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Update pressure: p^(n+1) = p^n + p'
|
||||
for j in 0..ny {
|
||||
for i in 0..nx {
|
||||
flow_field.p[(j, i)] += flow_field.p_prime[(j, i)];
|
||||
}
|
||||
}
|
||||
|
||||
Ok(max_residual)
|
||||
}
|
||||
|
||||
/// Correct velocities based on pressure correction
|
||||
/// u^(n+1) = u* - (dt/ρ)∇p'
|
||||
fn correct_velocities(
|
||||
&self,
|
||||
flow_field: &mut FlowField,
|
||||
dt: f64,
|
||||
rho: f64,
|
||||
dx: f64,
|
||||
dy: f64,
|
||||
) -> CfdResult<()> {
|
||||
let (nx, ny, _, _) = flow_field.grid_info();
|
||||
|
||||
// Correct u-velocities
|
||||
for j in 1..(ny - 1) {
|
||||
for i in 1..nx {
|
||||
let dp_dx = if i > 0 && i < nx {
|
||||
(flow_field.p_prime[(j, i.min(nx - 1))]
|
||||
- flow_field.p_prime[(j, (i - 1).max(0))])
|
||||
/ dx
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
flow_field.u[(j, i)] = flow_field.u_star[(j, i)] - (dt / rho) * dp_dx;
|
||||
}
|
||||
}
|
||||
|
||||
// Correct v-velocities
|
||||
for j in 1..ny {
|
||||
for i in 1..(nx - 1) {
|
||||
let dp_dy = if j > 0 && j < ny {
|
||||
(flow_field.p_prime[(j.min(ny - 1), i)]
|
||||
- flow_field.p_prime[((j - 1).max(0), i)])
|
||||
/ dy
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
flow_field.v[(j, i)] = flow_field.v_star[(j, i)] - (dt / rho) * dp_dy;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl IncompressibleSolver for PisoSolver {
|
||||
type Parameters = PisoParameters;
|
||||
type Result = PisoResult;
|
||||
|
||||
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 = std::time::Instant::now();
|
||||
let mut residual_history = Vec::new();
|
||||
let (_nx, _ny, dx, dy) = flow_field.grid_info();
|
||||
|
||||
// Physical properties from config
|
||||
let rho = self.config.density;
|
||||
let nu = self.config.viscosity / rho; // kinematic viscosity
|
||||
|
||||
// Store old values for time derivative
|
||||
flow_field.update_old_values();
|
||||
|
||||
// Apply boundary conditions
|
||||
flow_field.apply_boundary_conditions(boundary_conditions)?;
|
||||
|
||||
// STEP 1: MOMENTUM PREDICTOR
|
||||
// Solve momentum equations with pressure from previous time step
|
||||
// ∂u/∂t + ∇·(u⊗u) = -∇p/ρ + ν∇²u
|
||||
self.solve_momentum_predictor(flow_field, dt, rho, nu)?;
|
||||
|
||||
let mut total_corrector_steps = 0;
|
||||
|
||||
// PRESSURE-VELOCITY CORRECTION LOOP
|
||||
for _corrector in 0..self.parameters.corrector_steps {
|
||||
// STEP 2: PRESSURE CORRECTION
|
||||
// Solve pressure Poisson equation: ∇²p' = ρ∇·u*/dt
|
||||
let pressure_residual = self.solve_pressure_correction(flow_field, dt, rho, dx, dy)?;
|
||||
residual_history.push(pressure_residual);
|
||||
|
||||
// STEP 3: VELOCITY CORRECTION
|
||||
// Update velocities: u = u* - (dt/ρ)∇p'
|
||||
self.correct_velocities(flow_field, dt, rho, dx, dy)?;
|
||||
|
||||
// Apply boundary conditions after correction
|
||||
flow_field.apply_boundary_conditions(boundary_conditions)?;
|
||||
|
||||
total_corrector_steps += 1;
|
||||
|
||||
// Check convergence
|
||||
if pressure_residual < self.parameters.tolerance {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let solve_time = start_time.elapsed();
|
||||
let final_residual = residual_history.last().copied().unwrap_or(0.0);
|
||||
let converged = final_residual < self.parameters.tolerance;
|
||||
|
||||
Ok(PisoResult {
|
||||
solver_result: SolverResult {
|
||||
converged,
|
||||
iterations: total_corrector_steps,
|
||||
final_residual,
|
||||
residual_history,
|
||||
solve_time,
|
||||
},
|
||||
corrector_steps_performed: total_corrector_steps,
|
||||
})
|
||||
}
|
||||
|
||||
async fn solve(
|
||||
&mut self,
|
||||
flow_field: &mut FlowField,
|
||||
boundary_conditions: &BoundaryConditions,
|
||||
) -> CfdResult<Self::Result> {
|
||||
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