# Gas Turbine¶

This tutorial introduces a new component, the combustion chamber. You will learn how to use the component and set up a simple open cycle gas turbine: It compresses air and burns fuel in the combustion chamber. The hot and pressurized flue gas expands in the turbine, which drives the compressor and the generator. You will also learn, how to use the fluid composition as a variable in your simulation.

Download the full script here: gas_turbine.py

## Setting up the Combustion Chamber¶

We are setting up our system step by step. Especially for larger systems, it is recommended you follow this approach, since TESPy highly relies on a set of good starting values for good convergence. You can learn more about it in the advanced tutorial section of the online documentation.

Note

There are two different types of combustion chambers available:

Both can handle varying fluid compositions for the air and the fuel and calculate the fluid composition of the flue gas. Thus, it is possible to e.g. specify the oxygen mass fraction in the flue gas in a calculation. The difference between the components lies in the fact, that the CombustionChamber does not consider heat or pressure losses, while DiabaticCombustionChamber does so.

In this tutorial, we will use the tespy.components.combustion.diabatic.DiabaticCombustionChamber. First, we set up a network and the components.

from tespy.networks import Network
from tespy.components import (
DiabaticCombustionChamber, Turbine, Source, Sink, Compressor
)
from tespy.connections import Connection, Ref, Bus

# define full fluid list for the network"s variable space
nw = Network(p_unit="bar", T_unit="C")

cp = Compressor("Compressor")
cc = DiabaticCombustionChamber("combustion chamber")
tu = Turbine("turbine")
air = Source("air source")
fuel = Source("fuel source")
fg = Sink("flue gas sink")


In the first step, we do not connect the inlet of the combustion chamber with the compressor but with the air source instead. Similarly, the outlet of the combustion chamber is directly connected to the flue gas sink.

c2 = Connection(air, "out1", cc, "in1", label="2")
c3 = Connection(cc, "out1", fg, "in1", label="3")
c5 = Connection(fuel, "out1", cc, "in2", label="5")


There are many specifications possible. For the combustion chamber we will specify its air to stoichiometric air ratio lamb and the thermal input ($$LHV \cdot \dot{m}_{f}$$).

Furthermore, we specify the efficiency eta of the component, which determines the heat loss as ratio of the thermal input. eta=1 means, no heat losses, thus adiabatic behavior.

The pressure ratio pr describes the ratio of the pressure at the outlet to the pressure at the inlet 1. The pressure value at the inlet 2 is detached from the other pressure values, it must be a result of a different parameter specification. In this example, we set it directly. Initially, we assume adiabatic behavior eta=1 and no pressure losses pr=1.

The ambient conditions as well as the fuel gas inlet temperature are defined in the next step. The vectors for the air and the fuel gas composition have to be specified using the individual fluids. The component can not handle “Air” as input fluid. We can run the code after the specifications.

cc.set_attr(pr=1, eta=1, lamb=1.5, ti=10e6)

c2.set_attr(
p=1, T=20,
fluid={"Ar": 0.0129, "N2": 0.7553, "CO2": 0.0004, "O2": 0.2314}
)
c5.set_attr(p=1, T=20, fluid={"CO2": 0.04, "CH4": 0.96, "H2": 0})

nw.solve(mode="design")
nw.print_results()


Of course, you can change the parametrization in any desired way. For example instead of stating the thermal input, you could choose any of the mass flows:

cc.set_attr(ti=None)
c5.set_attr(m=1)
nw.solve(mode="design")


or instead of the air to stoichiometric air ratio you could specify the flue gas temperature.

cc.set_attr(lamb=None)
c3.set_attr(T=1400)
nw.solve(mode="design")


It is also possible to make modifications on the fluid composition, for example, we can add hydrogen to the fuel mixture.

c5.set_attr(fluid={"CO2": 0.03, "CH4": 0.92, "H2": 0.05})
nw.solve(mode="design")


The most convenient way to access the fluid composition is to access the results dataframe for the connections.

print(nw.results["Connection"])


Note

All component and connection results are available in the results dict of the Network instance. The keys of the dictionary are the respective class names.

## Setting up the Full System¶

After learning more about the component, we are going to add the remaining components: The turbine, the compressor and the generator. To do that, remove the existing connections from the network, create the new connections and add them to the network again. We also add a Bus representing the generator, assuming 98 % mechanical-electrical efficiency.

nw.del_conns(c2, c3)
c1 = Connection(air, "out1", cp, "in1", label="1")
c2 = Connection(cp, "out1", cc, "in1", label="2")
c3 = Connection(cc, "out1", tu, "in1", label="3")
c4 = Connection(tu, "out1", fg, "in1", label="4")

generator = Bus("generator")
{"comp": tu, "char": 0.98, "base": "component"},
{"comp": cp, "char": 0.98, "base": "bus"},
)


Since we deleted the connection 2 and 3, all specifications for those connections have to be added again. The air fluid composition is specified on connection 1 with ambient pressure and temperature. The compressor pressure ratio is set to 15 bar. Finally, set the gas turbine outlet pressure to ambient pressure as well as the compressor’s and turbine’s efficiency. We start with simulation which specifies a fixed value for the flue gas mass flow to generate good starting values. After that, the turbine inlet temperature is set to 1200 °C.

