Heating values#

The heating value of a fuel is the heat of combustion (i.e., heat released during combustion) at the reference temperature, \(T_{\text{ref}}\) = 25°C, assuming complete combustion. This is a useful quantity, commonly used to represent the energy density of a fuel, or the thermal energy that can be obtained from a fuel.

We can calculate the heating value using a steady-state energy balance on the stoichiometric reaction per 1 kmole of fuel, at constant temperature, and assuming complete combustion. The heating value is then

\[ HV = H_R - H_P \;, \]

where \(H_R\) is the enthalpy of the reactants (per kmol of fuel) and \(H_P\) is the enthalpy of the products (per kmol of fuel).

For example, let’s find the heating value of propane (C\(_3\)H\(_8\)). Its balanced overall chemical reaction is:

\[ \text{C}_3 \text{H}_8 + 5 \text{O}_2 \rightarrow 3 \text{CO}_2 + 4 \text{H}_2 \text{O} \]

(We do not need to consider nitrogen, or worry about excess air, because the nitrogen and unreacted oxygen would have the same state in the reactants and products.)

There are two heating values:

  • lower heating value: water in the products is vapor

  • higher heating value: water in the products is liquid

Let’s calculate the lower heating value first:

import numpy as np
import cantera as ct
import pandas as pd

from pint import UnitRegistry
ureg = UnitRegistry()
Q_ = ureg.Quantity

# for convenience:
def to_si(quant):
    '''Converts a Pint Quantity to magnitude at base SI units.
    '''
    return quant.to_base_units().magnitude
# fixed information

temperature = Q_(25, 'degC').to('K')
pressure = Q_(1, 'atm')

Lower heating value#

To calculate the enthalpy of the reactants and products, we add up the specific molar enthalpy of each species:

\[\begin{split} H = \sum_{i=1}^C n_i \overline{h}_{i} \left( T_{\text{ref}} \right) \\ \rightarrow H = n \sum_{i=1}^C y_i \overline{h}_{i} \left( T_{\text{ref}} \right) \;, \end{split}\]

where \(C\) is the number of components in the reactants/products, \(n_i\) is the number of moles of component \(i\), \(\overline{h}_{i} (T)\) is the molar specific enthalpy of component \(i\), \(n\) is the total number of moles in the reactants/products, and \(y_i\) is the mole fraction of component \(i\) in the reactants/products.

gas = ct.Solution('gri30.yaml')

molecular_weight_propane = Q_(44.10, 'kg/kmol')

# set state for reactants
gas.TPX = to_si(temperature), to_si(pressure), 'C3H8:1, O2:5'
moles_reactants = 1 + 5 # per mole of fuel
enthalpy_reactants = moles_reactants * Q_(gas.enthalpy_mole, 'J/kmol')

# product state
gas.TPX = to_si(temperature), to_si(pressure), 'CO2:3, H2O:4'
moles_products = 3 + 4 # per mole of fuel
enthalpy_products = moles_products * Q_(gas.enthalpy_mole, 'J/kmol')

heating_value_lower = (
    enthalpy_reactants - enthalpy_products
    ) / molecular_weight_propane
print(f'Lower heating value: {heating_value_lower.to("kJ/kg"): .0f}')
Lower heating value: 46348 kilojoule / kilogram

Higher heating value#

The higher heating value represents the case where all the water in the products is in the liquid phase. We can calculate this by adding the difference between the enthalpies of saturated water vapor and saturated liquid water at the reference temperature (times the number of moles of water):

\[ \Delta \overline{h}_{fg} = \overline{h}_g \left( T_{\text{ref}} \right) - \overline{h}_f \left( T_{\text{ref}} \right) \;, \]

which is the specific enthalpy change of vaporization.

We can obtain this value by finding the molar specific enthalpy of saturated liquid water and saturated water vapor, by specifying the temperature and vapor fractions of 0 and 1 (e.g., with TX):

water = ct.Water()

# liquid water
water.TQ = to_si(temperature), 0.0
enthalpy_liquid = Q_(water.enthalpy_mole, 'J/kmol')

