Files
Entropyk/crates/components/src/heat_exchanger/shell_and_tube.rs
sepehr 3358b74342 Add diagram workbench UI with Modelica DoF coaching and ISO glyphs.
Ship the Next.js cycle editor with CAD chrome, technical HX symbols, Fixed/Free boundary guidance, and secondary water/air pressure drop support in the solver stack.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-17 22:46:46 +02:00

191 lines
5.8 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Shell-and-tube heat exchanger rating via BellDelaware shell-side factors.
//!
//! Computes shell-side HTC `h_s = h_ideal · J_C · J_L · J_B · J_R · J_S` and a
//! combined UA with tube-side Gnielinski / Cooper / Shah as selected.
use super::bphx_correlation::{BphxCorrelation, CorrelationParams, FlowRegime};
use super::correlation_registry::CorrelationId;
use super::pool_boiling::{cooper_1984, PoolBoilingInput};
/// Geometric inputs for BellDelaware correction factors.
#[derive(Debug, Clone, Copy)]
pub struct BellDelawareGeometry {
/// Fraction of tubes in cross-flow (F_C ≈ 1 2 F_W).
pub f_c: f64,
/// Leakage area ratio r_s = S_sb / (S_sb + S_tb).
pub r_s: f64,
/// Leakage/main stream area ratio r_lm = (S_sb + S_tb) / S_m.
pub r_lm: f64,
/// Bypass correction J_B (typical 0.70.9).
pub j_b: f64,
/// Laminar gradient correction J_R (1.0 if Re_s ≥ 100).
pub j_r: f64,
/// Unequal baffle spacing correction J_S (1.0 if uniform).
pub j_s: f64,
/// Ideal cross-flow HTC [W/(m²·K)] before corrections.
pub h_ideal: f64,
/// Shell-side area [m²].
pub area_shell_m2: f64,
/// Tube-side area [m²].
pub area_tube_m2: f64,
}
impl Default for BellDelawareGeometry {
fn default() -> Self {
Self {
f_c: 0.9,
r_s: 0.4,
r_lm: 0.3,
j_b: 0.8,
j_r: 1.0,
j_s: 1.0,
h_ideal: 2500.0,
area_shell_m2: 30.0,
area_tube_m2: 25.0,
}
}
}
/// BellDelaware correction factors.
#[derive(Debug, Clone, Copy)]
pub struct BellDelawareFactors {
/// Configuration factor J_C.
pub j_c: f64,
/// Leakage factor J_L.
pub j_l: f64,
/// Bypass factor J_B.
pub j_b: f64,
/// Laminar factor J_R.
pub j_r: f64,
/// Spacing factor J_S.
pub j_s: f64,
}
/// Computes J-factors from geometry (Taborek / Delaware handbook forms).
pub fn bell_delaware_factors(geom: &BellDelawareGeometry) -> BellDelawareFactors {
let j_c = 0.55 + 0.72 * geom.f_c.clamp(0.0, 1.0);
let j_l = 0.44 * (1.0 - geom.r_s)
+ (1.0 - 0.44 * (1.0 - geom.r_s)) * (-2.2 * geom.r_lm).exp();
BellDelawareFactors {
j_c: j_c.clamp(0.65, 1.15),
j_l: j_l.clamp(0.2, 1.0),
j_b: geom.j_b.clamp(0.7, 0.9),
j_r: geom.j_r.clamp(0.7, 1.0),
j_s: geom.j_s.clamp(0.8, 1.0),
}
}
/// Shell-side HTC after BellDelaware corrections [W/(m²·K)].
pub fn shell_side_htc(geom: &BellDelawareGeometry) -> f64 {
let f = bell_delaware_factors(geom);
geom.h_ideal * f.j_c * f.j_l * f.j_b * f.j_r * f.j_s
}
/// Tube-side HTC using a registered correlation [W/(m²·K)].
pub fn tube_side_htc(
correlation: CorrelationId,
params: &CorrelationParams,
pool: Option<&PoolBoilingInput>,
) -> f64 {
match correlation {
CorrelationId::Cooper1984 => pool
.and_then(|p| cooper_1984(p).ok())
.unwrap_or(3000.0),
CorrelationId::Gnielinski1976 => BphxCorrelation::Gnielinski1976.compute_htc(params).h,
CorrelationId::Shah2009 => BphxCorrelation::Shah2009.compute_htc(params).h,
CorrelationId::Shah1979 => BphxCorrelation::Shah1979.compute_htc(params).h,
CorrelationId::Cavallini2006 => BphxCorrelation::Cavallini2006.compute_htc(params).h,
CorrelationId::Kandlikar1990 => BphxCorrelation::Kandlikar1990.compute_htc(params).h,
_ => BphxCorrelation::Gnielinski1976.compute_htc(params).h,
}
}
/// Combined UA [W/K] from shell and tube sides (wall resistance neglected).
pub fn shell_and_tube_ua(
geom: &BellDelawareGeometry,
h_tube: f64,
) -> f64 {
let h_shell = shell_side_htc(geom);
let r = 1.0 / (h_shell.max(1.0) * geom.area_shell_m2.max(1e-9))
+ 1.0 / (h_tube.max(1.0) * geom.area_tube_m2.max(1e-9));
1.0 / r
}
/// Rating helper wrapping BellDelaware + tube correlation.
#[derive(Debug, Clone)]
pub struct ShellAndTubeHx {
geom: BellDelawareGeometry,
tube_correlation: CorrelationId,
last_ua: f64,
}
impl ShellAndTubeHx {
/// Creates a shell-and-tube rater with default geometry.
pub fn new(geom: BellDelawareGeometry) -> Self {
Self {
geom,
tube_correlation: CorrelationId::Gnielinski1976,
last_ua: 0.0,
}
}
/// Selects tube-side correlation.
pub fn with_tube_correlation(mut self, id: CorrelationId) -> Self {
self.tube_correlation = id;
self
}
/// Rates UA from operating tube-side params.
pub fn rate_ua(&mut self, params: &CorrelationParams) -> f64 {
let h_tube = tube_side_htc(self.tube_correlation, params, None);
self.last_ua = shell_and_tube_ua(&self.geom, h_tube);
self.last_ua
}
/// Last computed UA [W/K].
pub fn ua(&self) -> f64 {
self.last_ua
}
/// Geometry accessor.
pub fn geometry(&self) -> &BellDelawareGeometry {
&self.geom
}
}
/// Default condensation CorrelationParams for tube-side rating.
pub fn default_tube_params(regime: FlowRegime) -> CorrelationParams {
CorrelationParams {
regime,
mass_flux: 200.0,
..CorrelationParams::default()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn factors_in_expected_bands() {
let f = bell_delaware_factors(&BellDelawareGeometry::default());
assert!(f.j_c >= 0.65 && f.j_c <= 1.15);
assert!(f.j_l >= 0.2 && f.j_l <= 1.0);
}
#[test]
fn shell_htc_less_than_ideal() {
let g = BellDelawareGeometry::default();
let h = shell_side_htc(&g);
assert!(h < g.h_ideal);
assert!(h > 500.0);
}
#[test]
fn rate_ua_positive() {
let mut hx = ShellAndTubeHx::new(BellDelawareGeometry::default());
let ua = hx.rate_ua(&default_tube_params(FlowRegime::SinglePhaseLiquid));
assert!(ua.is_finite() && ua > 1000.0);
}
}