Jump to content

Thermodynamics/Property tables and charts/CoolProp/Saturated water - Temperature table/Code

"""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])