///|
pub struct ThermalStreamTarget {
id : String
supply_c : Double
target_c : Double
heat_capacity_flow_kw_per_k : Double
} derive(Debug)
///|
pub struct PinchTargets {
hot_target_c : Double
cold_target_c : Double
minimum_hot_utility_kw : Double
minimum_cold_utility_kw : Double
total_hot_duty_kw : Double
total_cold_duty_kw : Double
} derive(Debug)
///|
pub struct HeatRecoveryMatch {
hot_id : String
cold_id : String
duty_kw : Double
hot_outlet_c : Double
cold_outlet_c : Double
feasible : Bool
} derive(Debug)
///|
pub fn ThermalStreamTarget::ThermalStreamTarget(
id~ : String,
supply_c~ : Double,
target_c~ : Double,
heat_capacity_flow_kw_per_k~ : Double,
) -> ThermalStreamTarget {
{ id, supply_c, target_c, heat_capacity_flow_kw_per_k }
}
///|
pub fn hot_stream_duty_kw(stream : ThermalStreamTarget) -> Double {
if stream.supply_c <= stream.target_c ||
stream.heat_capacity_flow_kw_per_k <= 0.0 {
0.0
} else {
(stream.supply_c - stream.target_c) * stream.heat_capacity_flow_kw_per_k
}
}
///|
pub fn cold_stream_duty_kw(stream : ThermalStreamTarget) -> Double {
if stream.target_c <= stream.supply_c ||
stream.heat_capacity_flow_kw_per_k <= 0.0 {
0.0
} else {
(stream.target_c - stream.supply_c) * stream.heat_capacity_flow_kw_per_k
}
}
///|
pub fn pinch_targets(
hot_streams : Array[ThermalStreamTarget],
cold_streams : Array[ThermalStreamTarget],
minimum_approach_c : Double,
) -> PinchTargets {
let mut hot_duty = 0.0
let mut cold_duty = 0.0
let mut hot_target = -1.0e30
let mut cold_target = -1.0e30
for stream in hot_streams {
hot_duty += hot_stream_duty_kw(stream)
hot_target = hot_target.max(stream.supply_c)
}
for stream in cold_streams {
cold_duty += cold_stream_duty_kw(stream)
cold_target = cold_target.max(stream.target_c)
}
{
hot_target_c: hot_target + minimum_approach_c / 2.0,
cold_target_c: cold_target - minimum_approach_c / 2.0,
minimum_hot_utility_kw: (cold_duty - hot_duty).max(0.0),
minimum_cold_utility_kw: (hot_duty - cold_duty).max(0.0),
total_hot_duty_kw: hot_duty,
total_cold_duty_kw: cold_duty,
}
}
///|
pub fn match_heat_streams(
hot : ThermalStreamTarget,
cold : ThermalStreamTarget,
minimum_approach_c : Double,
) -> HeatRecoveryMatch {
let hot_duty = hot_stream_duty_kw(hot)
let cold_duty = cold_stream_duty_kw(cold)
let duty = hot_duty.min(cold_duty)
let feasible = hot.supply_c - cold.supply_c >= minimum_approach_c &&
duty > 0.0
let hot_drop = if hot.heat_capacity_flow_kw_per_k <= 0.0 {
0.0
} else {
duty / hot.heat_capacity_flow_kw_per_k
}
let cold_rise = if cold.heat_capacity_flow_kw_per_k <= 0.0 {
0.0
} else {
duty / cold.heat_capacity_flow_kw_per_k
}
{
hot_id: hot.id,
cold_id: cold.id,
duty_kw: if feasible {
duty
} else {
0.0
},
hot_outlet_c: hot.supply_c - hot_drop,
cold_outlet_c: cold.supply_c + cold_rise,
feasible,
}
}
///|
pub fn exchanger_area_m2(
duty_w : Double,
overall_u_w_per_m2_k : Double,
lmtd_k : Double,
correction_factor : Double,
) -> Double {
let denominator = overall_u_w_per_m2_k * lmtd_k * correction_factor
if denominator <= 0.0 {
0.0
} else {
duty_w.abs() / denominator
}
}
///|
pub fn exchanger_overdesign_area_m2(
required_area_m2 : Double,
overdesign_fraction : Double,
) -> Double {
required_area_m2.max(0.0) * (1.0 + overdesign_fraction.max(0.0))
}
///|
pub fn heat_recovery_fraction(
recovered_duty_kw : Double,
available_hot_duty_kw : Double,
) -> Double {
if available_hot_duty_kw <= 0.0 {
0.0
} else {
(recovered_duty_kw / available_hot_duty_kw).max(0.0).min(1.0)
}
}
///|
pub fn utility_target_after_recovery(
process_duty_kw : Double,
recovered_duty_kw : Double,
) -> Double {
(process_duty_kw - recovered_duty_kw).max(0.0)
}