Rankine Cycle

Topology of the rankine cycle

Figure: Topology of the rankine cycle

Topology of the rankine cycle

Figure: Topology of the rankine cycle

Download the full script here: rankine.py

Setting up the Cycle

We will model the cycle including the cooling water of the condenser. For this start with the Network set up we already know.

from tespy.networks import Network

# create a network object with R134a as fluid
my_plant = Network()
my_plant.set_attr(T_unit='C', p_unit='bar', h_unit='kJ / kg')

Following, we create the components and connect them. The Condenser has a hot side inlet and outlet as well as a cold side inlet and outlet. The hot side is indicated by using the index 1 for the inlet and outlet in1 and out1, the cold side uses the index 2 (in2 and out2).

Again, for the closed thermodynamic cycle we have to insert a cycle closer. The cooling water inlet and the cooling water outlet of the condenser are directly connected to a Source and a Sink respectively.

from tespy.components import (
    CycleCloser, Pump, Condenser, Turbine, SimpleHeatExchanger, Source, Sink
)

cc = CycleCloser('cycle closer')
sg = SimpleHeatExchanger('steam generator')
mc = Condenser('main condenser')
tu = Turbine('steam turbine')
fp = Pump('feed pump')

cwso = Source('cooling water source')
cwsi = Sink('cooling water sink')

from tespy.connections import Connection

c1 = Connection(cc, 'out1', tu, 'in1', label='1')
c2 = Connection(tu, 'out1', mc, 'in1', label='2')
c3 = Connection(mc, 'out1', fp, 'in1', label='3')
c4 = Connection(fp, 'out1', sg, 'in1', label='4')
c0 = Connection(sg, 'out1', cc, 'in1', label='0')

my_plant.add_conns(c1, c2, c3, c4, c0)

c11 = Connection(cwso, 'out1', mc, 'in2', label='11')
c12 = Connection(mc, 'out2', cwsi, 'in1', label='12')

my_plant.add_conns(c11, c12)

For the parameters, we predefine the pressure losses in the heat exchangers. For the condenser, the hot side pressure losses are neglected pr1=1, for the cooling water side we assume pressure loss of 2 % pr2=0.98. The steam generator inflicts a pressure loss of 10 %.

The turbine and feed pump will have the isentropic efficiency specified. For the connection parameters, the fluid has to be defined in both the main cycle and the cooling water system. Furthermore, the live steam temperature, pressure and mass flow are set. Lastly, we set the condensation pressure level and the feed and return flow temperature of the cooling water as well as its feed pressure.

mc.set_attr(pr1=1, pr2=0.98)
sg.set_attr(pr=0.9)
tu.set_attr(eta_s=0.9)
fp.set_attr(eta_s=0.75)

c11.set_attr(T=20, p=1.2, fluid={'water': 1})
c12.set_attr(T=30)
c1.set_attr(T=600, p=150, m=10, fluid={'water': 1})
c2.set_attr(p=0.1)

my_plant.solve(mode='design')
my_plant.print_results()

After running the simulation, for example, we can observe the temperature differences at the condenser. Instead of directly setting a pressure value for condensation, we could also set the upper terminal temperature difference ttd_u instead. It is defined as the condensation temperature to cooling water return flow temperature.

Tip

You will find the documentation of each equation of the components in the respective seciton of the API documentation. For example, the condenser tespy.components.heat_exchangers.condenser.Condenser.

mc.set_attr(ttd_u=4)
c2.set_attr(p=None)

my_plant.solve(mode='design')
my_plant.print_results()

After rerunning, we will see that the condensation temperature and pressure are both automatically calculated by the specified terminal temperature value.

Generating T-s Diagram

To visualize the Rankine cycle, we generate a temperature (T) versus entropy (s) diagram using the fluprodia (Fluid Property Diagram) package.

Click to expand to code section
# Adding feature to plot the T-s Diagram using fluprodia library
# Importing necessary library
import matplotlib.pyplot as plt
import numpy as np
from fluprodia import FluidPropertyDiagram

# Initial Setup
diagram = FluidPropertyDiagram('water')
diagram.set_unit_system(T='°C', p='bar', h='kJ/kg')

# Storing the model result in the dictionary
result_dict = {}
result_dict.update(
    {cp.label: cp.get_plotting_data()[1] for cp in my_plant.comps['object']
     if cp.get_plotting_data() is not None})

# Iterate over the results obtained from TESPy simulation
for key, data in result_dict.items():
    # Calculate individual isolines for T-s diagram
    result_dict[key]['datapoints'] = diagram.calc_individual_isoline(**data)

# Create a figure and axis for plotting T-s diagram
fig, ax = plt.subplots(1, figsize=(20, 10))
isolines = {
    'Q': np.linspace(0, 1, 2),
    'p': np.array([1, 2, 5, 10, 20, 50, 100, 300]),
    'v': np.array([]),
    'h': np.arange(500, 3501, 500)
}

