Thermodynamics/Property tables and charts/CoolProp/Saturated water - Temperature table/Code
Appearance
"""Reproduce thermodynamic data generated by the Wikiversity tool.
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 = 'Temperature'
PRESSURE_AXIS = 'Pressure'
SATURATED = 'Saturated'
PRESSURES_MPA = []
TEMPERATURES_C = [0.01,
5.0,
10.0,
15.0,
20.0,
25.0,
30.0,
35.0,
40.0,
45.0,
50.0,
55.0,
60.0,
65.0,
70.0,
75.0,
80.0,
85.0,
90.0,
95.0,
100.0,
105.0,
110.0,
115.0,
120.0,
125.0,
130.0,
135.0,
140.0,
145.0,
150.0,
155.0,
160.0,
165.0,
170.0,
175.0,
180.0,
185.0,
190.0,
195.0,
200.0,
205.0,
210.0,
215.0,
220.0,
225.0,
230.0,
235.0,
240.0,
245.0,
250.0,
255.0,
260.0,
265.0,
270.0,
275.0,
280.0,
285.0,
290.0,
295.0,
300.0,
305.0,
310.0,
315.0,
320.0,
325.0,
330.0,
335.0,
340.0,
345.0,
350.0,
355.0,
360.0,
365.0,
370.0,
373.94]
COLUMN_KEYS = ['t_c',
'p_bar',
'v_f',
'v_g',
'u_f',
'u_fg',
'u_g',
'h_f',
'h_fg',
'h_g',
's_f',
's_fg',
's_g']
COLUMN_UNITS = {'t_c': '°C',
'p_bar': 'kPa',
'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 = {'t_c': 'temperature',
'p_bar': 'pressure',
'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 = {'t_c': '°C',
'p_bar': 'bar',
'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 temperature, T_sat (°C)',
'Saturation pressure, p_sat (kPa)',
'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 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_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([convert_value(values[key], key) for key in COLUMN_KEYS])