//! Shell-and-tube heat exchanger rating via Bell–Delaware 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 Bell–Delaware 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.7–0.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, } } } /// Bell–Delaware 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 Bell–Delaware 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 Bell–Delaware + 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); } }