//! Boundary conditions for incompressible flow //! //! This module implements various boundary condition types commonly used //! in incompressible CFD simulations. use super::FlowField; use crate::{CfdError, CfdResult}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; /// Boundary condition types #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] pub enum BoundaryType { /// No-slip wall (u = v = 0) NoSlipWall, /// Free-slip wall (tangential velocity free, normal velocity zero) FreeSlipWall, /// Moving wall with specified velocity MovingWall { u: f64, v: f64 }, /// Velocity inlet with specified velocity VelocityInlet { u: f64, v: f64 }, /// Pressure outlet with specified pressure PressureOutlet { pressure: f64 }, /// Symmetry boundary Symmetry, /// Periodic boundary Periodic, } /// Boundary location #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum BoundaryLocation { /// Bottom boundary (j = 0) Bottom, /// Top boundary (j = ny-1) Top, /// Left boundary (i = 0) Left, /// Right boundary (i = nx-1) Right, } /// Individual boundary condition specification #[derive(Debug, Clone, Serialize, Deserialize)] pub struct BoundaryCondition { /// Type of boundary condition pub bc_type: BoundaryType, /// Location on the boundary pub location: BoundaryLocation, /// Start index (for partial boundaries) pub start_index: Option, /// End index (for partial boundaries) pub end_index: Option, } /// Collection of boundary conditions for a domain #[derive(Debug, Clone, Serialize, Deserialize)] pub struct BoundaryConditions { /// Boundary conditions by location conditions: HashMap>, /// Point-wise boundary conditions for complex geometries point_conditions: HashMap<(usize, usize), BoundaryType>, } impl BoundaryConditions { /// Create new empty boundary conditions #[must_use] pub fn new() -> Self { Self { conditions: HashMap::new(), point_conditions: HashMap::new(), } } /// Add boundary condition for an entire boundary pub fn add_boundary_condition(&mut self, location: BoundaryLocation, bc_type: BoundaryType) { let bc = BoundaryCondition { bc_type, location, start_index: None, end_index: None, }; self.conditions.entry(location).or_default().push(bc); } /// Add boundary condition for a segment of a boundary pub fn add_partial_boundary_condition( &mut self, location: BoundaryLocation, bc_type: BoundaryType, start_index: usize, end_index: usize, ) -> CfdResult<()> { if start_index >= end_index { return Err(CfdError::invalid_parameter( "Start index must be less than end index", )); } let bc = BoundaryCondition { bc_type, location, start_index: Some(start_index), end_index: Some(end_index), }; self.conditions.entry(location).or_default().push(bc); Ok(()) } /// Set boundary condition for a specific point pub fn set_velocity_bc(&mut self, i: usize, j: usize, u: f64, v: f64) -> CfdResult<()> { let bc_type = BoundaryType::MovingWall { u, v }; self.point_conditions.insert((i, j), bc_type); Ok(()) } /// Set pressure boundary condition for a specific point pub fn set_pressure_bc(&mut self, i: usize, j: usize, pressure: f64) -> CfdResult<()> { let bc_type = BoundaryType::PressureOutlet { pressure }; self.point_conditions.insert((i, j), bc_type); Ok(()) } /// Apply all boundary conditions to a flow field pub fn apply_to_flow_field(&self, flow_field: &mut FlowField) -> CfdResult<()> { let (_nx, _ny, _, _) = flow_field.grid_info(); // Apply boundary conditions by location for (location, bcs) in &self.conditions { for bc in bcs { self.apply_boundary_condition(*location, bc, flow_field)?; } } // Apply point-wise boundary conditions for (&(i, j), &bc_type) in &self.point_conditions { self.apply_point_boundary_condition(i, j, bc_type, flow_field)?; } Ok(()) } /// Apply a single boundary condition fn apply_boundary_condition( &self, location: BoundaryLocation, bc: &BoundaryCondition, flow_field: &mut FlowField, ) -> CfdResult<()> { let (nx, ny, _, _) = flow_field.grid_info(); match location { BoundaryLocation::Bottom => { let start = bc.start_index.unwrap_or(0); let end = bc.start_index.unwrap_or(nx); self.apply_bottom_bc(bc.bc_type, start, end, flow_field)?; } BoundaryLocation::Top => { let start = bc.start_index.unwrap_or(0); let end = bc.end_index.unwrap_or(nx); self.apply_top_bc(bc.bc_type, start, end, flow_field)?; } BoundaryLocation::Left => { let start = bc.start_index.unwrap_or(0); let end = bc.end_index.unwrap_or(ny); self.apply_left_bc(bc.bc_type, start, end, flow_field)?; } BoundaryLocation::Right => { let start = bc.start_index.unwrap_or(0); let end = bc.end_index.unwrap_or(ny); self.apply_right_bc(bc.bc_type, start, end, flow_field)?; } } Ok(()) } /// Apply boundary condition at bottom wall (j = 0) fn apply_bottom_bc( &self, bc_type: BoundaryType, start: usize, end: usize, flow_field: &mut FlowField, ) -> CfdResult<()> { let j = 0; match bc_type { BoundaryType::NoSlipWall => { // u = 0 at wall, v = 0 at wall for i in start..end.min(flow_field.nx + 1) { flow_field.u[(j, i)] = 0.0; } for i in start..end.min(flow_field.nx) { flow_field.v[(j, i)] = 0.0; } } BoundaryType::MovingWall { u, v } => { for i in start..end.min(flow_field.nx + 1) { flow_field.u[(j, i)] = u; } for i in start..end.min(flow_field.nx) { flow_field.v[(j, i)] = v; } } BoundaryType::FreeSlipWall => { // Zero normal velocity, free tangential velocity for i in start..end.min(flow_field.nx) { flow_field.v[(j, i)] = 0.0; } // u remains unchanged (free slip) } BoundaryType::Symmetry => { // Same as free slip for velocity for i in start..end.min(flow_field.nx) { flow_field.v[(j, i)] = 0.0; } } _ => { return Err(CfdError::invalid_parameter( "Invalid boundary condition for bottom wall", )); } } Ok(()) } /// Apply boundary condition at top wall (j = ny-1) fn apply_top_bc( &self, bc_type: BoundaryType, start: usize, end: usize, flow_field: &mut FlowField, ) -> CfdResult<()> { let j = flow_field.ny - 1; match bc_type { BoundaryType::NoSlipWall => { for i in start..end.min(flow_field.nx + 1) { flow_field.u[(j, i)] = 0.0; } for i in start..end.min(flow_field.nx) { flow_field.v[(j + 1, i)] = 0.0; } } BoundaryType::MovingWall { u, v } => { for i in start..end.min(flow_field.nx + 1) { flow_field.u[(j, i)] = u; } for i in start..end.min(flow_field.nx) { flow_field.v[(j + 1, i)] = v; } } BoundaryType::FreeSlipWall => { for i in start..end.min(flow_field.nx) { flow_field.v[(j + 1, i)] = 0.0; } } BoundaryType::Symmetry => { for i in start..end.min(flow_field.nx) { flow_field.v[(j + 1, i)] = 0.0; } } _ => { return Err(CfdError::invalid_parameter( "Invalid boundary condition for top wall", )); } } Ok(()) } /// Apply boundary condition at left wall (i = 0) fn apply_left_bc( &self, bc_type: BoundaryType, start: usize, end: usize, flow_field: &mut FlowField, ) -> CfdResult<()> { let i = 0; match bc_type { BoundaryType::NoSlipWall => { for j in start..end.min(flow_field.ny) { flow_field.u[(j, i)] = 0.0; flow_field.v[(j, i)] = 0.0; } } BoundaryType::VelocityInlet { u, v } => { for j in start..end.min(flow_field.ny) { flow_field.u[(j, i)] = u; flow_field.v[(j, i)] = v; } } BoundaryType::FreeSlipWall => { for j in start..end.min(flow_field.ny) { flow_field.u[(j, i)] = 0.0; } // v remains unchanged } BoundaryType::Symmetry => { for j in start..end.min(flow_field.ny) { flow_field.u[(j, i)] = 0.0; } } _ => { return Err(CfdError::invalid_parameter( "Invalid boundary condition for left wall", )); } } Ok(()) } /// Apply boundary condition at right wall (i = nx-1) fn apply_right_bc( &self, bc_type: BoundaryType, start: usize, end: usize, flow_field: &mut FlowField, ) -> CfdResult<()> { let i = flow_field.nx; match bc_type { BoundaryType::NoSlipWall => { for j in start..end.min(flow_field.ny) { flow_field.u[(j, i)] = 0.0; if i > 0 { flow_field.v[(j, i - 1)] = 0.0; } } } BoundaryType::PressureOutlet { pressure } => { // Zero gradient for velocity, specified pressure for j in start..end.min(flow_field.ny) { if i > 0 { flow_field.u[(j, i)] = flow_field.u[(j, i - 1)]; // Zero gradient flow_field.v[(j, i - 1)] = flow_field.v[(j, i - 2)]; // Zero gradient flow_field.p[(j, i - 1)] = pressure; } } } BoundaryType::FreeSlipWall => { for j in start..end.min(flow_field.ny) { flow_field.u[(j, i)] = 0.0; } } BoundaryType::Symmetry => { for j in start..end.min(flow_field.ny) { flow_field.u[(j, i)] = 0.0; } } _ => { return Err(CfdError::invalid_parameter( "Invalid boundary condition for right wall", )); } } Ok(()) } /// Apply point-wise boundary condition fn apply_point_boundary_condition( &self, i: usize, j: usize, bc_type: BoundaryType, flow_field: &mut FlowField, ) -> CfdResult<()> { let (nx, ny, _, _) = flow_field.grid_info(); if i >= nx || j >= ny { return Err(CfdError::invalid_parameter( "Point boundary condition out of bounds", )); } match bc_type { BoundaryType::MovingWall { u, v } => { if i < nx + 1 && j < ny { flow_field.u[(j, i)] = u; } if i < nx && j < ny + 1 { flow_field.v[(j, i)] = v; } } BoundaryType::PressureOutlet { pressure } => { flow_field.p[(j, i)] = pressure; } BoundaryType::NoSlipWall => { if i < nx + 1 && j < ny { flow_field.u[(j, i)] = 0.0; } if i < nx && j < ny + 1 { flow_field.v[(j, i)] = 0.0; } } _ => { return Err(CfdError::invalid_parameter( "Unsupported point boundary condition", )); } } Ok(()) } /// Set up lid-driven cavity boundary conditions #[must_use] pub fn lid_driven_cavity(_nx: usize, _ny: usize, lid_velocity: f64) -> Self { let mut bcs = Self::new(); // Bottom, left, right walls: no-slip bcs.add_boundary_condition(BoundaryLocation::Bottom, BoundaryType::NoSlipWall); bcs.add_boundary_condition(BoundaryLocation::Left, BoundaryType::NoSlipWall); bcs.add_boundary_condition(BoundaryLocation::Right, BoundaryType::NoSlipWall); // Top wall: moving wall with specified velocity bcs.add_boundary_condition( BoundaryLocation::Top, BoundaryType::MovingWall { u: lid_velocity, v: 0.0, }, ); bcs } /// Set up channel flow boundary conditions #[must_use] pub fn channel_flow(_nx: usize, _ny: usize, inlet_velocity: f64, outlet_pressure: f64) -> Self { let mut bcs = Self::new(); // Left: velocity inlet bcs.add_boundary_condition( BoundaryLocation::Left, BoundaryType::VelocityInlet { u: inlet_velocity, v: 0.0, }, ); // Right: pressure outlet bcs.add_boundary_condition( BoundaryLocation::Right, BoundaryType::PressureOutlet { pressure: outlet_pressure, }, ); // Top and bottom: no-slip walls bcs.add_boundary_condition(BoundaryLocation::Top, BoundaryType::NoSlipWall); bcs.add_boundary_condition(BoundaryLocation::Bottom, BoundaryType::NoSlipWall); bcs } /// Get boundary conditions for a specific location #[must_use] pub fn get_conditions(&self, location: BoundaryLocation) -> Option<&Vec> { self.conditions.get(&location) } /// Check if boundary conditions are properly specified pub fn validate(&self, nx: usize, ny: usize) -> CfdResult<()> { // Check that all boundaries have at least one condition let required_locations = [ BoundaryLocation::Bottom, BoundaryLocation::Top, BoundaryLocation::Left, BoundaryLocation::Right, ]; for &location in &required_locations { if !self.conditions.contains_key(&location) && self.point_conditions.is_empty() { return Err(CfdError::boundary_condition(format!( "No boundary condition specified for {location:?}" ))); } } // Validate point conditions are within domain for &(i, j) in self.point_conditions.keys() { if i >= nx || j >= ny { return Err(CfdError::boundary_condition(format!( "Point boundary condition at ({i}, {j}) is outside domain ({nx}x{ny})" ))); } } Ok(()) } } impl Default for BoundaryConditions { fn default() -> Self { Self::new() } }