oemof-solph model with variable partload efficiency

oemof-solph model with variable partload efficiency#

The final modification in our energy system model is to implement the load dependent variable COP. To do that we can use the OffsetConverter. This component requires different input compared to our previous implementations. Instead of a conversion factor connecting input with output as shown in eq. (8), we define a slope and an offset (eq. (9)). This also changes the overall efficiency value in an interesting way per eq. (10): We have nonlinear efficiency in a linear model. We can visualize the effect in Fig. 20. For that we load the TESPy results and plot the compressor power and COP over the heat production.

(8)#\[E_\text{out} = \eta \cdot E_\text{in}\]
(9)#\[E_\text{out} = E_0 + m \cdot \dot E_\text{in}\]
(10)#\[\eta = \frac{E_\text{out}}{E_0 + m \cdot \dot E_\text{in}}\]
../_images/0565b8c2e85bb9edeaa2ceacbf1b6df3d764c2a120a1527b6e75c8b9c134907f.png

Fig. 20 Compressor power and COP as function of the heat pump heat production at 7 °C.#

from utilities import load_tespy_coefficients, load_input_data
from matplotlib import pyplot as plt
import numpy as np


input_data = load_input_data().head(24*2)
tespy_coefficients = load_tespy_coefficients()

example = tespy_coefficients.loc[7]

heat_production_range = np.linspace(0.5, 1) * 9.1e3
compressor_power = (heat_production_range - example.loc["offset"]) / example.loc["slope"]
cop = heat_production_range / compressor_power


fig, ax = plt.subplots(2, sharex=True)

ax[0].plot(heat_production_range, compressor_power)
ax[0].set_ylim([0, compressor_power.max() * 1.05])
ax[0].set_ylabel("Compressor power in W")

ax[1].plot(heat_production_range, cop)
ax[1].set_ylim([0, cop.max() * 1.05])
ax[1].set_ylabel("COP")

ax[1].set_xlabel("Heat production in W")
ax[1].set_xlim([0, heat_production_range.max() * 1.05])

plt.close()
FutureWarning: The 'delim_whitespace' keyword in pd.read_csv is deprecated and will be removed in a future version. Use ``sep='\s+'`` instead

We can transform the input data from the TESPy model by mapping them onto the ambient temperatures similarly as we had done this for the linear model.

input_data["slope"] = input_data["Ambient temperature (d°C)"].map(tespy_coefficients["slope"])
input_data["offset"] = input_data["Ambient temperature (d°C)"].map(tespy_coefficients["offset"])

Then we load the energy system and add the heat pump with the necessary changes:

from utilities import create_energy_system_stub

es, bus_electricity, bus_heat_35C = create_energy_system_stub(input_data)

With respect to the previous version using minimal load), the Converter is replaced by an OffsetConverter.

import oemof.solph as solph

hp_thermal_power = 9.1  # kW

slope = input_data["slope"][:-1]
offset = input_data["offset"][:-1]/1e3
demand = input_data["Heat load (kW)"][:-1]


heat_pump = solph.components.OffsetConverter(
        label=f"heat pump",
        inputs={bus_electricity: solph.Flow(
            nominal_value=5,
            min=0.0,
        )},
        outputs={bus_heat_35C: solph.Flow(
            nominal_value=hp_thermal_power,
            nonconvex=solph.NonConvex(),
            min=0.5,
        )},
        coefficients=[offset, slope]
)

es.add(heat_pump)

We can solve our model and have a look at the results.

model = solph.Model(energysystem=es)
model.solve()
results = solph.processing.results(model)
FutureWarning: Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`
FutureWarning: Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`
FutureWarning: Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`
FutureWarning: Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`
FutureWarning: Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`

In our final example Fig. 21 shows that the heat pump is mostly operated at full load. This is because the COP of the heat pump in largest in that point (see Fig. 20). The storage losses are higher overall as the filling level is higher as well. However, the increased storage losses are lower than those induced by decrease of the heat pump’s COP in part load. The overall electricity consumption consequently increases from 14.23 kWh to 28.39 kWh.

../_images/036ea1e28605b97bb5321096c083f67d03c179b6bd6f0fa35dbf4dd38f6323d8.png

Fig. 21 Operation of the heat pump modeled as OffsetConverter.#

from utilities import sumarise_solph_results

fig, electricity_total = sumarise_solph_results(results)
plt.close()
WARNING:TESPyLogger:FutureWarning: Calling float on a single element Series is deprecated and will raise a TypeError in the future. Use float(ser.iloc[0]) instead
Electricity demand: 28.4 kWh