# Set isolines for T-s diagram
diagram.set_isolines(**isolines)
diagram.calc_isolines()

# Draw isolines on the T-s diagram
diagram.draw_isolines(fig, ax, 'Ts', x_min=0, x_max=7500, y_min=0, y_max=650)

# Adjust the font size of the isoline labels
for text in ax.texts:
    text.set_fontsize(10)

# Plot T-s curves for each component
for key in result_dict.keys():
    datapoints = result_dict[key]['datapoints']
    _ = ax.plot(datapoints['s'], datapoints['T'], color='#ff0000', linewidth=2)
    _ = ax.scatter(datapoints['s'][0], datapoints['T'][0], color='#ff0000')

# Set labels and title for the T-s diagram
ax.set_xlabel('Entropy, s in J/kgK', fontsize=16)
ax.set_ylabel('Temperature, T in °C', fontsize=16)
ax.set_title('T-s Diagram of Rankine Cycle', fontsize=20)

# Set font size for the x-axis and y-axis ticks
ax.tick_params(axis='x', labelsize=12)
ax.tick_params(axis='y', labelsize=12)
plt.tight_layout()

# Save the T-s diagram plot as an SVG file
fig.savefig('rankine_ts_diagram.svg')

The steps involved in generating the T-s diagram are as follows:

  • Import the Package: Import fluprodia and create an object by passing the alias of the fluid.

  • Specify the Unit System: Set the unit system for all fluid properties.

  • Specify Custom Isolines: Define custom isolines for the diagram.

  • Calculate and draw isolines: Calculate and draw the background isolines.

  • Calculate and draw process points and change of state

  • Save and Export the Diagram: Save and export the completed T-s diagram.

T-s Diagram of Rankine Cycle

Figure: T-s Diagram of Rankine Cycle

T-s Diagram of Rankine Cycle

Figure: T-s Diagram of Rankine Cycle

Besides visualization, this feature is also useful for analysis purposes. For example, if the T-s diagram forms a closed loop, validating the accuracy of the model and that the operating fluid completes a successful Rankine Cycle. By applying fluprodia, we can create and customize different types of diagrams for all pure and pseudo-pure fluids available in CoolProp. For more information on fluprodia, we refer users to the fluprodia documentation.

Assess Electrical Power

To assess the electrical power output we want to consider the power generated by the turbine as well as the power required to drive the feed pump. It is possible to include both of the component’s power values in a single electrical Bus. We can do this by importing the tespy.connections.bus.Bus class, creating an instance and adding both components to the bus.

from tespy.connections import Bus

powergen = Bus("electrical power output")

powergen.add_comps(
    {"comp": tu, "char": 0.97, "base": "component"},
    {"comp": fp, "char": 0.97, "base": "bus"},
)

my_plant.add_busses(powergen)

my_plant.solve(mode='design')
my_plant.print_results()

Note

The Bus can take components which either produce or consume energy. Specifying 'base': 'bus' means, that the efficiency value is referenced to the electrical power

\[\dot{W} = \dot{W}_\text{el} \cdot \eta\]

while specifying 'base': 'component' (default) takes the component’s power as base value.

\[\dot{W}_\text{el} = \dot{W} \cdot \eta\]

The results for the bus are printed separately. Observe, that the steam turbine’s electrical power production (bus value) is lower than the component value, while it is inverted for the feed pump.

You can also set the total desired power production of the system, for example replacing the mass flow specification at connection 1:

powergen.set_attr(P=-10e6)
c1.set_attr(m=None)

my_plant.solve(mode='design')
my_plant.print_results()

Analyze Efficiency and power generation

In this section, we will analyze the power production and the efficiency of the cycle, given constant steam mass flow and with varying values for the

  • live steam pressure,

  • live steam temperature and

  • cooling water temperature level.

To do that, we are using a very similar setup as has been used in the heat pump tutorial. For the feed water temperature level we want to set the change in temperature at the condenser to a constant value. Also, we have to unset the power generation specification again and use a constant mass flow instead. With iterinfo=False we can disable the printout of the convergence history.

Click to expand to code section
my_plant.set_attr(iterinfo=False)
c1.set_attr(m=20)
powergen.set_attr(P=None)

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

data = {
    'T_livesteam': np.linspace(450, 750, 7),
    'T_cooling': np.linspace(15, 45, 7),
    'p_livesteam': np.linspace(75, 225, 7)
}
eta = {
    'T_livesteam': [],
    'T_cooling': [],
    'p_livesteam': []
}
power = {
    'T_livesteam': [],
    'T_cooling': [],
    'p_livesteam': []
}

for T in data['T_livesteam']:
    c1.set_attr(T=T)
    my_plant.solve('design')
    eta['T_livesteam'] += [abs(powergen.P.val) / sg.Q.val * 100]
    power['T_livesteam'] += [abs(powergen.P.val) / 1e6]

