Skip to content

Commit

Permalink
Merge c3c151b into 66d17e1
Browse files Browse the repository at this point in the history
  • Loading branch information
jakob-wo committed Jun 4, 2020
2 parents 66d17e1 + c3c151b commit 5b5309f
Show file tree
Hide file tree
Showing 12 changed files with 898 additions and 1 deletion.
Binary file added docs/_pics/abs_chiller_col.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
219 changes: 219 additions & 0 deletions docs/absorption_chillers.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
.. _absorption_chillers_label:


~~~~~~~~~~~~~~~~~~~~~~~
Absorption chillers
~~~~~~~~~~~~~~~~~~~~~~~

Calculations for absorption chillers based on the characteristic equation method.


Scope
_____

This module was developed to provide cooling capacity and COP calculations
based on temperatures for energy system optimizations with oemof.solph.


Concept
_______

A characteristic equation model to describe the performance of
absorption chillers.


.. figure:: _pics/abs_chiller_col.png
:width: 40 %
:alt: abs_chiller_col.png
:align: center
:figclass: align-center

Fig.1: Absorption cooling (or heating) process.

The cooling capacity (:math:`\dot{Q}_{E}`) is determined by a function of
the characteristic
temperature difference (:math:`\Delta\Delta t'`) that combines the external
mean temperatures of the heat exchangers.

Various approaches of the characteristic equation method exists.
Here we use the approach described by Kühn and Ziegler [1]:

.. math::
\Delta\Delta t = t_{G} - a \cdot t_{AC} + e \cdot t_{E}
with the assumption

.. math::
t_{A} = t_{C} = t_{AC}
where :math:`t` is the external mean fluid temperature of the heat exchangers
(G: Generator, AC: Absorber and Condenser, E: Evaporator)
and :math:`a` and :math:`e` are characteristic parameters.

The cooling capacity (:math:`\dot{Q}_{E}`) and the driving
heat (:math:`\dot{Q}_{G}`) can be expressed as linear functions of :math:`\Delta\Delta t'`:

.. math::
\dot{Q}_{E} = s_{E} \cdot \Delta\Delta t' + r_{E}
.. math::
\dot{Q}_{G} = s_{G} \cdot \Delta\Delta t' + r_{G}
with the characteristic parameters :math:`s_{E}`, :math:`r_{E}`,
:math:`s_{G}`, and :math:`r_{G}`.

The COP can then be calculated from :math:`\dot{Q}_{E}` and :math:`\dot{Q}_{G}`:

.. math::
COP = \frac{\dot{Q}_{E}}{\dot{Q}_{G}}
These arguments are used in the formulas of the function:

========================= =================================================== ===========
symbol argument explanation
========================= =================================================== ===========
:math:`\Delta\Delta t'` :py:obj:`ddt` Characteristic temperature difference

:math:`t_G` :py:obj:`t_hot` External mean fluid temperature of generator

:math:`t_{AC}` :py:obj:`t_cool` External mean fluid temperature of absorber and condenser

:math:`t_E` :py:obj:`t_chill` External mean fluid temperature of evaporator

:math:`a` :py:obj:`coef_a` Characteristic parameter

:math:`e` :py:obj:`coef_e` Characteristic parameter

:math:`s` :py:obj:`coef_s` Characteristic parameter

:math:`r` :py:obj:`coef_r` Characteristic parameter

:math:`\dot{Q}` :py:obj:`Q_dots` Heat flux

:math:`\dot{Q}_{E}` :py:obj:`Q_dots_evap` Cooling capacity (heat flux at evaporator)

:math:`\dot{Q}_{G}` :py:obj:`Q_dots_gen` Driving heat (heat flux at generator)

:math:`COP` :py:obj:`COP` Coefficient of performance
========================= =================================================== ===========

Usage
_____

The following example shows how to calculate the COP of a small absorption chiller.
The characteristic coefficients used in this examples belong to a 10 kW absorption chiller developed and
tested at the Technische Universität Berlin [1].

.. code-block:: python

import oemof.thermal.absorption_heatpumps_and_chillers as abs_chiller

# Characteristic temperature difference
ddt = abs_chiller.calc_characteristic_temp(
t_hot=[85], # in °C
t_cool=[26], # in °C
t_chill=[15], # in °C
coef_a=10,
coef_e=2.5,
method='kuehn_and_ziegler')

# Cooling capacity
Q_dots_evap = abs_chiller.calc_heat_flux(
ddts=ddt,
coef_s=0.42,
coef_r=0.9,
method='kuehn_and_ziegler')

# Driving heat
Q_dots_gen = abs_chiller.calc_heat_flux(
ddts=ddt,
coef_s=0.51,
coef_r=2,
method='kuehn_and_ziegler')

