Variable CHP¶
Modeling different types of CHP plants¶
General description¶
This example is not a real use case of an energy system but an example to show how a combined heat and power plant (chp) with an extraction turbine works in contrast to a chp (eg. block device) with a fixed heat fraction. Both chp plants distribute power and heat to a separate heat and power Bus with a heat and power demand. The i/o balance plot shows that the fixed chp plant produces heat and power excess and therefore needs more natural gas. The bar plot just shows the difference in the usage of natural gas.
Code¶
Download source code: variable_chp.py
Click to display code
import logging
import os
import warnings
import matplotlib.pyplot as plt
import pandas as pd
from oemof.tools import logger
from oemof import solph
# import oemof plots
try:
from oemof_visio import plot as oeplot
except ImportError:
oeplot = None
def shape_legend(node, reverse=True, **kwargs):
handels = kwargs["handles"]
labels = kwargs["labels"]
axes = kwargs["ax"]
parameter = {}
new_labels = []
for label in labels:
label = label.replace("(", "")
label = label.replace("), flow)", "")
label = label.replace(node, "")
label = label.replace(",", "")
label = label.replace(" ", "")
new_labels.append(label)
labels = new_labels
parameter["bbox_to_anchor"] = kwargs.get("bbox_to_anchor", (1, 0.5))
parameter["loc"] = kwargs.get("loc", "center left")
parameter["ncol"] = kwargs.get("ncol", 1)
plotshare = kwargs.get("plotshare", 0.9)
if reverse:
handels.reverse()
labels.reverse()
box = axes.get_position()
axes.set_position([box.x0, box.y0, box.width * plotshare, box.height])
parameter["handles"] = handels
parameter["labels"] = labels
axes.legend(**parameter)
return axes
def write_lp_file(model):
lp_filename = os.path.join(
solph.helpers.extend_basic_path("lp_files"), "variable_chp.lp"
)
logging.info("Store lp-file in {0}.".format(lp_filename))
model.write(lp_filename, io_options={"symbolic_solver_labels": True})
def main():
# Read data file
filename = os.path.join(os.getcwd(), "variable_chp.csv")
try:
data = pd.read_csv(filename)
except FileNotFoundError:
msg = "Data file not found: {0}. Only one value used!"
warnings.warn(msg.format(filename), UserWarning)
data = pd.DataFrame(
{
"pv": [0.3],
"wind": [0.6],
"demand_el": [500],
"demand_th": [344],
}
)
logger.define_logging()
logging.info("Initialize the energy system")
# create time index for 192 hours in May.
date_time_index = solph.create_time_index(2012, number=len(data))
energysystem = solph.EnergySystem(
timeindex=date_time_index, infer_last_interval=False
)
##########################################################################
# Create oemof.solph objects
##########################################################################
logging.info("Create oemof.solph objects")
# container for instantiated nodes
noded = dict()
# create natural gas bus
noded["bgas"] = solph.Bus(label="natural_gas")
# create commodity object for gas resource
noded["rgas"] = solph.components.Source(
label="rgas", outputs={noded["bgas"]: solph.Flow(variable_costs=50)}
)
# create two electricity buses and two heat buses
noded["bel"] = solph.Bus(label="electricity")
noded["bel2"] = solph.Bus(label="electricity_2")
noded["bth"] = solph.Bus(label="heat")
noded["bth2"] = solph.Bus(label="heat_2")
# create excess components for the elec/heat bus to allow overproduction
noded["excess_bth_2"] = solph.components.Sink(
label="excess_bth_2", inputs={noded["bth2"]: solph.Flow()}
)
noded["excess_therm"] = solph.components.Sink(
label="excess_therm", inputs={noded["bth"]: solph.Flow()}
)
noded["excess_bel_2"] = solph.components.Sink(
label="excess_bel_2", inputs={noded["bel2"]: solph.Flow()}
)
noded["excess_elec"] = solph.components.Sink(
label="excess_elec", inputs={noded["bel"]: solph.Flow()}
)
# create simple sink object for electrical demand for each electrical bus
noded["demand_elec"] = solph.components.Sink(
label="demand_elec",
inputs={
noded["bel"]: solph.Flow(fix=data["demand_el"], nominal_capacity=1)
},
)
noded["demand_el_2"] = solph.components.Sink(
label="demand_el_2",
inputs={
noded["bel2"]: solph.Flow(
fix=data["demand_el"], nominal_capacity=1
)
},
)
# create simple sink object for heat demand for each thermal bus
noded["demand_therm"] = solph.components.Sink(
label="demand_therm",
inputs={
noded["bth"]: solph.Flow(
fix=data["demand_th"], nominal_capacity=741000
)
},
)
noded["demand_therm_2"] = solph.components.Sink(
label="demand_th_2",
inputs={
noded["bth2"]: solph.Flow(
fix=data["demand_th"], nominal_capacity=741000
)
},
)
# This is just a dummy Converter with a nominal input of zero
noded["fixed_chp_gas"] = solph.components.Converter(
label="fixed_chp_gas",
inputs={noded["bgas"]: solph.Flow(nominal_capacity=0)},
outputs={noded["bel"]: solph.Flow(), noded["bth"]: solph.Flow()},
conversion_factors={noded["bel"]: 0.3, noded["bth"]: 0.5},
)
# create a fixed Converter to distribute to the heat_2 and elec_2 buses
noded["fixed_chp_gas_2"] = solph.components.Converter(
label="fixed_chp_gas_2",
inputs={noded["bgas"]: solph.Flow(nominal_capacity=10e10)},
outputs={noded["bel2"]: solph.Flow(), noded["bth2"]: solph.Flow()},
conversion_factors={noded["bel2"]: 0.3, noded["bth2"]: 0.5},
)
# create a fixed Converter to distribute to the heat and elec buses
noded["variable_chp_gas"] = solph.components.ExtractionTurbineCHP(
label="variable_chp_gas",
inputs={noded["bgas"]: solph.Flow(nominal_capacity=10e10)},
outputs={noded["bel"]: solph.Flow(), noded["bth"]: solph.Flow()},
conversion_factors={noded["bel"]: 0.3, noded["bth"]: 0.5},
conversion_factor_full_condensation={noded["bel"]: 0.5},
)
##########################################################################
# Optimise the energy system
##########################################################################
logging.info("Optimise the energy system")
energysystem.add(*noded.values())
om = solph.Model(energysystem)
# If uncomment the following line you can store the lp file but you should
# use less timesteps (3) to make it better readable and smaller.
# write_lp_file(om)
logging.info("Solve the optimization problem")
om.solve(solver="cbc", solve_kwargs={"tee": False})
results = solph.processing.results(om)
##########################################################################
# Plot the results
##########################################################################
myresults = solph.views.node(results, "natural_gas")
myresults = myresults["sequences"].sum(axis=0)
myresults = myresults.drop(myresults.index[0]).reset_index(drop=True)
myresults.rename({0: "fixed", 1: "variable", 2: "total"}, inplace=True)
myresults.plot(kind="bar", rot=0, title="Usage of natural gas")
plt.show()
# Create a plot with 6 tiles that shows the difference between the
# Converter and the ExtractionTurbineCHP used for chp plants.
smooth_plot = True
if oeplot:
logging.info("Plot the results")
cdict = {
(("variable_chp_gas", "electricity"), "flow"): "#42c77a",
(("fixed_chp_gas_2", "electricity_2"), "flow"): "#20b4b6",
(("fixed_chp_gas", "electricity"), "flow"): "#20b4b6",
(("fixed_chp_gas", "heat"), "flow"): "#20b4b6",
(("variable_chp_gas", "heat"), "flow"): "#42c77a",
(("heat", "demand_therm"), "flow"): "#5b5bae",
(("heat_2", "demand_th_2"), "flow"): "#5b5bae",
(("electricity", "demand_elec"), "flow"): "#5b5bae",
(("electricity_2", "demand_el_2"), "flow"): "#5b5bae",
(("heat", "excess_therm"), "flow"): "#f22222",
(("heat_2", "excess_bth_2"), "flow"): "#f22222",
(("electricity", "excess_elec"), "flow"): "#f22222",
(("electricity_2", "excess_bel_2"), "flow"): "#f22222",
(("fixed_chp_gas_2", "heat_2"), "flow"): "#20b4b6",
}
fig = plt.figure(figsize=(18, 9))
plt.rc("legend", **{"fontsize": 13})
plt.rcParams.update({"font.size": 13})
fig.subplots_adjust(
left=0.07,
bottom=0.12,
right=0.86,
top=0.93,
wspace=0.03,
hspace=0.2,
)
# subplot of electricity bus (fixed chp) [1]
electricity_2 = solph.views.node(results, "electricity_2")
x_length = len(electricity_2["sequences"].index)
myplot = oeplot.io_plot(
bus_label="electricity_2",
df=electricity_2["sequences"],
cdict=cdict,
smooth=smooth_plot,
line_kwa={"linewidth": 4},
ax=fig.add_subplot(3, 2, 1),
inorder=[(("fixed_chp_gas_2", "electricity_2"), "flow")],
outorder=[
(("electricity_2", "demand_el_2"), "flow"),
(("electricity_2", "excess_bel_2"), "flow"),
],
)
myplot["ax"].set_ylabel("Power in MW")
myplot["ax"].set_xlabel("")
myplot["ax"].get_xaxis().set_visible(False)
myplot["ax"].set_xlim(0, x_length)
myplot["ax"].set_title("Electricity output (fixed chp)")
myplot["ax"].legend_.remove()
# subplot of electricity bus (variable chp) [2]
electricity = solph.views.node(results, "electricity")
myplot = oeplot.io_plot(
bus_label="electricity",
df=electricity["sequences"],
cdict=cdict,
smooth=smooth_plot,
line_kwa={"linewidth": 4},
ax=fig.add_subplot(3, 2, 2),
inorder=[
(("fixed_chp_gas", "electricity"), "flow"),
(("variable_chp_gas", "electricity"), "flow"),
],
outorder=[
(("electricity", "demand_elec"), "flow"),
(("electricity", "excess_elec"), "flow"),
],
)
myplot["ax"].get_yaxis().set_visible(False)
myplot["ax"].set_xlabel("")
myplot["ax"].get_xaxis().set_visible(False)
myplot["ax"].set_title("Electricity output (variable chp)")
myplot["ax"].set_xlim(0, x_length)
shape_legend("electricity", plotshare=1, **myplot)
# subplot of heat bus (fixed chp) [3]
heat_2 = solph.views.node(results, "heat_2")
myplot = oeplot.io_plot(
bus_label="heat_2",
df=heat_2["sequences"],
cdict=cdict,
smooth=smooth_plot,
line_kwa={"linewidth": 4},
ax=fig.add_subplot(3, 2, 3),
inorder=[(("fixed_chp_gas_2", "heat_2"), "flow")],
outorder=[
(("heat_2", "demand_th_2"), "flow"),
(("heat_2", "excess_bth_2"), "flow"),
],
)
myplot["ax"].set_ylabel("Power in MW")
myplot["ax"].set_ylim([0, 600000])
myplot["ax"].get_xaxis().set_visible(False)
myplot["ax"].set_title("Heat output (fixed chp)")
myplot["ax"].set_xlim(0, x_length)
myplot["ax"].legend_.remove()
# subplot of heat bus (variable chp) [4]
heat = solph.views.node(results, "heat")
myplot = oeplot.io_plot(
bus_label="heat",
df=heat["sequences"],
cdict=cdict,
smooth=smooth_plot,
line_kwa={"linewidth": 4},
ax=fig.add_subplot(3, 2, 4),
inorder=[
(("fixed_chp_gas", "heat"), "flow"),
(("variable_chp_gas", "heat"), "flow"),
],
outorder=[
(("heat", "demand_therm"), "flow"),
(("heat", "excess_therm"), "flow"),
],
)
myplot["ax"].set_ylim([0, 600000])
myplot["ax"].get_yaxis().set_visible(False)
myplot["ax"].get_xaxis().set_visible(False)
myplot["ax"].set_title("Heat output (variable chp)")
myplot["ax"].set_xlim(0, x_length)
shape_legend("heat", plotshare=1, **myplot)
if smooth_plot:
style = None
else:
style = "steps-mid"
# subplot of efficiency (fixed chp) [5]
fix_chp_gas2 = solph.views.node(results, "fixed_chp_gas_2")
ngas = fix_chp_gas2["sequences"][
(("natural_gas", "fixed_chp_gas_2"), "flow")
]
elec = fix_chp_gas2["sequences"][
(("fixed_chp_gas_2", "electricity_2"), "flow")
]
heat = fix_chp_gas2["sequences"][
(("fixed_chp_gas_2", "heat_2"), "flow")
]
e_ef = elec.div(ngas)
h_ef = heat.div(ngas)
df = pd.DataFrame(pd.concat([h_ef, e_ef], axis=1))
my_ax = df.reset_index(drop=True).plot(
drawstyle=style, ax=fig.add_subplot(3, 2, 5), linewidth=2
)
my_ax.set_ylabel("efficiency")
my_ax.set_ylim([0, 0.55])
my_ax.set_xlabel("May 2012")
my_ax = oeplot.set_datetime_ticks(
my_ax,
df.index,
tick_distance=24,
date_format="%d",
offset=12,
tight=True,
)
my_ax.set_title("Efficiency (fixed chp)")
my_ax.legend_.remove()
# subplot of efficiency (variable chp) [6]
var_chp_gas = solph.views.node(results, "variable_chp_gas")
ngas = var_chp_gas["sequences"][
(("natural_gas", "variable_chp_gas"), "flow")
]
elec = var_chp_gas["sequences"][
(("variable_chp_gas", "electricity"), "flow")
]
heat = var_chp_gas["sequences"][(("variable_chp_gas", "heat"), "flow")]
e_ef = elec.div(ngas)
h_ef = heat.div(ngas)
e_ef.name = "electricity "
h_ef.name = "heat"
df = pd.DataFrame(pd.concat([h_ef, e_ef], axis=1))
my_ax = df.reset_index(drop=True).plot(
drawstyle=style, ax=fig.add_subplot(3, 2, 6), linewidth=2
)
my_ax.set_ylim([0, 0.55])
my_ax = oeplot.set_datetime_ticks(
my_ax,
df.index,
tick_distance=24,
date_format="%d",
offset=12,
tight=True,
)
my_ax.get_yaxis().set_visible(False)
my_ax.set_xlabel("May 2012")
my_ax.set_title("Efficiency (variable chp)")
my_box = my_ax.get_position()
my_ax.set_position(
[my_box.x0, my_box.y0, my_box.width * 1, my_box.height]
)
my_ax.legend(loc="center left", bbox_to_anchor=(1, 0.5), ncol=1)
plt.show()
else:
logging.warning(
"You have to install 'oemof-visio' to see the i/o-plot"
)
logging.warning(
"Use: pip install git+https://github.com/oemof/oemof_visio.git"
)
if __name__ == "__main__":
main()
Data¶
Download data: variable_chp.csv
Installation requirements¶
This example requires oemof.solph (v0.5.x), install by:
pip install oemof.solph[examples]
Optional to see the i/o balance plot:
pip install git+https://github.com/oemof/oemof_visio.git