Skip to content

Thermo

fpc.thermo

thermo.py - thermodynamics calculations.

Thermodynamics tracks energy as it moves between heat and work. These helpers cover the ideal gas law, heat transfer by conduction and convection, sensible heat, the Carnot efficiency limit on heat engines, and the log-mean temperature difference used to size heat exchangers.

Thermo

Thermodynamics helpers.

Source code in fpc/thermo.py
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
class Thermo:
    """Thermodynamics helpers."""

    # Universal gas constant [J/(mol*K)].
    R_UNIVERSAL = 8.314462618

    def ideal_gas_pressure(self, *, moles, temperature, volume):
        """Pressure from the ideal gas law: P = n*R*T / V.

        Temperatures MUST be absolute (kelvin) - a common beginner mistake is
        to pass celsius. Use ``celsius_to_kelvin`` first if needed.

        :param moles: amount of substance n [mol]
        :param temperature: absolute temperature T [K]
        :param volume: volume V [m^3]
        :returns: pressure [Pa]
        """
        return moles * self.R_UNIVERSAL * temperature / volume

    def sensible_heat(self, *, mass, specific_heat, delta_temperature):
        """Heat to change a substance's temperature: Q = m*c*dT.

        :param mass: mass [kg]
        :param specific_heat: specific heat capacity c [J/(kg*K)]
        :param delta_temperature: temperature change dT [K or degC, same size]
        :returns: heat energy [J] (positive = added, negative = removed)
        """
        return mass * specific_heat * delta_temperature

    def conduction_heat_rate(self, *, conductivity, area, delta_temperature, thickness):
        """Steady heat conduction through a wall (Fourier's law).

        Q_dot = k * A * dT / L.

        :param conductivity: thermal conductivity k [W/(m*K)]
        :param area: cross-sectional area A [m^2]
        :param delta_temperature: temperature difference across the wall [K]
        :param thickness: wall thickness L [m]
        :returns: heat transfer rate [W]
        """
        return conductivity * area * delta_temperature / thickness

    def convection_heat_rate(self, *, coefficient, area, delta_temperature):
        """Convective heat transfer (Newton's law of cooling).

        Q_dot = h * A * dT.

        :param coefficient: convective heat transfer coefficient h [W/(m^2*K)]
        :param area: surface area A [m^2]
        :param delta_temperature: surface-to-fluid temperature difference [K]
        :returns: heat transfer rate [W]
        """
        return coefficient * area * delta_temperature

    def carnot_efficiency(self, *, hot_temperature, cold_temperature):
        """Maximum possible efficiency of a heat engine: 1 - Tc/Th.

        Both temperatures are absolute (kelvin). No real engine can beat this.

        :param hot_temperature: hot reservoir temperature Th [K]
        :param cold_temperature: cold reservoir temperature Tc [K]
        :returns: efficiency as a fraction between 0 and 1
        """
        return 1 - (cold_temperature / hot_temperature)

    def celsius_to_kelvin(self, *, celsius):
        """Convert temperature from degrees Celsius to kelvin.

        :param celsius: temperature [degC]
        :returns: temperature [K]
        """
        return celsius + 273.15

    def log_mean_temperature_difference(self, *, delta_t_1, delta_t_2):
        """Log-mean temperature difference (LMTD) for a heat exchanger.

        LMTD = (dT1 - dT2) / ln(dT1 / dT2), where dT1 and dT2 are the hot-cold
        temperature differences at the two ends of the exchanger. The duty is
        then Q = U * A * LMTD. When dT1 == dT2 the formula is 0/0; its limit
        is simply dT1, which we return directly.

        For counterflow: dT1 = Th_in - Tc_out, dT2 = Th_out - Tc_in.
        For parallel flow: dT1 = Th_in - Tc_in, dT2 = Th_out - Tc_out.

        :param delta_t_1: temperature difference at one end [K]
        :param delta_t_2: temperature difference at the other end [K]
        :returns: log-mean temperature difference [K]
        :raises ValueError: if either difference is not positive (that would
            mean the streams cross, which a simple exchanger cannot do)
        """
        if delta_t_1 <= 0 or delta_t_2 <= 0:
            raise ValueError("both end temperature differences must be positive")
        if math.isclose(delta_t_1, delta_t_2):
            return delta_t_1
        return (delta_t_1 - delta_t_2) / math.log(delta_t_1 / delta_t_2)