# reset to base temperature
c1.set_attr(T=600)

for T in data['T_cooling']:
    c12.set_attr(T=T)
    c11.set_attr(T=T - 10)
    my_plant.solve('design')
    eta['T_cooling'] += [abs(powergen.P.val) / sg.Q.val * 100]
    power['T_cooling'] += [abs(powergen.P.val) / 1e6]

# reset to base temperature
c12.set_attr(T=30)
c11.set_attr(T=20)

for p in data['p_livesteam']:
    c1.set_attr(p=p)
    my_plant.solve('design')
    eta['p_livesteam'] += [abs(powergen.P.val) / sg.Q.val * 100]
    power['p_livesteam'] += [abs(powergen.P.val) / 1e6]

# reset to base pressure
c1.set_attr(p=150)


fig, ax = plt.subplots(2, 3, 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 + 3].scatter(data[key], power[key], s=100, color="#18a999")
    i += 1

ax[0].set_ylabel('Efficiency in %')
ax[3].set_ylabel('Power in MW')
ax[3].set_xlabel('Live steam temperature in °C')
ax[4].set_xlabel('Feed water temperature in °C')
ax[5].set_xlabel('Live steam pressure in bar')
plt.tight_layout()
fig.savefig('rankine_parametric-darkmode.svg')
plt.close()
Parametric analysis of the efficiency and power output

Figure: Parametric analysis of the efficiency and power output

Parametric analysis of the efficiency and power output

Figure: Parametric analysis of the efficiency and power output

Part load Simulation

In the part load simulation part, we are starting with a specific design of the plant and calculate the part load performance with some assumptions on the component’s individual behavior. The table below summarizes the assumptions, which we will keep as simple as possible at this moment. For more insights have a look at the step by step heat pump tutorial or at the Network documentation.

Component

Assumptions

Settings

Turbine

cone law applies

unset inlet pressure and apply cone law

Condenser

constant heat transfer coefficient

unset terminal temperature difference and set heat transfer coefficient

Cooling water

constant volumetric flow

unset return temperature value and set volumetric flow

With these specifications, the following physics are applied to the model:

  • Due to the constant volumetric flow of water, the temperature of the cooling water returning from the condenser will react to the total heat transferred in the condensation: Increased heat transfer means increasing temperature, decreased heat transfer means decreased temperature.

  • The constant heat transfer coefficient of the condenser will calculate the condensation temperature (and therefore pressure) based on the temperature regime in the cooling water side:

    • Increase in temperature for the cooling water leads to increased condensation temperature (at constant heat transfer).

    • Increase in heat transfer means increase in necessary temperature difference at the condenser (at constant cooling water inlet temperature).

  • The cone law is a mathematical model to predict the pressure at the turbine’s inlet based on the deviation from the design conditions. Generally, increased mass flow leads to higher inlet pressure (at constant inlet temperature and constant outlet pressure). However, this equation is more complex, since a lot more parameters are involved compared to the other equations applied.

In order to apply these specifications, we can use the design and offdesign keywords. The keyword design unsets the specified data in the list in an offdesign calculation. The keyword offdesign automatically sets the respective parameter for the offdesign calculation. In case the specification refers to a value, the value is taken from the design mode calculation. In the example, the main condenser’s kA value is calculated in the design simulation and its value will be kept constant through the offdesign simulations.

mc.set_attr(design=["ttd_u"], offdesign=["kA"])
c11.set_attr(offdesign=["v"])
c12.set_attr(design=["T"])
c1.set_attr(design=["p"])
tu.set_attr(offdesign=["cone"])

We have to save the design state of the network and run the solve method with the design_path specified.

my_plant.solve("design")
my_plant.save("rankine_design")

Finally, we can alter the mass flow from its design value of 20 kg/s to only 50 % of its value. In this example, we calculate the efficiency and plot it.

partload_efficiency = []
partload_m_range = np.linspace(20, 10, 11)

for m in partload_m_range:
    c1.set_attr(m=m)
    my_plant.solve("offdesign", design_path="rankine_design")
    partload_efficiency += [abs(powergen.P.val) / sg.Q.val * 100]


fig, ax = plt.subplots(1, figsize=(16, 8))
ax.grid()
ax.scatter(partload_m_range, partload_efficiency, s=100, color="#1f567d")
ax.set_xlabel("Mass flow in kg/s")
ax.set_ylabel("Plant electrical efficiency in %")

plt.tight_layout()
fig.savefig('rankine_partload.svg')
plt.close()
Part load electric efficiency of the Rankine cycle

Figure: Part load electric efficiency of the Rankine cycle

Part load electric efficiency of the Rankine cycle

Figure: Part load electric efficiency of the Rankine cycle