Files
rustytorch/crates/specialized/rtx-cfd/src/solvers/incompressible/boundary_conditions.rs
T
2026-03-04 00:08:42 +00:00

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()
}
}