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>
This commit is contained in:
2026-07-17 22:46:46 +02:00
parent 62efea0646
commit 3358b74342
275 changed files with 70187 additions and 5230 deletions

View File

@@ -0,0 +1,190 @@
//! 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);
}
}