# water vapor
water.TQ = to_si(temperature), 1.0
enthalpy_vapor = Q_(water.enthalpy_mole, 'J/kmol')

# moles of water in the products, per mole of fuel
moles_water = 4

heating_value_higher = (
    enthalpy_reactants - enthalpy_products + 
    (enthalpy_vapor - enthalpy_liquid) * moles_water
    ) / molecular_weight_propane
print(f'Higher heating value: {heating_value_higher.to("kJ/kg"): .0f}')
Higher heating value: 50339 kilojoule / kilogram

Calculate for other common fuels#

We can generalize the above calculations and find the higher and lower heating values for various common fuels.

In this case, we need to determine the composition of the complete combustion products automatically. We can do this by recognizing that all carbon from the fuel ends up as CO\(_2\), all hydrogen as H\(_2\)O, and all nitrogen as N\(_2\) (if any in the fuel). The elemental_mole_fraction function provides this for the reactant mixture.

def get_heating_values(fuel):
    """Returns lower and higher heating values for fuel
    
    Arguments:
    fuel -- species name for fuel (str)
    
    Returns:
    lower heating value, higher heating value
    """
    temperature = Q_(25, 'degC').to('K')
    pressure = Q_(1, 'atm')
    
    gas = ct.Solution('gri30.yaml')
    gas.TP = to_si(temperature), to_si(pressure)
    gas.set_equivalence_ratio(1.0, fuel, 'O2:1.0')
    
    enthalpy_reactants = Q_(gas.enthalpy_mass, 'J/kg')
    mass_fraction_fuel = gas.mass_fraction_dict()[fuel]
    
    mass_fractions_products = {
        'CO2': gas.elemental_mole_fraction('C'),
        'H2O': 0.5 * gas.elemental_mole_fraction('H'),
        'N2': 0.5 * gas.elemental_mole_fraction('N')
        }
    
    gas.TPX = to_si(temperature), to_si(pressure), mass_fractions_products
    mass_fraction_water = gas.mass_fraction_dict()['H2O']
    enthalpy_products = Q_(gas.enthalpy_mass, 'J/kg')
    
    heating_value_lower = (
        enthalpy_reactants - enthalpy_products
        ) / mass_fraction_fuel
    
    water = ct.Water()

    # liquid water
    water.TQ = to_si(temperature), 0.0
    enthalpy_liquid = Q_(water.enthalpy_mass, 'J/kg')

    # water vapor
    water.TQ = to_si(temperature), 1.0
    enthalpy_vapor = Q_(water.enthalpy_mass, 'J/kg')
    
    heating_value_higher = (
        enthalpy_reactants - enthalpy_products + 
        (enthalpy_vapor - enthalpy_liquid) * mass_fraction_water
        ) / mass_fraction_fuel
    
    return heating_value_lower, heating_value_higher
fuels = {
    'H2': 'hydrogen', 'NH3': 'ammonia',
    'CH4': 'methane', 'C2H6': 'ethane', 'C2H4': 'ethylene', 
    'C2H2': 'acetylene', 'C3H8': 'propane',
    }

df = pd.DataFrame(
    [], 
    columns=['Name', 'Lower heating value (kJ/kg)', 
             'Higher heating value (kJ/kg)'
            ],
    index=fuels.keys()
    )

for fuel, name in fuels.items():
    LHV, HHV = get_heating_values(fuel)
    df['Name'][fuel] = name
    df['Lower heating value (kJ/kg)'][fuel] = LHV.to('kJ/kg').magnitude
    df['Higher heating value (kJ/kg)'][fuel] = HHV.to('kJ/kg').magnitude

df.style.format({
    'Lower heating value (kJ/kg)': '{:,.0f}',
    'Higher heating value (kJ/kg)': '{:,.0f}',
    })
  Name Lower heating value (kJ/kg) Higher heating value (kJ/kg)
H2 hydrogen 119,953 141,777
NH3 ammonia 18,604 22,479
CH4 methane 50,025 55,510
C2H6 ethane 47,510 51,900
C2H4 ethylene 47,165 50,302
C2H2 acetylene 48,277 49,967
C3H8 propane 46,352 50,343