ideal_gas_pressure(*, moles, temperature, volume)

Pressure from the ideal gas law: P = nRT / V.

Temperatures MUST be absolute (kelvin) - a common beginner mistake is to pass celsius. Use celsius_to_kelvin first if needed.

Parameters:

Name Type Description Default
moles

amount of substance n [mol]

required
temperature

absolute temperature T [K]

required
volume

volume V [m^3]

required

Returns:

Type Description

pressure [Pa]

Source code in fpc/thermo.py
21
22
23
24
25
26
27
28
29
30
31
32
def ideal_gas_pressure(self, *, moles, temperature, volume):
    """Pressure from the ideal gas law: P = n*R*T / V.

    Temperatures MUST be absolute (kelvin) - a common beginner mistake is
    to pass celsius. Use ``celsius_to_kelvin`` first if needed.

    :param moles: amount of substance n [mol]
    :param temperature: absolute temperature T [K]
    :param volume: volume V [m^3]
    :returns: pressure [Pa]
    """
    return moles * self.R_UNIVERSAL * temperature / volume

sensible_heat(*, mass, specific_heat, delta_temperature)

Heat to change a substance's temperature: Q = mcdT.

Parameters:

Name Type Description Default
mass

mass [kg]

required
specific_heat

specific heat capacity c [J/(kg*K)]

required
delta_temperature

temperature change dT [K or degC, same size]

required

Returns:

Type Description

heat energy [J] (positive = added, negative = removed)

Source code in fpc/thermo.py
34
35
36
37
38
39
40
41
42
def sensible_heat(self, *, mass, specific_heat, delta_temperature):
    """Heat to change a substance's temperature: Q = m*c*dT.

    :param mass: mass [kg]
    :param specific_heat: specific heat capacity c [J/(kg*K)]
    :param delta_temperature: temperature change dT [K or degC, same size]
    :returns: heat energy [J] (positive = added, negative = removed)
    """
    return mass * specific_heat * delta_temperature

conduction_heat_rate(*, conductivity, area, delta_temperature, thickness)

