///|
pub struct UtilityDesign {
name : String
duty_w : Double
required_mass_flow_kg_per_s : Double
inlet_temperature_c : Double
outlet_temperature_c : Double
cost_index : Double
} derive(Debug)
///|
pub fn steam_utility(
mass_flow_kg_per_s : Double,
inlet_temperature_c : Double,
outlet_temperature_c : Double,
) -> UtilityDesign {
let duty = mass_flow_kg_per_s.max(0.0) *
(
saturated_vapor_enthalpy_kj_per_kg(outlet_temperature_c) -
saturated_liquid_enthalpy_kj_per_kg(inlet_temperature_c)
) *
1000.0
{
name: "medium-pressure steam",
duty_w: duty,
required_mass_flow_kg_per_s: mass_flow_kg_per_s.max(0.0),
inlet_temperature_c,
outlet_temperature_c,
cost_index: 1.0,
}
}
///|
pub fn cooling_water_utility(
duty_w : Double,
inlet_temperature_c : Double,
outlet_temperature_c : Double,
) -> UtilityDesign {
let delta = (outlet_temperature_c - inlet_temperature_c).abs()
let flow = if delta <= 0.0 { 0.0 } else { duty_w.abs() / (4180.0 * delta) }
{
name: "cooling water",
duty_w: -duty_w.abs(),
required_mass_flow_kg_per_s: flow,
inlet_temperature_c,
outlet_temperature_c,
cost_index: 0.2,
}
}
///|
pub fn chilled_water_utility(
duty_w : Double,
inlet_temperature_c : Double,
outlet_temperature_c : Double,
) -> UtilityDesign {
let delta = (outlet_temperature_c - inlet_temperature_c).abs()
let flow = if delta <= 0.0 { 0.0 } else { duty_w.abs() / (4180.0 * delta) }
{
name: "chilled water",
duty_w: -duty_w.abs(),
required_mass_flow_kg_per_s: flow,
inlet_temperature_c,
outlet_temperature_c,
cost_index: 0.8,
}
}
///|
pub fn thermal_oil_utility(
duty_w : Double,
inlet_temperature_c : Double,
outlet_temperature_c : Double,
cp_j_per_kg_k : Double,
) -> UtilityDesign {
let delta = (outlet_temperature_c - inlet_temperature_c).abs()
let flow = if delta <= 0.0 || cp_j_per_kg_k <= 0.0 {
0.0
} else {
duty_w.abs() / (cp_j_per_kg_k * delta)
}
{
name: "thermal oil",
duty_w: duty_w.abs(),
required_mass_flow_kg_per_s: flow,
inlet_temperature_c,
outlet_temperature_c,
cost_index: 0.6,
}
}
///|
pub fn utility_cost_index(design : UtilityDesign) -> Double {
design.required_mass_flow_kg_per_s * design.cost_index
}
///|
pub fn select_utility(
duty_w : Double,
candidates : Array[UtilityDesign],
) -> UtilityDesign {
if candidates.length() == 0 {
{
name: "none",
duty_w: 0.0,
required_mass_flow_kg_per_s: 0.0,
inlet_temperature_c: 0.0,
outlet_temperature_c: 0.0,
cost_index: 0.0,
}
} else {
let mut selected = candidates[0]
let mut best_score = 1.0e30
for candidate in candidates {
let matches = if duty_w >= 0.0 {
candidate.duty_w >= 0.0
} else {
candidate.duty_w <= 0.0
}
let score = if matches { utility_cost_index(candidate) } else { 1.0e30 }
if score < best_score {
best_score = score
selected = candidate
}
}
selected
}
}
///|
pub fn pump_power_w(
mass_flow_kg_per_s : Double,
pressure_rise_pa : Double,
efficiency : Double,
) -> Double {
if mass_flow_kg_per_s <= 0.0 || pressure_rise_pa <= 0.0 || efficiency <= 0.0 {
0.0
} else {
mass_flow_kg_per_s * pressure_rise_pa / efficiency.min(1.0)
}
}
///|
pub fn pump_head_m(
pressure_rise_pa : Double,
density_kg_per_m3 : Double,
) -> Double {
if density_kg_per_m3 <= 0.0 {
0.0
} else {
pressure_rise_pa / (density_kg_per_m3 * 9.80665)
}
}
///|
pub fn compressor_power_w(
mass_flow_kg_per_s : Double,
inlet_pressure_kpa : Double,
outlet_pressure_kpa : Double,
efficiency : Double,
heat_capacity_ratio : Double,
) -> Double {
if mass_flow_kg_per_s <= 0.0 ||
inlet_pressure_kpa <= 0.0 ||
outlet_pressure_kpa <= inlet_pressure_kpa ||
efficiency <= 0.0 ||
heat_capacity_ratio <= 1.0 {
0.0
} else {
let exponent = (heat_capacity_ratio - 1.0) / heat_capacity_ratio
let pressure_ratio = outlet_pressure_kpa / inlet_pressure_kpa
let specific = heat_capacity_ratio /
(heat_capacity_ratio - 1.0) *
287.0 *
celsius_to_kelvin(20.0) *
@math.pow(pressure_ratio, exponent)
mass_flow_kg_per_s * specific / efficiency.min(1.0)
}
}
///|
pub fn pipe_cross_section_m2(diameter_m : Double) -> Double {
if diameter_m <= 0.0 {
0.0
} else {
0.25 * @math.PI * diameter_m * diameter_m
}
}
///|
pub fn pipe_velocity_m_per_s(
volumetric_flow_m3_per_s : Double,
diameter_m : Double,
) -> Double {
let area = pipe_cross_section_m2(diameter_m)
if area <= 0.0 || volumetric_flow_m3_per_s <= 0.0 {
0.0
} else {
volumetric_flow_m3_per_s / area
}
}
///|
pub fn reynolds_number(
density_kg_per_m3 : Double,
velocity_m_per_s : Double,
diameter_m : Double,
viscosity_pa_s : Double,
) -> Double {
if viscosity_pa_s <= 0.0 {
0.0
} else {
density_kg_per_m3 * velocity_m_per_s * diameter_m / viscosity_pa_s
}
}
///|
pub fn friction_factor(reynolds : Double, roughness_ratio : Double) -> Double {
if reynolds <= 0.0 {
0.0
} else if reynolds < 2300.0 {
64.0 / reynolds
} else {
let argument = roughness_ratio / 3.7 + 5.74 / @math.pow(reynolds, 0.9)
0.25 / @math.pow(@math.log10(argument), 2.0)
}
}
///|
pub fn pressure_drop_pa(
density_kg_per_m3 : Double,
diameter_m : Double,
length_m : Double,
roughness_m : Double,
viscosity_pa_s : Double,
) -> Double {
let velocity = pipe_velocity_m_per_s(1.0, diameter_m)
let reynolds = reynolds_number(
density_kg_per_m3, velocity, diameter_m, viscosity_pa_s,
)
let factor = friction_factor(reynolds, roughness_m / diameter_m.max(1.0e-12))
factor *
length_m.max(0.0) /
diameter_m.max(1.0e-12) *
density_kg_per_m3 *
velocity *
velocity /
2.0
}
///|
pub fn heat_loss_to_ambient(
area_m2 : Double,
overall_u_w_per_m2_k : Double,
process_temperature_c : Double,
ambient_temperature_c : Double,
) -> Double {
area_m2.max(0.0) *
overall_u_w_per_m2_k.max(0.0) *
(process_temperature_c - ambient_temperature_c)
}