cp.set_attr(eta_s=0.85, pr=15)
tu.set_attr(eta_s=0.90)
c1.set_attr(
p=1, T=20,
fluid={"Ar": 0.0129, "N2": 0.7553, "CO2": 0.0004, "O2": 0.2314}
)
c3.set_attr(m=30)
c4.set_attr(p=Ref(c1, 1, 0))
nw.solve("design")
c3.set_attr(m=None, T=1200)
nw.solve("design")
nw.print_results()


Note, that the pressure of the fuel is lower than the pressure of the air at the combustion chamber as we did not change the pressure of connection 5. A respective warning is printed after the calculation. We can fix it like so:

# unset the value, set Referenced value instead
c5.set_attr(p=None)
c5.set_attr(p=Ref(c2, 1.05, 0))
nw.solve("design")


We can investigate, how the turbine inlet temperature and the compressor pressure ratio affect thermal efficiency and power generation. Also, we assume 2 % heat losses and 3 % pressure losses in the combustion chamber.

Click to expand to code section
cc.set_attr(pr=0.97, eta=0.98)
nw.set_attr(iterinfo=False)
import matplotlib.pyplot as plt
import numpy as np

# make text reasonably sized
plt.rc('font', **{'size': 18})

data = {
'T_3': np.linspace(900, 1400, 11),
'pr': np.linspace(10, 30, 11)
}
power = {
'T_3': [],
'pr': []
}
eta = {
'T_3': [],
'pr': []
}

for T in data['T_3']:
c3.set_attr(T=T)
nw.solve('design')
power['T_3'] += [abs(generator.P.val) / 1e6]
eta['T_3'] += [abs(generator.P.val) / cc.ti.val * 100]

# reset to base value
c3.set_attr(T=1200)

for pr in data['pr']:
cp.set_attr(pr=pr)
nw.solve('design')
power['pr'] += [abs(generator.P.val) / 1e6]
eta['pr'] += [abs(generator.P.val) / cc.ti.val * 100]

# reset to base value
cp.set_attr(pr=15)

fig, ax = plt.subplots(2, 2, figsize=(16, 8), sharex='col', sharey='row')

ax = ax.flatten()
[a.grid() for a in ax]

i = 0
for key in data:
ax[i].scatter(data[key], eta[key], s=100, color="#1f567d")
ax[i + 2].scatter(data[key], power[key], s=100, color="#18a999")
i += 1

ax[0].set_ylabel('Efficiency in %')
ax[2].set_ylabel('Power in MW')
ax[2].set_xlabel('Turbine inlet temperature °C')
ax[3].set_xlabel('Compressure pressure ratio')

plt.tight_layout()
fig.savefig('gas_turbine_parametric.svg')
plt.close()


## Fluid Composition Specifications¶

In this section you will learn how the fluid composition can be used as a variable in such systems. To begin, we can set the oxygen mass fraction on the flue gas instead of the turbine inlet pressure, since it determines the share of oxygen that is not required in the combustion. We can see, how the turbine inlet temperature correlates with the oxygen mass fraction.

Click to expand to code section
c3.set_attr(T=None)

data = np.linspace(0.1, 0.2, 6)
T3 = []

for oxy in data[::-1]:
c3.set_attr(fluid={"O2": oxy})
nw.solve('design')
T3 += [c3.T.val]

# reset to base value
c3.fluid.is_set.remove("O2")
c3.set_attr(T=1200)

fig, ax = plt.subplots(1, figsize=(16, 8))

ax.scatter(data * 100, T3, s=100, color="#1f567d")
ax.grid()

ax.set_ylabel('Turbine inlet temperature in °C')
ax.set_xlabel('Oxygen mass fraction in flue gas in %')

plt.tight_layout()
fig.savefig('gas_turbine_oxygen.svg')
plt.close()



Let us now assume, we do have an unknown shares of hydrogen and methane within our fuel mixture. With the known mass flow of the fuel and an overall thermal input, we can calculate both fractions by removing their respective values from the input parameters and using the fluid_balance keyword instead, which automatically calculates the sum of all fluid mass fractions to be 1.

Investigate how changing the thermal input requires a different mixture of hydrogen and methane.

Attention

With this setup, a thermal input below the lower heating value of methane or above the lower heating value of hydrogen (each multiplied with the mass flow of 1 kg/s) does not make sense as input specification. This is individual of every fluid you use as fuel, and you cannot easily abstract the values to any other combination.

Click to expand to code section
# fix mass fractions of all potential fluids except combustion gases
c5.set_attr(fluid={"CO2": 0.03, "O2": 0, "H2O": 0, "Ar": 0, "N2": 0, "CH4": None, "H2": None})
c5.set_attr(fluid_balance=True)

data = np.linspace(50, 60, 11)

CH4 = []
H2 = []

for ti in data:
cc.set_attr(ti=ti * 1e6)
nw.solve('design')
CH4 += [c5.fluid.val["CH4"] * 100]
H2 += [c5.fluid.val["H2"] * 100]

nw._convergence_check()

fig, ax = plt.subplots(1, figsize=(16, 8))

ax.scatter(data, CH4, s=100, color="#1f567d", label="CH4 mass fraction")
ax.scatter(data, H2, s=100, color="#18a999", label="H2 mass fraction")
ax.grid()
ax.legend()

ax.set_ylabel('Mass fraction of the fuel in %')
ax.set_xlabel('Thermal input in MW')
ax.set_ybound([0, 100])

plt.tight_layout()
fig.savefig('gas_turbine_fuel_composition.svg')
plt.close()