Steady heat conduction through a wall (Fourier's law).

Q_dot = k * A * dT / L.

Parameters:

Name Type Description Default
conductivity

thermal conductivity k [W/(m*K)]

required
area

cross-sectional area A [m^2]

required
delta_temperature

temperature difference across the wall [K]

required
thickness

wall thickness L [m]

required

Returns:

Type Description

heat transfer rate [W]

Source code in fpc/thermo.py
44
45
46
47
48
49
50
51
52
53
54
55
def conduction_heat_rate(self, *, conductivity, area, delta_temperature, thickness):
    """Steady heat conduction through a wall (Fourier's law).

    Q_dot = k * A * dT / L.

    :param conductivity: thermal conductivity k [W/(m*K)]
    :param area: cross-sectional area A [m^2]
    :param delta_temperature: temperature difference across the wall [K]
    :param thickness: wall thickness L [m]
    :returns: heat transfer rate [W]
    """
    return conductivity * area * delta_temperature / thickness

convection_heat_rate(*, coefficient, area, delta_temperature)

Convective heat transfer (Newton's law of cooling).

Q_dot = h * A * dT.

Parameters:

Name Type Description Default
coefficient

convective heat transfer coefficient h [W/(m^2*K)]

required
area

surface area A [m^2]

required
delta_temperature

surface-to-fluid temperature difference [K]

required

Returns:

Type Description

heat transfer rate [W]

Source code in fpc/thermo.py
57
58
59
60
61
62
63
64
65
66
67
def convection_heat_rate(self, *, coefficient, area, delta_temperature):
    """Convective heat transfer (Newton's law of cooling).

    Q_dot = h * A * dT.

    :param coefficient: convective heat transfer coefficient h [W/(m^2*K)]
    :param area: surface area A [m^2]
    :param delta_temperature: surface-to-fluid temperature difference [K]
    :returns: heat transfer rate [W]
    """
    return coefficient * area * delta_temperature

carnot_efficiency(*, hot_temperature, cold_temperature)

Maximum possible efficiency of a heat engine: 1 - Tc/Th.

Both temperatures are absolute (kelvin). No real engine can beat this.

Parameters:

Name Type Description Default
hot_temperature

hot reservoir temperature Th [K]

required
cold_temperature

cold reservoir temperature Tc [K]

required

Returns:

Type Description

efficiency as a fraction between 0 and 1

Source code in fpc/thermo.py
69
70
71
72
73
74
75
76
77
78
def carnot_efficiency(self, *, hot_temperature, cold_temperature):
    """Maximum possible efficiency of a heat engine: 1 - Tc/Th.

    Both temperatures are absolute (kelvin). No real engine can beat this.

    :param hot_temperature: hot reservoir temperature Th [K]
    :param cold_temperature: cold reservoir temperature Tc [K]
    :returns: efficiency as a fraction between 0 and 1
    """
    return 1 - (cold_temperature / hot_temperature)

celsius_to_kelvin(*, celsius)

Convert temperature from degrees Celsius to kelvin.

Parameters:

Name Type Description Default
celsius

temperature [degC]

required

Returns:

Type Description

temperature [K]

Source code in fpc/thermo.py
80
81
82
83
84
85
86
def celsius_to_kelvin(self, *, celsius):
    """Convert temperature from degrees Celsius to kelvin.

    :param celsius: temperature [degC]
    :returns: temperature [K]
    """
    return celsius + 273.15

log_mean_temperature_difference(*, delta_t_1, delta_t_2)

Log-mean temperature difference (LMTD) for a heat exchanger.

LMTD = (dT1 - dT2) / ln(dT1 / dT2), where dT1 and dT2 are the hot-cold temperature differences at the two ends of the exchanger. The duty is then Q = U * A * LMTD. When dT1 == dT2 the formula is 0/0; its limit is simply dT1, which we return directly.

For counterflow: dT1 = Th_in - Tc_out, dT2 = Th_out - Tc_in. For parallel flow: dT1 = Th_in - Tc_in, dT2 = Th_out - Tc_out.

Parameters:

Name Type Description Default
delta_t_1

temperature difference at one end [K]

required
delta_t_2

temperature difference at the other end [K]

required

Returns:

Type Description

log-mean temperature difference [K]

Raises:

Type Description
ValueError

if either difference is not positive (that would mean the streams cross, which a simple exchanger cannot do)

Source code in fpc/thermo.py
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
def log_mean_temperature_difference(self, *, delta_t_1, delta_t_2):
    """Log-mean temperature difference (LMTD) for a heat exchanger.

    LMTD = (dT1 - dT2) / ln(dT1 / dT2), where dT1 and dT2 are the hot-cold
    temperature differences at the two ends of the exchanger. The duty is
    then Q = U * A * LMTD. When dT1 == dT2 the formula is 0/0; its limit
    is simply dT1, which we return directly.

    For counterflow: dT1 = Th_in - Tc_out, dT2 = Th_out - Tc_in.
    For parallel flow: dT1 = Th_in - Tc_in, dT2 = Th_out - Tc_out.

    :param delta_t_1: temperature difference at one end [K]
    :param delta_t_2: temperature difference at the other end [K]
    :returns: log-mean temperature difference [K]
    :raises ValueError: if either difference is not positive (that would
        mean the streams cross, which a simple exchanger cannot do)
    """
    if delta_t_1 <= 0 or delta_t_2 <= 0:
        raise ValueError("both end temperature differences must be positive")
    if math.isclose(delta_t_1, delta_t_2):
        return delta_t_1
    return (delta_t_1 - delta_t_2) / math.log(delta_t_1 / delta_t_2)