Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/stratified therm storage a1 #718

Merged
merged 12 commits into from
Jan 29, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ Here is a template for new release sections
- Add benchmark test for json benchmark file from EPA (EPA-MVS compatability) (#781)
- Add a try-except code block to catch fatal errors that cause simulation to terminate unsuccessfully (#754)

- The parameters `fixed_losses_relative` and `fixed_losses_absolute` were added. It is now possible to model a stratified thermal energy storage. The usage of this new component has been tested and documented (#718)
- It is now possible to model a stratified thermal energy storage. In this context, the two optional parameters `fixed_losses_relative` and `fixed_losses_absolute` were added and can be set in the `storage_*.csv` file. The usage of this new component was tested in `test_A1_csv_to_json.py`, `test_D1_model_components.py` and `test_benchmark_stratified_thermal_storage.py`. A documentation was added in the chapter `Modeling Assumptions of the MVS` (#718)


### Changed
Expand All @@ -59,6 +61,9 @@ Here is a template for new release sections
- `C1.check_non_dispatchable_source_time_series()`, now verification not only applied to renewable assets, but all non-dispatchable assets (#726)
- Add `MINIMAL_DEGREE_OF_AUTONOMY` to EPA-MVS parser (`utils.data_parser.convert_epa_params_to_mvs()`)(#726)
- Provide the modeler with helpful messages enabling simpler identification and rectification of problems in their input files (#754)
- In `test_A1_csv_to_json.py` tests were added that check whether default values of `0` are set for `fixed_losses_relative` and `fixed_losses_absolute` in case the user does not pass these two parameters (#718)
- In `test_D1_model_components.py` tests were added that check whether the `GenericStorage` parameter `investment.minimum` is set to `0` in case `fixed_losses_relative` and `fixed_losses_absolute` are not passed and to `1` in case they are passed as times series or floats. At this time it is not possible to do an ivestment optimization of a stratified thermal energy storage without a non-zero `investment.minimum` (see this [issue](https://github.com/oemof/oemof-thermal/issues/174)) (#718)
- The two optional parameters `fixed_losses_relative` and `fixed_losses_absolute` were added in `tests/inputs/mvs_config.json` (#718)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, we will again and again recompile mvs_config.json (from the tests/inputs csv data), so adding the parameters here will probably be lost. Is there a reason that you have this additionally to the benchmark test input files?


### Removed
- Remove `MissingParameterWarning` and use `logging.warning` instead (#761)
Expand Down
2 changes: 2 additions & 0 deletions docs/MVS_parameters.rst
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,8 @@ The file `simulation_settings.csv` includes the following parameters:
* :ref:`timestep-label`
* :ref:`outputlpfile-label`

.. _storage_csv:

storage_*.csv
^^^^^^^^^^^^^^

Expand Down
2 changes: 2 additions & 0 deletions docs/MVS_parameters_list.csv
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
None, The number of days for which the simulation is to be run.,365, None, Numeric, Days,evaluated_period,evaluatedperiod-label
None, Price received for feeding electricity into the grid.,0, None, Numeric, currency/kWh,feedin_tariff,feedintariff-label
None, Name of the csv file containing the input PV generation time-series. E.g.: filename.csv, demand_harbor.csv, None, str, None,file_name,filename-label
0, Thermal losses of storage independent of state of charge between two consecutive timesteps relative to nominal storage capacity., 0.0016, Between 0 and 1, Numeric, factor,fixed_thermal_losses_relative,fixed_thermal_losses_relative-label
0, Thermal losses of storage independent of state of charge and independent of nominal storage capacity between two consecutive timesteps., 0.0003, Between 0 and 1, Numeric, factor,fixed_thermal_losses_absolute,fixed_thermal_losses_absolute-label
None, The bus/component from which the energyVector is arriving into the asset., Electricity, None, str, None,inflow_direction,inflowdirection-label
None," The already existing installed capacity in-place, which will also be replaced after its lifetime.",50, Each component in the energyProduction.csv should have a value., Numeric, kWp,installedCap,installedcap-label
None, Name of the asset, Electricity grid DSO," Input the names in a computer readable format, preferably with underscores instead of spaces, and avoiding special characters (eg. pv_plant_01)", str, None,label,labl-label
Expand Down
61 changes: 61 additions & 0 deletions docs/Model_Assumptions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,65 @@ In case of excessive excess energy, a warning is given that it seems to be cheap
High excess energy can for example result into an optimized inverter capacity that is smaller than the peak generation of installed PV.
This becomes unrealistic when the excess is very high.

Energy storage
###########

Generic storages are defined with file `energyStorage.csv` and `storage_*.csv` and have subassets, which are listed in :ref:`storage_csv`.

Stratified thermal energy storage uses the two optional parameters `fixed_losses_relative` and `fixed_losses_absolute`. If these two are not included in `storage_*.csv` or are equal to zero, then a normal generic storage is simulated.
They are used to take into account temperature dependent losses of a thermal storage. To model a thermal energy storage without stratification, the two parameters are not set. The default values of `fixed_losses_relative` and `fixed_losses_absolute` are zero.
Except for these two additional parameters the stratified thermal storage is implemented in the same way as other storage components.

Precalculations of the `installedCap`, `efficiency`, `fixed_losses_relative` and `fixed_losses_absolute` can be done orientating on the stratified thermal storage component of `oemof.thermal <https://github.com/oemof/oemof-thermal>`__.
MaGering marked this conversation as resolved.
Show resolved Hide resolved
The parameters `U-value`, `volume` and `surface` of the storage, which are required to calculate `installedCap`, can be precalculated as well.

The efficiency :math:`\eta` of the storage is calculated as follows:

.. math::
\eta = 1 - loss{\_}rate

This example shows how to do precalculations using stratified thermal storage specific input data:


.. code-block:: python

from oemof.thermal.stratified_thermal_storage import (
calculate_storage_u_value,
calculate_storage_dimensions,
calculate_capacities,
calculate_losses,
)

# Precalculation
u_value = calculate_storage_u_value(
input_data['s_iso'],
input_data['lamb_iso'],
input_data['alpha_inside'],
input_data['alpha_outside'])

volume, surface = calculate_storage_dimensions(
input_data['height'],
input_data['diameter']
)

nominal_storage_capacity = calculate_capacities(
volume,
input_data['temp_h'],
input_data['temp_c'])

loss_rate, fixed_losses_relative, fixed_losses_absolute = calculate_losses(
u_value,
input_data['diameter'],
input_data['temp_h'],
input_data['temp_c'],
input_data['temp_env'])

Please see the `oemof.thermal` `examples <https://github.com/oemof/oemof-thermal/tree/dev/examples/stratified_thermal_storage>`__ and the `documentation <https://oemof-thermal.readthedocs.io/en/latest/stratified_thermal_storage.html>`__ for further information.

For an investment optimization the height of the storage should be left open in the precalculations and `installedCap` should be set to 0 or NaN.

An implementation of the stratified thermal storage component has been done in `pvcompare <https://github.com/greco-project/pvcompare>`__. You can find the precalculations of the stratified thermal energy storage made in `pvcompare` `here <https://github.com/greco-project/pvcompare/tree/dev/pvcompare/stratified_thermal_storage.py>`__.

Energy providers (DSOs)
-----------------------

Expand Down Expand Up @@ -742,6 +801,8 @@ A benchmark is a point of reference against which results are compared to assess

* Parser converting an energy system model from EPA to MVS (`data <https://github.com/rl-institut/multi-vector-simulator/tree/dev/tests/benchmark_test_inputs/epa_benchmark.json>`__/`pytest <https://github.com/rl-institut/multi-vector-simulator/blob/dev/tests/test_benchmark_scenarios.py>`__)

* Stratified thermal energy storage (`data <https://github.com/rl-institut/multi-vector-simulator/tree/dev/tests/benchmark_test_inputs/Feature_stratified_thermal_storage>`__/`pytest <https://github.com/rl-institut/multi-vector-simulator/tree/dev/tests/test_benchmark_stratified_thermal_storage.py>`__): With fixed thermal losses absolute and relative reduced storage capacity only if these losses apply

More tests can still be implemented with regard to:

* The investment model within the MVS
Expand Down
57 changes: 52 additions & 5 deletions src/multi_vector_simulator/A1_csv_to_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@
SOC_INITIAL,
SOC_MAX,
SOC_MIN,
THERM_LOSSES_REL,
THERM_LOSSES_ABS,
STORAGE_CAPACITY,
FILENAME,
UNIT,
Expand Down Expand Up @@ -216,6 +218,11 @@ def create_json_from_csv(
add_storage_components() the
parameter is set to True

:notes:
Tested with:
- test_default_values_storage_without_thermal_losses()
- test_default_values_storage_with_thermal_losses()

:return: dict
the converted dictionary
"""
Expand Down Expand Up @@ -312,7 +319,13 @@ def create_json_from_csv(
)
# add column specific parameters
if column == STORAGE_CAPACITY:
MaGering marked this conversation as resolved.
Show resolved Hide resolved
extra = [SOC_INITIAL, SOC_MAX, SOC_MIN]
extra = [
SOC_INITIAL,
SOC_MAX,
SOC_MIN,
THERM_LOSSES_REL,
THERM_LOSSES_ABS,
]
MaGering marked this conversation as resolved.
Show resolved Hide resolved
elif column == INPUT_POWER or column == OUTPUT_POWER:
extra = [C_RATE, DISPATCH_PRICE]
else:
Expand All @@ -325,10 +338,42 @@ def create_json_from_csv(
column_parameters = parameters + extra
# check if required parameters are missing
for i in set(column_parameters) - set(df_copy.index):
raise MissingParameterError(
f"In file {filename}.csv the parameter {i}"
f" in column {column} is missing."
)
if i == THERM_LOSSES_REL:
MaGering marked this conversation as resolved.
Show resolved Hide resolved
logging.debug(
f"You are not using the parameter {THERM_LOSSES_REL}, which allows considering relative thermal energy losses (Values: Float). This is an advanced setting that most users can ignore."
)
# Set 0 as default parameter for losses in storage capacity
losses = pd.DataFrame(
data={
"": THERM_LOSSES_REL,
df_copy.columns[0]: ["factor"],
MaGering marked this conversation as resolved.
Show resolved Hide resolved
df_copy.columns[1]: [0],
}
)
losses = losses.set_index("")
# Append missing parameter to dataframe
df_copy = df_copy.append(losses, ignore_index=False, sort=False)
elif i == THERM_LOSSES_ABS:
logging.debug(
f"You are not using the parameter {THERM_LOSSES_ABS}, which allows considering relative thermal energy losses (Values: Float). This is an advanced setting that most users can ignore."
)
# Set 0 as default parameter for losses in storage capacity
losses = pd.DataFrame(
data={
"": THERM_LOSSES_ABS,
df_copy.columns[0]: ["kWh"],
df_copy.columns[1]: [0],
}
)
losses = losses.set_index("")
# Append missing parameter to dataframe
df_copy = df_copy.append(losses, ignore_index=False, sort=False)
else:
raise MissingParameterError(
f"In file {filename}.csv the parameter {i}"
f" in column {column} is missing."
)

for i in df_copy.index:
if i not in column_parameters:
# check if not required parameters are set to Nan and
Expand All @@ -339,6 +384,8 @@ def create_json_from_csv(
SOC_INITIAL,
SOC_MAX,
SOC_MIN,
THERM_LOSSES_REL,
THERM_LOSSES_ABS,
]:
warnings.warn(
WrongParameterWarning(
Expand Down
8 changes: 7 additions & 1 deletion src/multi_vector_simulator/C0_data_processing.py
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,13 @@ def energyStorage(dict_values, group):
)

# check if parameters are provided as timeseries
for parameter in [EFFICIENCY, SOC_MIN, SOC_MAX]:
for parameter in [
EFFICIENCY,
SOC_MIN,
SOC_MAX,
THERM_LOSSES_REL,
THERM_LOSSES_ABS,
]:
if parameter in dict_values[group][asset][subasset] and (
FILENAME in dict_values[group][asset][subasset][parameter]
and HEADER in dict_values[group][asset][subasset][parameter]
Expand Down
29 changes: 29 additions & 0 deletions src/multi_vector_simulator/D1_model_components.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
SOC_INITIAL,
SOC_MAX,
SOC_MIN,
THERM_LOSSES_REL,
THERM_LOSSES_ABS,
STORAGE_CAPACITY,
TIMESERIES,
TIMESERIES_NORMALIZED,
Expand Down Expand Up @@ -578,6 +580,8 @@ def storage_fix(model, dict_asset, **kwargs):
}, # maximum discharge possible in one timestep
loss_rate=1
- dict_asset[STORAGE_CAPACITY][EFFICIENCY][VALUE], # from timestep to timestep
fixed_losses_absolute=dict_asset[STORAGE_CAPACITY][THERM_LOSSES_ABS][VALUE],
fixed_losses_relative=dict_asset[STORAGE_CAPACITY][THERM_LOSSES_REL][VALUE],
MaGering marked this conversation as resolved.
Show resolved Hide resolved
min_storage_level=dict_asset[STORAGE_CAPACITY][SOC_MIN][VALUE],
max_storage_level=dict_asset[STORAGE_CAPACITY][SOC_MAX][VALUE],
initial_storage_level=dict_asset[STORAGE_CAPACITY][SOC_INITIAL][
Expand All @@ -602,16 +606,39 @@ def storage_optimize(model, dict_asset, **kwargs):
-----
Tested with:
- test_storage_optimize()
- test_storage_optimize_investment_minimum_0_float()
- test_storage_optimize_investment_minimum_0_time_series()
- test_storage_optimize_investment_minimum_1_rel_float()
- test_storage_optimize_investment_minimum_1_abs_float()
- test_storage_optimize_investment_minimum_1_rel_times_series()
- test_storage_optimize_investment_minimum_1_abs_times_series()

Returns
-------
Indirectly updated `model` and dict of asset in `kwargs` with the storage object.

"""
# investment.minimum for an InvestmentStorage is 0 as default
minimum = 0

# Set investment.minimum to 1 if
# non-zero fixed_thermal_losses_relative or fixed_thermal_losses_absolute exist as
for losses in [THERM_LOSSES_REL, THERM_LOSSES_ABS]:
# 1. float or
try:
float(dict_asset[STORAGE_CAPACITY][losses][VALUE])
if dict_asset[STORAGE_CAPACITY][losses][VALUE] != 0:
minimum = 1
# 2. time series
except TypeError:
if sum(dict_asset[STORAGE_CAPACITY][losses][VALUE]) != 0:
minimum = 1

storage = solph.components.GenericStorage(
label=dict_asset[LABEL],
investment=solph.Investment(
ep_costs=dict_asset[STORAGE_CAPACITY][SIMULATION_ANNUITY][VALUE],
minimum=minimum,
maximum=dict_asset[STORAGE_CAPACITY][MAXIMUM_CAP][VALUE],
existing=dict_asset[STORAGE_CAPACITY][INSTALLED_CAP][VALUE],
),
Expand Down Expand Up @@ -641,6 +668,8 @@ def storage_optimize(model, dict_asset, **kwargs):
}, # maximum discharge power
loss_rate=1
- dict_asset[STORAGE_CAPACITY][EFFICIENCY][VALUE], # from timestep to timestep
fixed_losses_absolute=dict_asset[STORAGE_CAPACITY][THERM_LOSSES_ABS][VALUE],
fixed_losses_relative=dict_asset[STORAGE_CAPACITY][THERM_LOSSES_REL][VALUE],
min_storage_level=dict_asset[STORAGE_CAPACITY][SOC_MIN][VALUE],
max_storage_level=dict_asset[STORAGE_CAPACITY][SOC_MAX][VALUE],
initial_storage_level=dict_asset[STORAGE_CAPACITY][SOC_INITIAL][
Expand Down
2 changes: 2 additions & 0 deletions src/multi_vector_simulator/utils/constants_json_strings.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@
SOC_INITIAL = "soc_initial"
SOC_MAX = "soc_max"
SOC_MIN = "soc_min"
THERM_LOSSES_REL = "fixed_thermal_losses_relative"
THERM_LOSSES_ABS = "fixed_thermal_losses_absolute"

# Constraints
CONSTRAINTS = "constraints"
Expand Down
19 changes: 19 additions & 0 deletions src/multi_vector_simulator/utils/data_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@
TIMESERIES_SOC,
TYPE_ASSET,
DSM,
THERM_LOSSES_REL,
THERM_LOSSES_ABS,
)

from multi_vector_simulator.utils.exceptions import MissingParameterError
Expand Down Expand Up @@ -388,6 +390,23 @@ def convert_epa_params_to_mvs(epa_dict):
# TODO remove this when change has been made on EPA side
if asset_group == ENERGY_STORAGE:

if (
THERM_LOSSES_REL
not in dict_asset[asset_label][STORAGE_CAPACITY]
):
dict_asset[asset_label][STORAGE_CAPACITY][THERM_LOSSES_REL] = {
UNIT: "factor",
VALUE: 0,
}
if (
THERM_LOSSES_ABS
not in dict_asset[asset_label][STORAGE_CAPACITY]
):
dict_asset[asset_label][STORAGE_CAPACITY][THERM_LOSSES_ABS] = {
UNIT: "kWh",
VALUE: 0,
}

if OPTIMIZE_CAP not in dict_asset[asset_label]:
dict_asset[asset_label][OPTIMIZE_CAP] = {
UNIT: TYPE_BOOL,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
,unit,constraints
minimal_renewable_factor,factor,0
maximum_emissions,kgCO2eq/a,None
minimal_degree_of_autonomy,factor,0
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
,unit,economic_data
project_duration,year,30
currency,str,EUR
discount_factor,factor,0.08
tax,factor,0.0
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
,unit,Electricity,Heat
energyVector,str,Electricity,Heat

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
,unit,demand_elec,demand_heat
dsm,str,False,False
file_name,str,demand_elec.csv,demand_heat.csv
type_asset,str,demand,demand
type_oemof,str,sink,sink
energyVector,str,Electricity,Heat
inflow_direction,str,Electricity,Heat
unit,str,kW,kWh
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
,unit,Heat pump
age_installed,year,0
development_costs,currency,0
specific_costs,currency/kW,1000
efficiency,factor,3.8
inflow_direction,str,Electricity
installedCap,kW,0
maximumCap,kW,None
lifetime,year,20
specific_costs_om,currency/kW/year,20
dispatch_price,currency/kWh,0
optimizeCap,bool,True
outflow_direction,str,Heat
energyVector,str,Heat
type_oemof,str,transformer
unit,str,kW
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
,unit
age_installed,year
development_costs,currency
specific_costs,currency/unit
file_name
installedCap,kWp
maximumCap, kWp
lifetime,year
specific_costs_om,currency/unit/year
dispatch_price,currency/kWh
optimizeCap,bool
outflow_direction,str
type_oemof,str
unit,str
energyVector,str
renewableAsset,bool
emission_factor,kgCO2eq/unit
Loading