= 0.05
+ ? `${(inspector.delta_p_sec_bar * 100).toFixed(2)} kPa`
+ : `${(inspector.delta_p_sec_bar * 1e5).toFixed(1)} Pa`
+ }
+ />
)}
{inspector.q_primary_kw != null && (
@@ -977,43 +991,6 @@ function ModelicaResultsView({
);
}
-function SolvedVariablesBlock({
- items,
- title,
-}: {
- items: SolvedVariable[];
- title: string;
-}) {
- if (items.length === 0) return null;
- return (
-
-
- {title}
-
-
-
-
- | Var |
- Value |
- Min |
- Max |
-
-
-
- {items.map((sv) => (
-
- | {sv.variable} |
- {fmtOpt(sv.value, 4)} |
- {fmtOpt(sv.min, 3)} |
- {fmtOpt(sv.max, 3)} |
-
- ))}
-
-
-
- );
-}
-
function MiniMetric({ label, value }: { label: string; value: string }) {
return (
diff --git a/apps/web/src/lib/componentInspector.ts b/apps/web/src/lib/componentInspector.ts
index 8a010f1..43be94a 100644
--- a/apps/web/src/lib/componentInspector.ts
+++ b/apps/web/src/lib/componentInspector.ts
@@ -182,8 +182,22 @@ export function buildComponentInspector(
}
const summaryLines: string[] = [];
- if (delta_p_bar != null) summaryLines.push(`ΔP = ${fmt(delta_p_bar, 3)} bar`);
- if (delta_p_sec_bar != null) summaryLines.push(`ΔP sec = ${fmt(delta_p_sec_bar, 3)} bar`);
+ if (delta_p_bar != null) {
+ const dp_kpa = delta_p_bar * 100.0;
+ summaryLines.push(
+ Math.abs(dp_kpa) >= 0.05
+ ? `ΔP = ${fmt(dp_kpa, 2)} kPa`
+ : `ΔP = ${fmt(delta_p_bar * 1e5, 1)} Pa`,
+ );
+ }
+ if (delta_p_sec_bar != null) {
+ const dp_kpa = delta_p_sec_bar * 100.0;
+ summaryLines.push(
+ Math.abs(dp_kpa) >= 0.05
+ ? `ΔP sec = ${fmt(dp_kpa, 2)} kPa`
+ : `ΔP sec = ${fmt(delta_p_sec_bar * 1e5, 1)} Pa`,
+ );
+ }
if (q_primary_kw != null) summaryLines.push(`Q̇ = ${fmt(Math.abs(q_primary_kw), 2)} kW`);
if (q_secondary_kw != null) summaryLines.push(`Q̇ sec = ${fmt(Math.abs(q_secondary_kw), 2)} kW`);
if (work_kw != null) summaryLines.push(`Ẇ = ${fmt(work_kw, 2)} kW`);
diff --git a/crates/components/src/heat_exchanger/bphx_exchanger.rs b/crates/components/src/heat_exchanger/bphx_exchanger.rs
index 0effc09..e67bbd9 100644
--- a/crates/components/src/heat_exchanger/bphx_exchanger.rs
+++ b/crates/components/src/heat_exchanger/bphx_exchanger.rs
@@ -498,7 +498,8 @@ impl BphxExchanger {
let dp_hot = self.side_channel_dp(state[m_h], rho_hot, z_dp)?;
let dp_cold = self.side_channel_dp(state[m_c], rho_cold, z_dp)?;
- let a_flow = self.geometry.channel_flow_area() * self.geometry.n_channels() as f64;
+ let a_flow =
+ self.geometry.channel_flow_area() * self.geometry.n_channels_per_side() as f64;
if a_flow <= 1e-30 {
return Err(ComponentError::InvalidState(
"BPHX channel flow area too small for pressure-drop Jacobian".into(),
diff --git a/crates/components/src/heat_exchanger/bphx_geometry.rs b/crates/components/src/heat_exchanger/bphx_geometry.rs
index 8138d09..d0f5144 100644
--- a/crates/components/src/heat_exchanger/bphx_geometry.rs
+++ b/crates/components/src/heat_exchanger/bphx_geometry.rs
@@ -170,20 +170,29 @@ impl BphxGeometry {
self.channel_spacing * self.plate_width
}
- /// Returns the total number of channels (n_plates - 1).
+ /// Returns the total number of inter-plate gaps (n_plates − 1).
pub fn n_channels(&self) -> u32 {
self.n_plates.saturating_sub(1)
}
- /// Returns the mass flux for a given mass flow rate.
+ /// Channels available to **one** fluid in a single-pass counterflow pack.
///
- /// G = m_dot / (A_channel × n_channels)
+ /// Alternating hot/cold channels split the gaps roughly in half:
+ /// `⌊n_plates / 2⌋` (at least 1). Using the full `n_channels` for mass flux
+ /// underestimates G by ~2× and ΔP by ~4×.
+ pub fn n_channels_per_side(&self) -> u32 {
+ (self.n_plates / 2).max(1)
+ }
+
+ /// Returns the mass flux for a given mass flow rate on one fluid side.
+ ///
+ /// G = m_dot / (A_channel × n_channels_per_side)
///
/// # Arguments
///
- /// * `mass_flow` - Total mass flow rate (kg/s)
+ /// * `mass_flow` - Total mass flow rate on that side (kg/s)
pub fn mass_flux(&self, mass_flow: f64) -> f64 {
- let n_channels = self.n_channels() as f64;
+ let n_channels = self.n_channels_per_side() as f64;
if n_channels < 1e-10 {
return 0.0;
}
@@ -460,8 +469,12 @@ mod tests {
.unwrap();
let mass_flow = 0.1;
+ // 10 plates → 5 channels per side; A_ch = 0.002 × 0.1 = 2e-4 m²
+ // G = 0.1 / (2e-4 × 5) = 100 kg/(m²·s)
let g = geo.mass_flux(mass_flow);
- assert!(g > 0.0);
+ assert!((g - 100.0).abs() < 1e-9, "got G={g}");
+ assert_eq!(geo.n_channels_per_side(), 5);
+ assert_eq!(geo.n_channels(), 9);
}
#[test]