Initial commit

This commit is contained in:
redclawsystems
2026-03-04 00:08:42 +00:00
commit 4d88dc0584
4449 changed files with 1556714 additions and 0 deletions
@@ -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
}
}