Skip to content

Commit

Permalink
Merge pull request #525 from rl-institut/feature/sector_wide_kpi
Browse files Browse the repository at this point in the history
Add system-wide KPI to evaluation
  • Loading branch information
smartie2076 committed Aug 28, 2020
2 parents 8bf16bd + 5ba53be commit 7ae1333
Show file tree
Hide file tree
Showing 20 changed files with 871 additions and 2,332 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,16 @@ Here is a template for new release sections
- Possibility to add an upper bound on the number of days to display in a timeseries' plot (#526)
- Graph of the energy system model to the report (#528)
- Function to encode images into dash app's layout (#528)
- System KPI now printed in automatic report (section "Energy system key performance indicators"), draft (#525)
- Added units to system-wide cost KPI in excel and in report. Some of these changes might need to be reworked when elaborating on units for the report (#525)
- `References.rst` to the readthedocs, which should gather all the references of the MVS (#525)
- New system-wide KPI:
- Demand per energy carrier, in original unit and electricity equivalent with `E3.total_demand_each_sector()` (#525)
- Attributed cost per energy carrier, related to the its share in the total demand equivalent with `E3.total_demand_each_sector()` (#525)
- LCOE per energy carrier `E3.add_levelized_cost_of_energy_carriers()` (#525)
- Default values for energy carrier "Heat" for `DEFAULT_WEIGHTS_ENERGY_CARRIERS` with `{UNIT: "KWh_eleq/kWh_therm", VALUE: 1}`. This is still TBD, as there is no source for this ratio yet (#525)
- Default unit for energy carriers defined in `DEFAULT_WEIGHTS_ENERGY_CARRIERS`: ENERGY_CARRIER_UNIT. Might be used to define the units of flows and LCOE. (#525)
- New constant variables: TIMESERIES_TOTAL, TIMESERIES_AVERAGE, LOGFILE, RENEWABLE_SHARE, TOTAL_DEMAND, SUFFIX_ELECTRICITY_EQUIVALENT, ATTRIBUTED_COSTS, LCOeleq, DEGREE_OF_SECTOR_COUPLING (#525)

### Changed
- Changed structure for `E2.get_cost()` and complete disaggregation of the formulas used in it (#520)
Expand All @@ -44,6 +54,8 @@ Here is a template for new release sections
- Changed `requirements.txt` (removing and updating dependencies) (#528)
- A png of the energy system model graph is only saved if either `-png` or `-pdf` options are chosen (#530)
- Accepting string "TRUE"/"FALSE" now for boolean parameters (#534)
- Order of pages in the readthedocs.io (#525)
- Reactivated KPI: Renewable share. Updated pytests (#525)

### Removed
- `E2.add_costs_and_total`() (#520)
Expand Down
9 changes: 9 additions & 0 deletions docs/References.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
=====================
References of the MVS
=====================

The MVS is currently under development in the H2020 research project `E-Land`. Still, there are already some references where additional information can be found regarding its intention, application, and method.

* Research project website: `E-LAND Horizon 2020. Novel solutions for decarbonised energy islands <https://elandh2020.eu/>`_

* Martha M. Hoffmann, Sanket Puranik, Marc Juanpera, José M Martín-Rapún, Heidi Tuiskula, Philipp Blechinger. *Investment planning in multi-vector energy systems: Definition of key performance indicators*, conference paper, poster to be presented at the virtual `*CIRED Berlin 2020 Workshop* <https://www.cired2020berlin.org/index.html>`_, 22 - 23 September.
9 changes: 5 additions & 4 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@ Welcome to Multi-Vector Simulator (MVS)'s documentation!

.. toctree::
Overview
Installation
Developing
Code
References
Model_Assumptions
simulating_with_the_mvs
MVS_parameters
Model_Assumptions
MVS_Outputs
Installation
Developing
Code
troubleshooting

==================
Expand Down
3 changes: 2 additions & 1 deletion src/A0_initialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
OVERWRITE,
DISPLAY_OUTPUT,
SAVE_PNG,
LOGFILE,
)
from src.constants_json_strings import LABEL

Expand Down Expand Up @@ -353,7 +354,7 @@ def process_user_arguments(
# Define logging settings and path for saving log
logger.define_logging(
logpath=path_output_folder,
logfile="mvs_logfile.log",
logfile=LOGFILE,
file_level=logging.DEBUG,
screen_level=screen_level,
)
Expand Down
14 changes: 11 additions & 3 deletions src/C0_data_processing.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,16 +222,21 @@ def process_all_assets(dict_values):
define_busses(dict_values)

# Define all excess sinks for each energy bus
auto_sinks = []
for bus_name in dict_values[ENERGY_BUSSES]:
define_sink(
dict_values=dict_values,
asset_name=bus_name + EXCESS,
price={VALUE: 0, UNIT: CURR + "/" + UNIT},
input_bus_name=bus_name,
)
auto_sinks.append(bus_name + EXCESS)
logging.debug(
"Created excess sink for energy bus %s", bus_name,
)
# Needed for E3.total_demand_each_sector(), but location is not perfect as it is more about the model then the settings.
# Decided against implementing a new major 1st level category in json to avoid an excessive datatree.
dict_values[SIMULATION_SETTINGS].update({EXCESS + AUTO_SINK: auto_sinks})

# process all energyAssets:
# Attention! Order of asset_groups important. for energyProviders/energyConversion sinks and sources
Expand Down Expand Up @@ -1294,7 +1299,10 @@ def evaluate_lifetime_costs(settings, economic_data, dict_asset):
Notes
-----
Tested with test_evaluate_lifetime_costs_adds_all_parameters()
Tested with:
- test_evaluate_lifetime_costs_adds_all_parameters()
- Test_Economic_KPI.test_benchmark_Economic_KPI_C2_E2()
"""

C2.determine_lifetime_price_dispatch(dict_asset, economic_data)
Expand Down Expand Up @@ -1466,8 +1474,8 @@ def receive_timeseries_from_csv(
dict_asset.update(
{
TIMESERIES_PEAK: {VALUE: max(dict_asset[TIMESERIES]), UNIT: unit,},
"timeseries_total": {VALUE: sum(dict_asset[TIMESERIES]), UNIT: unit,},
"timeseries_average": {
TIMESERIES_TOTAL: {VALUE: sum(dict_asset[TIMESERIES]), UNIT: unit,},
TIMESERIES_AVERAGE: {
VALUE: sum(dict_asset[TIMESERIES]) / len(dict_asset[TIMESERIES]),
UNIT: unit,
},
Expand Down
14 changes: 10 additions & 4 deletions src/C2_economic_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
- calculate present costs based on annuity
- calculate effective fuel price cost, in case there is a annual fuel price change (this functionality still has to be checked in this module)
"""
import logging
import pandas as pd

from src.constants import UNIT_HOUR

Expand All @@ -27,8 +29,6 @@
LIFETIME_PRICE_DISPATCH,
)

import pandas as pd

# annuity factor to calculate present value of cash flows
def annuity_factor(project_life, discount_factor):
"""
Expand Down Expand Up @@ -128,10 +128,10 @@ def capex_from_investment(
# Specific capex for the optimization
specific_capex = first_time_investment + specific_replacement_costs_optimized

# Calculating the replacement costs per unit for the currently already installed assets
specific_replacement_costs_installed = get_replacement_costs(
age_of_asset, project_life, lifetime, first_time_investment, discount_factor
)
# Calculating the replacement costs per unit for the currently already installed assets
return (
specific_capex,
specific_replacement_costs_optimized,
Expand Down Expand Up @@ -182,10 +182,16 @@ def get_replacement_costs(

replacement_costs = 0

# In case the asset_lifetime is larger then
# Latest investment is first investment
latest_investment = first_time_investment
# Starting from first investment (in the past for installed capacities)
year = -age_of_asset
if abs(year) >= asset_lifetime:
logging.error(
f"The age of the asset is with {age_of_asset} lower or equal then the {asset_lifetime}. "
f"This does not make sense, as a replacement is imminent or should already have happened."
f"Please check this value."
)

present_value_of_capital_expenditures = pd.Series([latest_investment], index=[year])

Expand Down
20 changes: 17 additions & 3 deletions src/E0_evaluation.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,18 @@
KPI_SCALAR_MATRIX,
KPI_SCALARS_DICT,
OPTIMIZED_FLOWS,
LABEL,
COST_TOTAL,
COST_OPERATIONAL_TOTAL,
COST_INVESTMENT,
COST_UPFRONT,
COST_DISPATCH,
COST_OM,
ANNUITY_TOTAL,
ANNUITY_OM,
LCOE_ASSET,
UNIT_YEAR,
CURR,
)

from src.constants_output import KPI_COST_MATRIX_ENTRIES, KPI_SCALAR_MATRIX_ENTRIES
Expand Down Expand Up @@ -60,6 +72,7 @@ def evaluate_dict(dict_values, results_main, results_meta):
-------
"""

dict_values.update(
{
KPI: {
Expand All @@ -69,6 +82,7 @@ def evaluate_dict(dict_values, results_main, results_meta):
}
}
)

bus_data = {}
# Store all information related to busses in bus_data
for bus in dict_values[ENERGY_BUSSES]:
Expand Down Expand Up @@ -144,11 +158,11 @@ def evaluate_dict(dict_values, results_main, results_meta):
store_result_matrix(dict_values[KPI], dict_values[group][asset])

E3.all_totals(dict_values)

# Processing further KPI
# todo : reactivate function
E3.total_demand_each_sector(dict_values)
E3.add_levelized_cost_of_energy_carriers(dict_values)
E3.total_renewable_and_non_renewable_energy_origin(dict_values)
E3.renewable_share(dict_values)
# E3.add_degree_of_sector_coupling(dict_values) feature not finished

logging.info("Evaluating optimized capacities and dispatch.")
return
Expand Down
88 changes: 86 additions & 2 deletions src/E1_process_results.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

from src.constants import TYPE_NONE
from src.constants_json_strings import (
ECONOMIC_DATA,
FLOW,
INSTALLED_CAP,
INPUT_POWER,
Expand All @@ -25,13 +26,17 @@
KPI_SCALARS_DICT,
OPTIMIZED_FLOWS,
UNIT,
CURR,
UNIT_YEAR,
ENERGY_CONSUMPTION,
LABEL,
VALUE,
OPTIMIZE_CAP,
SIMULATION_SETTINGS,
EVALUATED_PERIOD,
TIMESERIES_PEAK,
TIMESERIES_TOTAL,
TIMESERIES_AVERAGE,
DSO_FEEDIN,
AUTO_SINK,
EXCESS,
Expand All @@ -54,8 +59,12 @@
COST_TOTAL,
COST_UPFRONT,
ANNUITY_TOTAL,
ANNUITY_OM,
LCOE_ASSET,
)

from src.constants_output import KPI_COST_MATRIX_ENTRIES


def get_timeseries_per_bus(dict_values, bus_data):
r"""
Expand Down Expand Up @@ -524,8 +533,8 @@ def convert_demand_to_dataframe(dict_values):
dem: [
demands[dem][UNIT],
demands[dem][TIMESERIES_PEAK][VALUE],
demands[dem]["timeseries_average"][VALUE],
demands[dem]["timeseries_total"][VALUE],
demands[dem][TIMESERIES_AVERAGE][VALUE],
demands[dem][TIMESERIES_TOTAL][VALUE],
]
}
)
Expand Down Expand Up @@ -722,3 +731,78 @@ def convert_costs_to_dataframe(dict_values):
df_pie_plot.iloc[-1, 0] = "Total"

return df_pie_plot


def convert_scalars_to_dataframe(dict_values):
"""
Processes the scalar system-wide KPI so that they can be included in the report
Parameters
----------
dict_values: dict
output values of MVS
Returns
-------
kpi_scalars_dataframe: :pandas:`pandas.DataFrame<frame>`
Dataframe to be displayed as a table in the report
Notes
-----
Currently, as the KPI_SCALARS_DICT does not hold any units, the table printed in the report is unit-les.
"""

units_cost_kpi = get_units_of_cost_matrix_entries(
dict_values[ECONOMIC_DATA], dict_values[KPI][KPI_SCALARS_DICT]
)

kpi_scalars_dataframe = pd.DataFrame(
dict_values[KPI][KPI_SCALARS_DICT], index=[VALUE]
)
kpi_names = kpi_scalars_dataframe.columns
kpi_scalars_dataframe = kpi_scalars_dataframe.transpose()
kpi_scalars_dataframe[KPI] = kpi_names
kpi_scalars_dataframe[UNIT] = units_cost_kpi
kpi_scalars_dataframe = kpi_scalars_dataframe[[KPI, UNIT, VALUE]]

return kpi_scalars_dataframe


def get_units_of_cost_matrix_entries(dict_economic, kpi_list):
"""
Determines the units of the costs KPI to be stored to :pandas: DataFrame.
Parameters
----------
dict_economic:
Economic project data
kpi_list:
List of cost matrix entries
Returns
-------
unit_list: list
List of units for the :pandas: DataFrame to be created
"""

unit_list = []
kpi_cost_unit_dict = {
LABEL: None,
UNIT: None,
COST_TOTAL: dict_economic[CURR],
COST_OPERATIONAL_TOTAL: dict_economic[CURR],
COST_INVESTMENT: dict_economic[CURR],
COST_UPFRONT: dict_economic[CURR],
COST_DISPATCH: dict_economic[CURR],
COST_OM: dict_economic[CURR],
ANNUITY_TOTAL: dict_economic[CURR] + "/" + UNIT_YEAR,
ANNUITY_OM: dict_economic[CURR] + "/" + UNIT_YEAR,
LCOE_ASSET: dict_economic[CURR] + "/" + "energy carrier unit",
}
for key in kpi_list:
if key not in kpi_cost_unit_dict:
unit_list.append("NA")
else:
unit_list.append(kpi_cost_unit_dict[key])
return unit_list
15 changes: 7 additions & 8 deletions src/E2_economics.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,6 @@
"""


class UnexpectedValueError(UserWarning):
"""Exception raised for value errors during economic post-processing"""

pass


class MissingParametersForEconomicEvaluation(UserWarning):
"Warning if one or more parameters are missing for economic post-processing of an asset"
pass
Expand Down Expand Up @@ -100,6 +94,11 @@ def get_costs(dict_asset, economic_data):
- COST_OPERATIONAL_TOTAL
- ANNUITY_TOTAL
- ANNUITY_OM
Tested with:
- test_all_cost_info_parameters_added_to_dict_asset()
- Test_Economic_KPI.test_benchmark_Economic_KPI_C2_E2()
"""

logging.debug("Calculating costs of asset %s", dict_asset[LABEL])
Expand Down Expand Up @@ -278,7 +277,7 @@ def calculate_dispatch_expenditures(dispatch_price, flow, asset):
elif isinstance(dispatch_price, pd.Series):
dispatch_expenditures = sum(dispatch_price * flow)
else:
raise UnexpectedValueError(
raise TypeError(
f"The dispatch price of asset {asset} is neither float nor pd.Series but {type(dispatch_price)}."
f"Please adapt E2.calculate_dispatch_costs() to evaluate the dispatch_expenditures of the asset."
)
Expand Down Expand Up @@ -358,7 +357,7 @@ def calculate_costs_replacement(
Initial capacity installed
optimized_capacity: float
Add capacity to be installed, as optimized
Additional capacity to be installed, as optimized
Returns
-------
Expand Down

0 comments on commit 7ae1333

Please sign in to comment.