COPs = Q_dots_evap / Q_dots_gen

Fig.2 illustrates how the cooling capacity and the COP of an absorption
chiller (here the 10 kW absorption chiller mentioned above) depend on the
cooling water temperature, i.e. the mean external fluid temperature at
absorber and condenser.

.. figure:: _pics/cooling_capacity_over_cooling_water_temperature_Kuehn.png
:width: 80 %
:alt: cooling_capacity_over_cooling_water_temperature_Kuehn.png
:align: center
:figclass: align-center

Fig.2: Dependency of the cooling capacity and the COP of a 10 kW absorption
chiller on the cooling water temperature.

You find the code that is behind Fig.2 in our examples:
https://github.com/oemof/oemof-thermal/tree/master/examples

You can run the calculations for any other absorption heat pump or chiller by
entering the specific parameters (a, e, s, r) belonging to that specific machine.
The specific parameters are determined by a numerical fit of the
four parameters with testing data or data from the fact sheet (technical
specifications from the manufacturer) if temperatures for at least two
points of operation are given.
You find detailed information in the referenced papers.

This package comes with characteristic parameters for five absorption chillers.
Four published by Puig-Arnavat et al. [3]: 'Rotartica', 'Safarik', 'Broad_01' and 'Broad_02'
and one published by Kühn and Ziegler [1]: 'Kuehn'.
If you like to contribute parameters for other machines,
please feel free to contact us or to contribute directly via github.

To model one of the machines provided by this package you can adapt the code
above in the following way.

.. code-block:: python

import oemof.thermal.absorption_heatpumps_and_chillers as abs_chiller
import pandas as pd
import os

filename_charpara = os.path.join(os.path.dirname(__file__), 'data/characteristic_parameters.csv')
charpara = pd.read_csv(filename_charpara)

chiller_name = 'Kuehn' # 'Rotartica', 'Safarik', 'Broad_01', 'Broad_02'

# Characteristic temperature difference
ddt = abs_chiller.calc_characteristic_temp(
t_hot=[85], # in °C
t_cool=[26], # in °C
t_chill=[15], # in °C
coef_a=charpara[(charpara['name'] == chiller_name)]['a'].values[0],
coef_e=charpara[(charpara['name'] == chiller_name)]['e'].values[0],
method='kuehn_and_ziegler')

# Cooling capacity
Q_dots_evap = abs_chiller.calc_heat_flux(
ddts=ddt,
coef_s=charpara[(charpara['name'] == chiller_name)]['s_E'].values[0],
coef_r=charpara[(charpara['name'] == chiller_name)]['r_E'].values[0],
method='kuehn_and_ziegler')

# Driving heat
Q_dots_gen = abs_chiller.calc_heat_flux(
ddts=ddt,
coef_s=charpara[(charpara['name'] == chiller_name)]['s_G'].values[0],
coef_r=charpara[(charpara['name'] == chiller_name)]['r_G'].values[0],
method='kuehn_and_ziegler')

COPs = [Qevap / Qgen for Qgen, Qevap in zip(Q_dots_gen, Q_dots_evap)]

You find information on the machines in [1], [2] and [3].
Please be aware that [2] introduces a slightly different approach
(using an improved characteristic equation with :math:`\Delta\Delta t''`
instead of :math:`\Delta\Delta t'`).
The characteristic parameters that we use are derived from [2] and therefore differ from those in [1].

References
__________


