Update project structure and configurations
This commit is contained in:
@@ -526,6 +526,26 @@ fn resolve_port_index(component_type: &str, port_name: &str, is_source: bool) ->
|
||||
}
|
||||
}
|
||||
},
|
||||
// BphxEvaporator and BphxCondenser: 2-port refrigerant circuit (inlet=0, outlet=1).
|
||||
// Secondary-fluid conditions are set via JSON params, not graph edges.
|
||||
"BphxEvaporator" | "BphxCondenser" => match port_lower.as_str() {
|
||||
"inlet" | "in" | "refrigerant_in" => 0,
|
||||
"outlet" | "out" | "refrigerant_out" => 1,
|
||||
_ => {
|
||||
tracing::warn!(
|
||||
port_name,
|
||||
component_type,
|
||||
"Unknown port name for {}, defaulting to {}",
|
||||
component_type,
|
||||
if is_source { 1 } else { 0 }
|
||||
);
|
||||
if is_source {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
// Default: inlet=0, outlet=1 for all 2-port components
|
||||
match port_lower.as_str() {
|
||||
@@ -598,21 +618,109 @@ fn parse_side_conditions(
|
||||
}
|
||||
|
||||
/// Build BphxGeometry from JSON params: dh (m), area (m²), n_plates. Defaults: 0.003, 0.5, 20.
|
||||
///
|
||||
/// Returns an error if any parameter is physically invalid (≤ 0).
|
||||
fn bphx_geometry_from_params(
|
||||
params: &std::collections::HashMap<String, serde_json::Value>,
|
||||
exchanger_type: entropyk_components::heat_exchanger::BphxType,
|
||||
) -> entropyk_components::heat_exchanger::BphxGeometry {
|
||||
) -> CliResult<entropyk_components::heat_exchanger::BphxGeometry> {
|
||||
use entropyk_components::heat_exchanger::BphxGeometry;
|
||||
let dh = params
|
||||
.get("dh_m")
|
||||
let dh = params.get("dh_m").and_then(|v| v.as_f64()).unwrap_or(0.003);
|
||||
if dh <= 0.0 {
|
||||
return Err(CliError::Config(format!(
|
||||
"BphxGeometry: dh_m must be > 0 (got {:.6} m)",
|
||||
dh
|
||||
)));
|
||||
}
|
||||
let area = params
|
||||
.get("area_m2")
|
||||
.and_then(|v| v.as_f64())
|
||||
.unwrap_or(0.003);
|
||||
let area = params.get("area_m2").and_then(|v| v.as_f64()).unwrap_or(0.5);
|
||||
let n_plates = params
|
||||
.unwrap_or(0.5);
|
||||
if area <= 0.0 {
|
||||
return Err(CliError::Config(format!(
|
||||
"BphxGeometry: area_m2 must be > 0 (got {:.4} m²)",
|
||||
area
|
||||
)));
|
||||
}
|
||||
let n_plates_raw = params
|
||||
.get("n_plates")
|
||||
.and_then(|v| v.as_u64())
|
||||
.unwrap_or(20) as u32;
|
||||
BphxGeometry::from_dh_area(dh, area, n_plates).with_exchanger_type(exchanger_type)
|
||||
.unwrap_or(20);
|
||||
if n_plates_raw > u32::MAX as u64 {
|
||||
return Err(CliError::Config(format!(
|
||||
"BphxGeometry: n_plates too large (got {}, max {})",
|
||||
n_plates_raw, u32::MAX
|
||||
)));
|
||||
}
|
||||
let n_plates = n_plates_raw as u32;
|
||||
if n_plates == 0 {
|
||||
return Err(CliError::Config(
|
||||
"BphxGeometry: n_plates must be > 0".into(),
|
||||
));
|
||||
}
|
||||
Ok(BphxGeometry::from_dh_area(dh, area, n_plates).with_exchanger_type(exchanger_type))
|
||||
}
|
||||
|
||||
/// Extract calibration factors for BphxEvaporator/BphxCondenser from JSON params.
|
||||
///
|
||||
/// Errors if `ua_nominal == 0` and an explicit `ua` override is provided (geometry is
|
||||
/// likely invalid). Warns if both `ua` and `f_ua` are provided simultaneously.
|
||||
fn bphx_calib_from_params(
|
||||
params: &std::collections::HashMap<String, serde_json::Value>,
|
||||
ua_nominal: f64,
|
||||
) -> CliResult<entropyk_core::Calib> {
|
||||
use entropyk_core::Calib;
|
||||
let config_ua = params.get("ua").and_then(|v| v.as_f64());
|
||||
let explicit_f_ua = params.get("f_ua").and_then(|v| v.as_f64());
|
||||
|
||||
if config_ua.is_some() && explicit_f_ua.is_some() {
|
||||
tracing::warn!(
|
||||
"BphxExchanger: both 'ua' and 'f_ua' provided — 'ua' takes precedence, 'f_ua' ignored"
|
||||
);
|
||||
}
|
||||
|
||||
let f_ua = match config_ua {
|
||||
Some(u) => {
|
||||
if u < 0.0 {
|
||||
return Err(CliError::Config(format!(
|
||||
"BphxExchanger: ua must be >= 0 (got {:.2} W/K)",
|
||||
u
|
||||
)));
|
||||
}
|
||||
if ua_nominal > 0.0 {
|
||||
u / ua_nominal
|
||||
} else {
|
||||
return Err(CliError::Config(
|
||||
"BphxExchanger: ua_nominal is zero — cannot compute f_ua from explicit 'ua' override. Check geometry parameters.".into(),
|
||||
));
|
||||
}
|
||||
}
|
||||
None => explicit_f_ua.unwrap_or(1.0),
|
||||
};
|
||||
|
||||
if f_ua <= 0.0 {
|
||||
return Err(CliError::Config(format!(
|
||||
"BphxExchanger: f_ua must be > 0 (got {:.4})",
|
||||
f_ua
|
||||
)));
|
||||
}
|
||||
|
||||
let f_dp = params.get("f_dp").and_then(|v| v.as_f64()).unwrap_or(1.0);
|
||||
if f_dp <= 0.0 {
|
||||
return Err(CliError::Config(format!(
|
||||
"BphxExchanger: f_dp must be > 0 (got {:.4})",
|
||||
f_dp
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(Calib {
|
||||
f_m: 1.0,
|
||||
f_dp,
|
||||
f_ua,
|
||||
f_power: 1.0,
|
||||
f_etav: 1.0,
|
||||
calibration_source: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates a pair of connected ports for components that need them (screw, MCHX, fan...).
|
||||
@@ -1230,9 +1338,8 @@ fn create_component(
|
||||
use entropyk_components::heat_exchanger::{
|
||||
BphxCorrelation, BphxEvaporator, BphxEvaporatorMode, BphxType,
|
||||
};
|
||||
use entropyk_core::Calib;
|
||||
|
||||
let geo = bphx_geometry_from_params(params, BphxType::Evaporator);
|
||||
let geo = bphx_geometry_from_params(params, BphxType::Evaporator)?;
|
||||
let refrigerant = params
|
||||
.get("refrigerant")
|
||||
.and_then(|v| v.as_str())
|
||||
@@ -1242,29 +1349,64 @@ fn create_component(
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("Water");
|
||||
|
||||
let mode = match params.get("mode").and_then(|v| v.as_str()).unwrap_or("dx") {
|
||||
let mode_str = params
|
||||
.get("mode")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("dx")
|
||||
.to_lowercase();
|
||||
let mode = match mode_str.as_str() {
|
||||
"flooded" => {
|
||||
let target_quality = params
|
||||
.get("target_quality")
|
||||
.and_then(|v| v.as_f64())
|
||||
.unwrap_or(0.7);
|
||||
if !(0.0..=1.0).contains(&target_quality) {
|
||||
return Err(CliError::Config(format!(
|
||||
"BphxEvaporator: target_quality must be in [0, 1] (got {:.4})",
|
||||
target_quality
|
||||
)));
|
||||
}
|
||||
BphxEvaporatorMode::Flooded { target_quality }
|
||||
}
|
||||
_ => {
|
||||
other => {
|
||||
if other != "dx" {
|
||||
tracing::warn!(
|
||||
mode = other,
|
||||
"Unknown BphxEvaporator mode '{}', falling back to 'dx'",
|
||||
other
|
||||
);
|
||||
}
|
||||
let target_superheat = params
|
||||
.get("target_superheat_k")
|
||||
.and_then(|v| v.as_f64())
|
||||
.unwrap_or(5.0);
|
||||
BphxEvaporatorMode::Dx {
|
||||
target_superheat,
|
||||
if target_superheat < 0.0 {
|
||||
return Err(CliError::Config(format!(
|
||||
"BphxEvaporator: target_superheat_k must be >= 0 (got {:.2} K)",
|
||||
target_superheat
|
||||
)));
|
||||
}
|
||||
BphxEvaporatorMode::Dx { target_superheat }
|
||||
}
|
||||
};
|
||||
|
||||
let correlation = match params.get("correlation").and_then(|v| v.as_str()) {
|
||||
Some("Shah1979") => BphxCorrelation::Shah1979,
|
||||
Some("Shah2021") => BphxCorrelation::Shah2021,
|
||||
_ => BphxCorrelation::Longo2004,
|
||||
let correlation_str = params
|
||||
.get("correlation")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("Longo2004")
|
||||
.to_lowercase();
|
||||
let correlation = match correlation_str.as_str() {
|
||||
"shah1979" => BphxCorrelation::Shah1979,
|
||||
"shah2021" => BphxCorrelation::Shah2021,
|
||||
"longo2004" => BphxCorrelation::Longo2004,
|
||||
other => {
|
||||
tracing::warn!(
|
||||
correlation = other,
|
||||
"Unknown BphxEvaporator correlation '{}', falling back to Longo2004",
|
||||
other
|
||||
);
|
||||
BphxCorrelation::Longo2004
|
||||
}
|
||||
};
|
||||
|
||||
let mut evap = BphxEvaporator::new(geo)
|
||||
@@ -1274,6 +1416,9 @@ fn create_component(
|
||||
.with_fluid_backend(Arc::clone(&backend))
|
||||
.with_correlation(correlation);
|
||||
|
||||
// Convention (Evaporator): hot_fluid = secondary (brine/water), cold_fluid = refrigerant.
|
||||
// The refrigerant evaporates (absorbs heat from the secondary).
|
||||
// Note: this is opposite to the Condenser convention — see BphxCondenser.
|
||||
if params.contains_key("hot_fluid") {
|
||||
let hot = parse_side_conditions(params, "hot")?;
|
||||
evap.set_secondary_conditions(hot);
|
||||
@@ -1283,24 +1428,7 @@ fn create_component(
|
||||
evap.set_refrigerant_conditions(cold);
|
||||
}
|
||||
|
||||
let ua_nominal = evap.ua();
|
||||
let config_ua = params.get("ua").and_then(|v| v.as_f64());
|
||||
let f_ua = config_ua
|
||||
.map(|u| if ua_nominal > 0.0 { u / ua_nominal } else { 1.0 })
|
||||
.unwrap_or_else(|| {
|
||||
params
|
||||
.get("f_ua")
|
||||
.and_then(|v| v.as_f64())
|
||||
.unwrap_or(1.0)
|
||||
});
|
||||
let f_dp = params.get("f_dp").and_then(|v| v.as_f64()).unwrap_or(1.0);
|
||||
evap.set_calib(Calib {
|
||||
f_m: 1.0,
|
||||
f_dp,
|
||||
f_ua,
|
||||
f_power: 1.0,
|
||||
f_etav: 1.0,
|
||||
});
|
||||
evap.set_calib(bphx_calib_from_params(params, evap.ua())?);
|
||||
|
||||
Ok(Box::new(evap))
|
||||
}
|
||||
@@ -1310,9 +1438,8 @@ fn create_component(
|
||||
use entropyk_components::heat_exchanger::{
|
||||
BphxCondenser, BphxCorrelation, BphxType,
|
||||
};
|
||||
use entropyk_core::Calib;
|
||||
|
||||
let geo = bphx_geometry_from_params(params, BphxType::Condenser);
|
||||
let geo = bphx_geometry_from_params(params, BphxType::Condenser)?;
|
||||
let refrigerant = params
|
||||
.get("refrigerant")
|
||||
.and_then(|v| v.as_str())
|
||||
@@ -1326,10 +1453,23 @@ fn create_component(
|
||||
.and_then(|v| v.as_f64())
|
||||
.unwrap_or(3.0);
|
||||
|
||||
let correlation = match params.get("correlation").and_then(|v| v.as_str()) {
|
||||
Some("Shah1979") => BphxCorrelation::Shah1979,
|
||||
Some("Shah2021") => BphxCorrelation::Shah2021,
|
||||
_ => BphxCorrelation::Longo2004,
|
||||
let correlation_str = params
|
||||
.get("correlation")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("Longo2004")
|
||||
.to_lowercase();
|
||||
let correlation = match correlation_str.as_str() {
|
||||
"shah1979" => BphxCorrelation::Shah1979,
|
||||
"shah2021" => BphxCorrelation::Shah2021,
|
||||
"longo2004" => BphxCorrelation::Longo2004,
|
||||
other => {
|
||||
tracing::warn!(
|
||||
correlation = other,
|
||||
"Unknown BphxCondenser correlation '{}', falling back to Longo2004",
|
||||
other
|
||||
);
|
||||
BphxCorrelation::Longo2004
|
||||
}
|
||||
};
|
||||
|
||||
let mut cond = BphxCondenser::new(geo)
|
||||
@@ -1339,6 +1479,9 @@ fn create_component(
|
||||
.with_target_subcooling(target_subcooling)
|
||||
.with_correlation(correlation);
|
||||
|
||||
// Convention (Condenser): hot_fluid = refrigerant, cold_fluid = secondary (brine/water).
|
||||
// The refrigerant condenses (releases heat to the secondary).
|
||||
// Note: this is opposite to the Evaporator convention — see BphxEvaporator.
|
||||
if params.contains_key("hot_fluid") {
|
||||
let hot = parse_side_conditions(params, "hot")?;
|
||||
cond.set_refrigerant_conditions(hot);
|
||||
@@ -1348,24 +1491,7 @@ fn create_component(
|
||||
cond.set_secondary_conditions(cold);
|
||||
}
|
||||
|
||||
let ua_nominal = cond.ua();
|
||||
let config_ua = params.get("ua").and_then(|v| v.as_f64());
|
||||
let f_ua = config_ua
|
||||
.map(|u| if ua_nominal > 0.0 { u / ua_nominal } else { 1.0 })
|
||||
.unwrap_or_else(|| {
|
||||
params
|
||||
.get("f_ua")
|
||||
.and_then(|v| v.as_f64())
|
||||
.unwrap_or(1.0)
|
||||
});
|
||||
let f_dp = params.get("f_dp").and_then(|v| v.as_f64()).unwrap_or(1.0);
|
||||
cond.set_calib(Calib {
|
||||
f_m: 1.0,
|
||||
f_dp,
|
||||
f_ua,
|
||||
f_power: 1.0,
|
||||
f_etav: 1.0,
|
||||
});
|
||||
cond.set_calib(bphx_calib_from_params(params, cond.ua())?);
|
||||
|
||||
Ok(Box::new(cond))
|
||||
}
|
||||
@@ -1375,8 +1501,48 @@ fn create_component(
|
||||
Ok(Box::new(SimpleComponent::new("", n_eqs)))
|
||||
}
|
||||
|
||||
"FreeCoolingExchanger" | "FreeCooling" => {
|
||||
use entropyk::{FreeCoolingConfig, FreeCoolingControlMode, FreeCoolingExchanger, FreeCoolingMode};
|
||||
use entropyk_components::port::{FluidId, Port};
|
||||
use entropyk_core::{CircuitId, Enthalpy, Pressure};
|
||||
|
||||
let effectiveness = params.get("effectiveness").and_then(|v| v.as_f64()).unwrap_or(0.85);
|
||||
let ua = params.get("ua").and_then(|v| v.as_f64()).unwrap_or(10_000.0);
|
||||
let cold_mass_flow = params.get("coldMassFlow").and_then(|v| v.as_f64()).unwrap_or(0.5);
|
||||
let hot_mass_flow = params.get("hotMassFlow").and_then(|v| v.as_f64()).unwrap_or(0.5);
|
||||
let cold_cp = params.get("coldCp").and_then(|v| v.as_f64()).unwrap_or(4186.0);
|
||||
let hot_cp = params.get("hotCp").and_then(|v| v.as_f64()).unwrap_or(4186.0);
|
||||
|
||||
let config = FreeCoolingConfig {
|
||||
effectiveness,
|
||||
ua,
|
||||
cold_mass_flow,
|
||||
hot_mass_flow,
|
||||
cold_cp,
|
||||
hot_cp,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let circuit_id = CircuitId(0);
|
||||
let fluid = FluidId::new("Water");
|
||||
let p = Pressure::from_pascals(3e5);
|
||||
let h = Enthalpy::from_joules_per_kg(63_000.0);
|
||||
|
||||
let (ci, co) = Port::new(FluidId::new("Water"), p, h)
|
||||
.connect(Port::new(FluidId::new("Water"), p, h))
|
||||
.map_err(|e| CliError::Config(format!("Port connect error: {}", e)))?;
|
||||
let (hi, ho) = Port::new(FluidId::new("Water"), p, h)
|
||||
.connect(Port::new(FluidId::new("Water"), p, h))
|
||||
.map_err(|e| CliError::Config(format!("Port connect error: {}", e)))?;
|
||||
|
||||
let fc = FreeCoolingExchanger::new("freecooling", circuit_id, config, ci, co, hi, ho)
|
||||
.map_err(|e| CliError::Config(format!("FreeCoolingExchanger error: {}", e)))?;
|
||||
|
||||
Ok(Box::new(fc))
|
||||
}
|
||||
|
||||
_ => Err(CliError::Config(format!(
|
||||
"Unknown component type: '{}'. Supported: ScrewEconomizerCompressor, MchxCondenserCoil, FloodedEvaporator, BphxEvaporator, BphxCondenser, Condenser, CondenserCoil, Evaporator, EvaporatorCoil, HeatExchanger, Compressor, ExpansionValve, Pump, Placeholder",
|
||||
"Unknown component type: '{}'. Supported: ScrewEconomizerCompressor, MchxCondenserCoil, FloodedEvaporator, BphxEvaporator, BphxCondenser, FreeCoolingExchanger, Condenser, CondenserCoil, Evaporator, EvaporatorCoil, HeatExchanger, Compressor, ExpansionValve, Pump, Placeholder",
|
||||
component_type
|
||||
))),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user