511 lines
16 KiB
Rust
511 lines
16 KiB
Rust
//! 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<usize>,
|
|
/// End index (for partial boundaries)
|
|
pub end_index: Option<usize>,
|
|
}
|
|
|
|
/// Collection of boundary conditions for a domain
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct BoundaryConditions {
|
|
/// Boundary conditions by location
|
|
conditions: HashMap<BoundaryLocation, Vec<BoundaryCondition>>,
|
|
/// 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<BoundaryCondition>> {
|
|
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()
|
|
}
|
|
}
|