Thermodynamics/Property tables and charts/CoolProp/Saturated water - Pressure table/Code
Appearance
"""Reproduce thermodynamic data.
Requirements: Python 3 and CoolProp 8.0.0.
The CSV values are intentionally not rounded or formatted for presentation.
"""
import csv
import sys
import CoolProp
from CoolProp.CoolProp import PhaseSI, PropsSI, set_reference_state
# Ensure unit symbols and property names are portable when output is redirected.
if hasattr(sys.stdout, "reconfigure"):
sys.stdout.reconfigure(encoding="utf-8", newline="")
REQUIRED_COOLPROP_VERSION = '8.0.0'
FLUID = 'Water'
FLUID_REF = f"HEOS::{FLUID}"
REFERENCE_CONVENTION = 'DEF'
STATE = 'Saturated'
AXIS = 'Pressure'
PRESSURE_AXIS = 'Pressure'
SATURATED = 'Saturated'
PRESSURES_MPA = [0.001,
0.0015,
0.002,
0.0025,
0.003,
0.004,
0.005,
0.0075,
0.01,
0.015,
0.02,
0.025,
0.03,
0.04,
0.05,
0.075,
0.1,
0.101325,
0.125,
0.15,
0.17500000000000002,
0.2,
0.225,
0.25,
0.275,
0.3,
0.325,
0.35000000000000003,
0.375,
0.4,
0.45,
0.5,
0.55,
0.6,
0.65,
0.7000000000000001,
0.75,
0.8,
0.85,
0.9,
0.9500000000000001,
1.0,
1.1,
1.2,
1.3,
1.4000000000000001,
1.5,
1.75,
2.0,
2.25,
2.5,
3.0,
3.5,
4.0,
5.0,
6.0,
7.0,
8.0,
9.0,
10.0,
11.0,
12.0,
13.0,
14.0,
15.0,
16.0,
17.0,
18.0,
19.0,
20.0,
21.0,
22.0,
22.064]
TEMPERATURES_C = []
COLUMN_KEYS = ['p_mpa',
't_c',
'v_f',
'v_g',
'u_f',
'u_fg',
'u_g',
'h_f',
'h_fg',
'h_g',
's_f',
's_fg',
's_g']
COLUMN_UNITS = {'p_mpa': 'kPa',
't_c': '°C',
'v_f': 'm³/kg',
'v_g': 'm³/kg',
'u_f': 'kJ/kg',
'u_fg': 'kJ/kg',
'u_g': 'kJ/kg',
'h_f': 'kJ/kg',
'h_fg': 'kJ/kg',
'h_g': 'kJ/kg',
's_f': 'kJ/(kg·K)',
's_fg': 'kJ/(kg·K)',
's_g': 'kJ/(kg·K)'}
COLUMN_GROUPS = {'p_mpa': 'pressure',
't_c': 'temperature',
'v_f': 'specific_volume',
'v_g': 'specific_volume',
'u_f': 'internal_energy',
'u_fg': 'internal_energy',
'u_g': 'internal_energy',
'h_f': 'enthalpy',
'h_fg': 'enthalpy',
'h_g': 'enthalpy',
's_f': 'entropy',
's_fg': 'entropy',
's_g': 'entropy'}
DEFAULT_COLUMN_UNITS = {'p_mpa': 'MPa',
't_c': '°C',
'v_f': 'm³/kg',
'v_g': 'm³/kg',
'u_f': 'kJ/kg',
'u_fg': 'kJ/kg',
'u_g': 'kJ/kg',
'h_f': 'kJ/kg',
'h_fg': 'kJ/kg',
'h_g': 'kJ/kg',
's_f': 'kJ/(kg·K)',
's_fg': 'kJ/(kg·K)',
's_g': 'kJ/(kg·K)'}
CSV_HEADERS = ['Saturation pressure, p_sat (kPa)',
'Saturation temperature, T_sat (°C)',
'Liquid specific volume, v_f (m³/kg)',
'Vapor specific volume, v_g (m³/kg)',
'Liquid internal energy, u_f (kJ/kg)',
'Internal energy of vaporization, u_fg (kJ/kg)',
'Vapor internal energy, u_g (kJ/kg)',
'Liquid enthalpy, h_f (kJ/kg)',
'Enthalpy of vaporization, h_fg (kJ/kg)',
'Vapor enthalpy, h_g (kJ/kg)',
'Liquid entropy, s_f (kJ/(kg·K))',
'Entropy of vaporization, s_fg (kJ/(kg·K))',
'Vapor entropy, s_g (kJ/(kg·K))']
if CoolProp.__version__ != REQUIRED_COOLPROP_VERSION:
raise RuntimeError(
f"CoolProp {REQUIRED_COOLPROP_VERSION} is required; "
f"found {CoolProp.__version__}."
)
set_reference_state(FLUID, REFERENCE_CONVENTION)
def convert_value(value, key):
"""Convert a calculated value from the tool's base unit to its selected unit."""
unit = COLUMN_UNITS[key]
default = DEFAULT_COLUMN_UNITS[key]
group = COLUMN_GROUPS[key]
if unit == default or unit == "—" or not isinstance(value, (int, float)):
return value
if group == "pressure":
base_mpa = value / 10.0 if key == "p_bar" else value / 1000.0 if key == "p_kpa" else value
return {
"MPa": base_mpa,
"kPa": base_mpa * 1000.0,
"bar": base_mpa * 10.0,
"Pa": base_mpa * 1_000_000.0,
"psi": base_mpa * 145.03773773,
}[unit]
if group == "temperature":
return {
"°C": value,
"K": value + 273.15,
"°F": value * 9.0 / 5.0 + 32.0,
"°R": (value + 273.15) * 9.0 / 5.0,
}[unit]
if group == "specific_volume":
return value * 16.01846337
if group == "density":
return value * 0.06242796058
if group in {"internal_energy", "enthalpy"}:
return value * (1000.0 if unit == "J/kg" else 0.429922614)
if group in {"entropy", "heat_capacity"}:
return value * (1000.0 if unit == "J/(kg·K)" else 0.238845897)
if group == "acoustic":
return value * 3.280839895
if key == "viscosity":
return value / (1_000_000.0 if unit == "Pa·s" else 1000.0)
if key == "conductivity":
return value * 0.577789318
return value
def normalize_negative_zero(value):
"""Return unsigned zero without rounding any nonzero CSV value."""
if isinstance(value, float) and value == 0.0:
return 0.0
return value
def saturation_values(independent_value):
input_key = "P" if AXIS == PRESSURE_AXIS else "T"
input_value = (
independent_value * 1_000_000.0
if AXIS == PRESSURE_AXIS
else independent_value + 273.15
)
if AXIS == PRESSURE_AXIS:
critical_pressure = PropsSI("pcrit", FLUID_REF)
if 0.0 < input_value - critical_pressure <= 1e-3:
input_value = critical_pressure
else:
critical_temperature = PropsSI("Tcrit", FLUID_REF)
if 0.0 < input_value - critical_temperature <= 1e-6:
input_value = critical_temperature
pressure = PropsSI("P", input_key, input_value, "Q", 0, FLUID_REF)
temperature = PropsSI("T", input_key, input_value, "Q", 0, FLUID_REF)
def prop(output, quality, scale=1.0):
return PropsSI(output, input_key, input_value, "Q", quality, FLUID_REF) / scale
density_liquid = prop("Dmass", 0)
density_vapor = prop("Dmass", 1)
internal_energy_liquid = prop("Umass", 0, 1000.0)
internal_energy_vapor = prop("Umass", 1, 1000.0)
enthalpy_liquid = prop("Hmass", 0, 1000.0)
enthalpy_vapor = prop("Hmass", 1, 1000.0)
entropy_liquid = prop("Smass", 0, 1000.0)
entropy_vapor = prop("Smass", 1, 1000.0)
return {
"p_mpa": pressure / 1_000_000.0,
"p_kpa": pressure / 1_000.0,
"p_bar": pressure / 100_000.0,
"t_c": temperature - 273.15,
"v_f": 1.0 / density_liquid,
"v_g": 1.0 / density_vapor,
"rho_f": density_liquid,
"rho_g": density_vapor,
"u_f": internal_energy_liquid,
"u_g": internal_energy_vapor,
"u_fg": internal_energy_vapor - internal_energy_liquid,
"h_f": enthalpy_liquid,
"h_g": enthalpy_vapor,
"h_fg": enthalpy_vapor - enthalpy_liquid,
"s_f": entropy_liquid,
"s_g": entropy_vapor,
"s_fg": entropy_vapor - entropy_liquid,
"cp_f": prop("Cpmass", 0, 1000.0),
"cp_g": prop("Cpmass", 1, 1000.0),
}
def single_phase_values(pressure_mpa, temperature_c):
pressure = pressure_mpa * 1_000_000.0
temperature = temperature_c + 273.15
phase = PhaseSI("P", pressure, "T", temperature, FLUID_REF)
def prop(output, scale=1.0):
return PropsSI(output, "P", pressure, "T", temperature, FLUID_REF) / scale
density = prop("Dmass")
return {
"p_mpa": pressure_mpa,
"p_kpa": pressure_mpa * 1000.0,
"p_bar": pressure_mpa * 10.0,
"t_c": temperature_c,
"phase": phase,
"v": 1.0 / density,
"rho": density,
"u": prop("Umass", 1000.0),
"h": prop("Hmass", 1000.0),
"s": prop("Smass", 1000.0),
"cp": prop("Cpmass", 1000.0),
"cv": prop("Cvmass", 1000.0),
"speed_sound": prop("speed_of_sound"),
"viscosity": prop("viscosity") * 1_000_000.0,
"conductivity": prop("conductivity"),
"prandtl": prop("Prandtl"),
}
def requested_phase(phase):
return (
(STATE == "Subcooled liquid" and phase == "liquid")
or (STATE == "Superheated vapor" and phase == "gas")
)
def rows():
if STATE == SATURATED:
independent_values = PRESSURES_MPA if AXIS == PRESSURE_AXIS else TEMPERATURES_C
for independent_value in independent_values:
yield saturation_values(independent_value)
return
for pressure_mpa in PRESSURES_MPA:
for temperature_c in TEMPERATURES_C:
try:
values = single_phase_values(pressure_mpa, temperature_c)
except ValueError:
continue
if requested_phase(values["phase"]):
yield values
writer = csv.writer(sys.stdout, lineterminator="\n")
writer.writerow(CSV_HEADERS)
for values in rows():
writer.writerow([
normalize_negative_zero(convert_value(values[key], key))
for key in COLUMN_KEYS
])