.. include:: ../src/oemof/thermal/absorption_heatpumps_and_chillers.py
:start-after: Reference**
:end-before: """
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Welcome to oemof.thermal's documentation!
:maxdepth: 1
:caption: User's guide

absorption_chillers
cogeneration
compression_heat_pumps_and_chillers
concentrating_solar_power
Expand Down
140 changes: 140 additions & 0 deletions examples/absorption_heatpumps_and_chiller/absorption_chiller.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@

# import absorption_heatpumps_and_chillers as abs_hp_chiller
import oemof.thermal.absorption_heatpumps_and_chillers as abs_hp_chiller
import oemof.solph as solph
import oemof.outputlib as outputlib
import pandas as pd
import os
import matplotlib.pyplot as plt

solver = 'cbc'
debug = False
number_of_time_steps = 48
solver_verbose = False

date_time_index = pd.date_range('1/1/2012', periods=number_of_time_steps,
freq='H')
energysystem = solph.EnergySystem(timeindex=date_time_index)

# Read data file
filename_data = os.path.join(os.path.dirname(__file__), 'data/AC_example.csv')
data = pd.read_csv(filename_data)

filename_charpara = os.path.join(os.path.dirname(__file__),
'data/characteristic_parameters.csv')
charpara = pd.read_csv(filename_charpara)
chiller_name = 'Kuehn'

# Buses with three different temperature levels
b_th_high = solph.Bus(label="hot")
# b_th_medium = solph.Bus(label="cool")
b_th_low = solph.Bus(label="chilled")
energysystem.add(b_th_high, b_th_low)

energysystem.add(solph.Source(
label='driving_heat',
outputs={b_th_high: solph.Flow(variable_costs=0)}))
energysystem.add(solph.Source(
label='cooling_shortage',
outputs={b_th_low: solph.Flow(variable_costs=20)}))
# energysystem.add(solph.Sink(
# label='dry_cooling_tower',
# inputs={b_th_medium: solph.Flow(variable_costs=0)}))
energysystem.add(solph.Sink(
label='cooling_demand',
inputs={b_th_low: solph.Flow(actual_value=1,
fixed=True,
nominal_value=35)}))

# Mean cooling water temperature in degC (dry cooling tower)
temp_difference = 4
t_cooling = [t + temp_difference for t in data['air_temperature']]
n = len(t_cooling)

# Pre-Calculations
ddt = abs_hp_chiller.calc_characteristic_temp(
t_hot=[85],
t_cool=t_cooling,
t_chill=[15] * n,
coef_a=charpara[(charpara['name'] == chiller_name)]['a'].values[0],
coef_e=charpara[(charpara['name'] == chiller_name)]['e'].values[0],
method='kuehn_and_ziegler')
Q_dots_evap = abs_hp_chiller.calc_heat_flux(
ddts=ddt,
coef_s=charpara[(charpara['name'] == chiller_name)]['s_E'].values[0],
coef_r=charpara[(charpara['name'] == chiller_name)]['r_E'].values[0],
method='kuehn_and_ziegler')
Q_dots_gen = abs_hp_chiller.calc_heat_flux(
ddts=ddt,
coef_s=charpara[(charpara['name'] == chiller_name)]['s_G'].values[0],
coef_r=charpara[(charpara['name'] == chiller_name)]['r_G'].values[0],
method='kuehn_and_ziegler')
COPs = [Qevap / Qgen for Qgen, Qevap in zip(Q_dots_gen, Q_dots_evap)]
nominal_Q_dots_evap = 10
actual_value = [Q_e / nominal_Q_dots_evap for Q_e in Q_dots_evap]
actual_value_df = pd.DataFrame(actual_value).clip(0)

# Absorption Chiller
energysystem.add(solph.Transformer(
label="AC",
inputs={b_th_high: solph.Flow()},
outputs={b_th_low: solph.Flow(nominal_value=nominal_Q_dots_evap,
max=actual_value_df.values,
variable_costs=5)},
conversion_factors={b_th_low: COPs}))

model = solph.Model(energysystem)

model.solve(solver=solver, solve_kwargs={'tee': solver_verbose})

energysystem.results['main'] = outputlib.processing.results(model)
energysystem.results['meta'] = outputlib.processing.meta_results(model)

energysystem.dump(dpath=None, filename=None)


# ****************************************************************************
# ********** PART 2 - Processing the results *********************************
# ****************************************************************************

energysystem = solph.EnergySystem()
energysystem.restore(dpath=None, filename=None)

results = energysystem.results['main']

high_temp_bus = outputlib.views.node(results, 'hot')
low_temp_bus = outputlib.views.node(results, 'chilled')

string_results = outputlib.views.convert_keys_to_strings(
energysystem.results['main'])
AC_output = string_results[
'AC', 'chilled']['sequences'].values
demand_cooling = string_results[
'chilled', 'cooling_demand']['sequences'].values
ASHP_input = string_results[
'hot', 'AC']['sequences'].values


fig2, axs = plt.subplots(3, 1, figsize=(8, 5), sharex=True)
axs[0].plot(AC_output, label='cooling output')
axs[0].plot(demand_cooling, linestyle='--', label='cooling demand')
axs[1].plot(COPs, linestyle='-.')
axs[2].plot(data['air_temperature'])
axs[0].set_title('Cooling capacity and demand')
axs[1].set_title('Coefficient of Performance')
axs[2].set_title('Air Temperature')
axs[0].legend()

axs[0].grid()
axs[1].grid()
axs[2].grid()
axs[0].set_ylabel('Cooling capacity in kW')
axs[1].set_ylabel('COP')
axs[2].set_ylabel('Temperature in $°$C')
axs[2].set_xlabel('Time in h')
plt.tight_layout()
plt.show()

print('********* Main results *********')
print(high_temp_bus['sequences'].sum(axis=0))
print(low_temp_bus['sequences'].sum(axis=0))

0 comments on commit 5b5309f

Please sign in to comment.