diff --git a/CaseStudy.py b/CaseStudy.py
index b89db41..fca9eb9 100644
--- a/CaseStudy.py
+++ b/CaseStudy.py
@@ -1,18 +1,44 @@
import copy
import os
import warnings
+from pathlib import Path
from typing import Optional, Self
import numpy as np
import pandas as pd
import ExcelReader
+from printer import Printer
+
+printer = Printer.getInstance()
class CaseStudy:
+ # Lists of dataframes based on their dependencies - every table should only be present in one of these lists
+ rpk_dependent_dataframes: list[str] = ["dPower_Demand",
+ "dPower_Hindex",
+ "dPower_ImpExpProfiles",
+ "dPower_Inflows",
+ "dPower_VRESProfiles"]
+ rp_only_dependent_dataframes: list[str] = ["dPower_WeightsRP"]
+ k_only_dependent_dataframes: list[str] = ["dPower_WeightsK"]
+ non_time_dependent_dataframes: list[str] = ["dPower_BusInfo",
+ "dPower_ImpExpHubs",
+ "dPower_Network",
+ "dPower_Storage",
+ "dPower_ThermalGen",
+ "dPower_VRES"]
+ non_dependent_dataframes: list[str] = ["dGlobal_Parameters",
+ "dGlobal_Scenarios",
+ "dPower_Parameters"]
+
+ # Subsets and supersets of the above lists
+ rp_dependent_dataframes: list[str] = rpk_dependent_dataframes + rp_only_dependent_dataframes
+ k_dependent_dataframes: list[str] = rpk_dependent_dataframes + k_only_dependent_dataframes
+ scenario_dependent_dataframes: list[str] = rpk_dependent_dataframes + rp_only_dependent_dataframes + k_only_dependent_dataframes + non_time_dependent_dataframes
def __init__(self,
- data_folder: str,
+ data_folder: str | Path,
do_not_scale_units: bool = False,
do_not_merge_single_node_buses: bool = False,
global_parameters_file: str = "Global_Parameters.xlsx", dGlobal_Parameters: pd.DataFrame = None,
@@ -31,7 +57,7 @@ def __init__(self,
power_hindex_file: str = "Power_Hindex.xlsx", dPower_Hindex: pd.DataFrame = None,
power_impexphubs_file: str = "Power_ImpExpHubs.xlsx", dPower_ImpExpHubs: pd.DataFrame = None,
power_impexpprofiles_file: str = "Power_ImpExpProfiles.xlsx", dPower_ImpExpProfiles: pd.DataFrame = None):
- self.data_folder = data_folder if data_folder.endswith("/") else data_folder + "/"
+ self.data_folder = str(data_folder) if str(data_folder).endswith("/") else str(data_folder) + "/"
self.do_not_scale_units = do_not_scale_units
self.do_not_merge_single_node_buses = do_not_merge_single_node_buses
@@ -45,7 +71,16 @@ def __init__(self,
self.dGlobal_Scenarios = dGlobal_Scenarios
else:
self.global_scenarios_file = global_scenarios_file
- self.dGlobal_Scenarios = ExcelReader.get_Global_Scenarios(self.data_folder + self.global_scenarios_file)
+ if not os.path.exists(self.data_folder + self.global_scenarios_file):
+ printer.warning(f"Executing without 'Global_Scenarios' (since no file was found at '{self.data_folder + self.global_scenarios_file}').")
+
+ # Create dataframe for only one Scenario
+ dGlobal_Scenarios = pd.DataFrame({"excl": np.nan, "id": np.nan, "scenarioID": ["ScenarioA"], "relativeWeight": [1], "comments": np.nan, "scenario": ["Scenarios"]})
+ dGlobal_Scenarios = dGlobal_Scenarios.set_index("scenarioID")
+
+ self.dGlobal_Scenarios = dGlobal_Scenarios
+ else:
+ self.dGlobal_Scenarios = ExcelReader.get_Global_Scenarios(self.data_folder + self.global_scenarios_file)
if dPower_Parameters is not None:
self.dPower_Parameters = dPower_Parameters
@@ -163,7 +198,7 @@ def scale_CaseStudy(self):
if self.dPower_Parameters["pEnableThermalGen"]:
self.scale_dPower_ThermalGen()
- if self.dPower_Inflows is not None:
+ if hasattr(self, "dPower_Inflows") and self.dPower_Inflows is not None:
self.scale_dPower_Inflows()
if self.dPower_Parameters["pEnableVRES"]:
@@ -202,8 +237,6 @@ def scale_dPower_Demand(self):
self.dPower_Demand["value"] *= self.power_scaling_factor
def scale_dPower_ThermalGen(self):
- self.dPower_ThermalGen = self.dPower_ThermalGen[(self.dPower_ThermalGen["ExisUnits"] > 0) | (self.dPower_ThermalGen["EnableInvest"] > 0)] # Filter out all generators that are not existing and not investable
-
self.dPower_ThermalGen['EFOR'] = self.dPower_ThermalGen['EFOR'].fillna(0) # Fill NaN values with 0 for EFOR
# Only FuelCost is adjusted by efficiency (OMVarCost is not), then both are scaled by the cost_scaling_factor / power_scaling_factor
@@ -239,7 +272,6 @@ def scale_dPower_Inflows(self):
self.dPower_Inflows["value"] *= self.power_scaling_factor
def scale_dPower_VRES(self):
- self.dPower_VRES = self.dPower_VRES[(self.dPower_VRES["ExisUnits"] > 0) | ((self.dPower_VRES["EnableInvest"] > 0) & (self.dPower_VRES["MaxInvest"] > 0))] # Filter out all generators that are not existing and not investable
if "MinProd" not in self.dPower_VRES.columns:
self.dPower_VRES['MinProd'] = 0
@@ -251,7 +283,6 @@ def scale_dPower_VRES(self):
self.dPower_VRES['Qmax'] = self.dPower_VRES['Qmax'].fillna(0) * self.reactive_power_scaling_factor
def scale_dPower_Storage(self):
- self.dPower_Storage = self.dPower_Storage[(self.dPower_Storage["ExisUnits"] > 0) | ((self.dPower_Storage["EnableInvest"] > 0) & (self.dPower_Storage["MaxInvest"] > 0))] # Filter out all generators that are not existing and not investable
self.dPower_Storage['IniReserve'] = self.dPower_Storage['IniReserve'].fillna(0)
self.dPower_Storage['MinReserve'] = self.dPower_Storage['MinReserve'].fillna(0)
self.dPower_Storage['MinProd'] = self.dPower_Storage["MinProd"].fillna(0)
@@ -273,8 +304,10 @@ def scale_dPower_ImpExpHubs(self):
def scale_dPower_ImpExpProfiles(self):
self.dPower_ImpExpProfiles["ImpExp"] *= self.power_scaling_factor
+ self.dPower_ImpExpProfiles["Price"] *= self.cost_scaling_factor / self.power_scaling_factor
def get_dGlobal_Parameters(self):
+ ExcelReader.check_LEGOExcel_version(self.data_folder + self.global_parameters_file, "v0.1.0", False)
dGlobal_Parameters = pd.read_excel(self.data_folder + self.global_parameters_file, skiprows=[0, 1])
dGlobal_Parameters = dGlobal_Parameters.drop(dGlobal_Parameters.columns[0], axis=1)
dGlobal_Parameters = dGlobal_Parameters.set_index('Solver Options')
@@ -288,6 +321,7 @@ def get_dGlobal_Parameters(self):
return dGlobal_Parameters
def get_dPower_Parameters(self):
+ ExcelReader.check_LEGOExcel_version(self.data_folder + self.power_parameters_file, "v0.1.0", False)
dPower_Parameters = pd.read_excel(self.data_folder + self.power_parameters_file, skiprows=[0, 1])
dPower_Parameters = dPower_Parameters.drop(dPower_Parameters.columns[0], axis=1)
dPower_Parameters = dPower_Parameters.dropna(how="all")
@@ -600,90 +634,75 @@ def to_full_hourly_model(self, inplace: bool) -> Optional['CaseStudy']:
else:
return None
- def _filter_dataframe(self, df_name: str, scenario_name: str) -> None:
- """
- Filters the dataframe with the given name to only include the scenario with the given name.
- :param df_name: The name of the dataframe to filter.
- :param scenario_name: The name of the scenario to filter for.
- :return: None
- """
- if not hasattr(self, df_name):
- raise ValueError(f"Dataframe '{df_name}' not found in the case study. Please check the input data.")
- df = getattr(self, df_name)
-
- filtered_df = df.loc[df['scenario'] == scenario_name]
-
- if len(df) > 0 and len(filtered_df) == 0:
- raise ValueError(f"Scenario '{scenario_name}' not found in '{df_name}'. Please check the input data.")
-
- setattr(self, df_name, filtered_df)
-
- def filter_scenario(self, scenario_name) -> Self:
+ def filter_scenario(self, scenario_name, inplace: bool = False) -> Optional[Self]:
"""
Filters each (relevant) dataframe in the case study to only include the scenario with the given name.
:param scenario_name: The name of the scenario to filter for.
- :return: Copy of the case study with the filtered dataframes.
+ :param inplace: If True, modifies the current instance. If False, returns a new instance.
+ :return: None if inplace is True, otherwise a new CaseStudy instance.
"""
- caseStudy = self.copy()
+ caseStudy = self if inplace else self.copy()
- # dGlobal_Parameters is not filtered, as it is the same for all scenarios
- # dPower_Parameters is not filtered, as it is the same for all scenarios
- caseStudy._filter_dataframe("dPower_BusInfo", scenario_name)
- caseStudy._filter_dataframe("dPower_Network", scenario_name)
- caseStudy._filter_dataframe("dPower_Demand", scenario_name)
- caseStudy._filter_dataframe("dPower_WeightsRP", scenario_name)
- caseStudy._filter_dataframe("dPower_WeightsK", scenario_name)
- caseStudy._filter_dataframe("dPower_Hindex", scenario_name)
+ for df_name in CaseStudy.scenario_dependent_dataframes:
+ if hasattr(caseStudy, df_name):
+ df = getattr(caseStudy, df_name)
+ if df is None:
+ continue
- if hasattr(caseStudy, "dPower_ThermalGen"):
- caseStudy._filter_dataframe("dPower_ThermalGen", scenario_name)
- if hasattr(caseStudy, "dPower_Inflows"):
- caseStudy._filter_dataframe("dPower_Inflows", scenario_name)
- if hasattr(caseStudy, "dPower_VRES"):
- caseStudy._filter_dataframe("dPower_VRES", scenario_name)
- caseStudy._filter_dataframe("dPower_VRESProfiles", scenario_name)
- if hasattr(caseStudy, "dPower_Storage"):
- caseStudy._filter_dataframe("dPower_Storage", scenario_name)
- if hasattr(caseStudy, "dPower_ImpExpHubs") and caseStudy.dPower_ImpExpHubs is not None:
- caseStudy._filter_dataframe("dPower_ImpExpHubs", scenario_name)
- caseStudy._filter_dataframe("dPower_ImpExpProfiles", scenario_name)
+ filtered_df = df.loc[df['scenario'] == scenario_name]
- return caseStudy
+ if len(df) > 0 and len(filtered_df) == 0:
+ raise ValueError(f"Scenario '{scenario_name}' not found in '{df_name}'. Please check the input data.")
- def filter_timesteps(self, start: str, end: str) -> Self:
- case_study = self.copy()
+ setattr(caseStudy, df_name, filtered_df)
- df_names = ["dPower_Demand", "dPower_VRESProfiles", "dPower_WeightsK", "dPower_Hindex"]
+ return None if inplace else caseStudy
- for df_name in df_names:
- df = getattr(case_study, df_name)
+ def filter_timesteps(self, start: str, end: str, inplace: bool = False) -> Optional[Self]:
+ """
+ Filters each (relevant) dataframe in the case study to only include the timesteps between start and end (both inclusive).
+ :param start: Start timestep (inclusive).
+ :param end: End timestep (inclusive).
+ :param inplace: If True, modifies the current instance. If False, returns a new instance.
+ :return: None if inplace is True, otherwise a new CaseStudy instance.
+ """
+ case_study = self if inplace else self.copy()
- index = df.index.names
- df_reset = df.reset_index()
+ for df_name in CaseStudy.k_dependent_dataframes:
+ if hasattr(case_study, df_name):
+ df = getattr(case_study, df_name)
- filtered_df_reset = df_reset.loc[(df_reset['k'] >= start) & (df_reset['k'] <= end)]
+ index = df.index.names
+ df_reset = df.reset_index()
- filtered_df = filtered_df_reset.set_index(index)
+ filtered_df_reset = df_reset.loc[(df_reset['k'] >= start) & (df_reset['k'] <= end)]
- setattr(case_study, df_name, filtered_df)
+ filtered_df = filtered_df_reset.set_index(index)
- return case_study
+ setattr(case_study, df_name, filtered_df)
- def filter_representative_periods(self, rp: str) -> Self:
- case_study = self.copy()
+ return None if inplace else case_study
- df_names = ["dPower_Demand", "dPower_VRESProfiles", "dPower_WeightsRP", "dPower_Hindex"]
+ def filter_representative_periods(self, rp: str, inplace: bool = False) -> Optional[Self]:
+ """
+ Filters each (relevant) dataframe in the case study to only include the representative period with the given name.
+ :param rp: Name of the representative period to filter for.
+ :param inplace: If True, modifies the current instance. If False, returns a new instance.
+ :return: None if inplace is True, otherwise a new CaseStudy instance.
+ """
+ case_study = self if inplace else self.copy()
- for df_name in df_names:
- df = getattr(case_study, df_name)
+ for df_name in CaseStudy.rp_dependent_dataframes:
+ if hasattr(case_study, df_name):
+ df = getattr(case_study, df_name)
- index = df.index.names
- df_reset = df.reset_index()
+ index = df.index.names
+ df_reset = df.reset_index()
- filtered_df_reset = df_reset.loc[(df_reset['rp'] == rp)]
+ filtered_df_reset = df_reset.loc[(df_reset['rp'] == rp)]
- filtered_df = filtered_df_reset.set_index(index)
+ filtered_df = filtered_df_reset.set_index(index)
- setattr(case_study, df_name, filtered_df)
+ setattr(case_study, df_name, filtered_df)
- return case_study
+ return None if inplace else case_study
diff --git a/ExcelReader.py b/ExcelReader.py
index 2a79bbf..c46af00 100644
--- a/ExcelReader.py
+++ b/ExcelReader.py
@@ -9,7 +9,7 @@
printer = Printer.getInstance()
-def __check_LEGOExcel_version(excel_file_path: str, version_specifier: str, fail_on_wrong_version: bool = False):
+def check_LEGOExcel_version(excel_file_path: str, version_specifier: str, fail_on_wrong_version: bool = False):
"""
Check if the Excel file has the correct version specifier.
:param excel_file_path: Path to the Excel file
@@ -42,7 +42,7 @@ def __read_non_pivoted_file(excel_file_path: str, version_specifier: str, indice
:param fail_on_wrong_version: If True, raise an error if the version of the Excel file does not match the expected version
:return: DataFrame containing the data from the Excel file
"""
- __check_LEGOExcel_version(excel_file_path, version_specifier, fail_on_wrong_version)
+ check_LEGOExcel_version(excel_file_path, version_specifier, fail_on_wrong_version)
xls = pd.ExcelFile(excel_file_path)
data = pd.DataFrame()
@@ -93,7 +93,7 @@ def get_Data_Packages(excel_file_path: str, keep_excluded_entries: bool = False,
dData_Packages = __read_non_pivoted_file(excel_file_path, "v0.1.0", ["dataPackage"], False, False, fail_on_wrong_version)
if keep_excluded_entries:
- printer.warning("'keep_excluded_entries' is set for 'get_dData_Packages', although nothing is excluded anyway - please check if this is intended.")
+ printer.warning("'keep_excluded_entries' is set for 'get_Data_Packages', although nothing is excluded anyway - please check if this is intended.")
return dData_Packages
@@ -109,7 +109,7 @@ def get_Data_Sources(excel_file_path: str, keep_excluded_entries: bool = False,
dData_Sources = __read_non_pivoted_file(excel_file_path, "v0.1.0", ["dataSource"], False, False, fail_on_wrong_version)
if keep_excluded_entries:
- printer.warning("'keep_excluded_entries' is set for 'get_dData_Sources', although nothing is excluded anyway - please check if this is intended.")
+ printer.warning("'keep_excluded_entries' is set for 'get_Data_Sources', although nothing is excluded anyway - please check if this is intended.")
return dData_Sources
@@ -153,10 +153,10 @@ def get_Power_Demand(excel_file_path: str, keep_excluded_entries: bool = False,
:param fail_on_wrong_version: If True, raise an error if the version of the Excel file does not match the expected version
:return: dPower_Demand
"""
- dPower_Demand = __read_pivoted_file(excel_file_path, "v0.1.2", ['rp', 'k', 'i'], 'k', ['rp', 'i', 'dataPackage', 'dataSource', 'id'], False, False, fail_on_wrong_version)
+ dPower_Demand = __read_pivoted_file(excel_file_path, "v0.1.4", ['rp', 'k', 'i'], 'k', ['rp', 'i', 'dataPackage', 'dataSource', 'id'], False, False, fail_on_wrong_version)
if keep_excluded_entries:
- printer.warning("'keep_excluded_entries' is set for 'get_dPower_Demand', although nothing is excluded anyway - please check if this is intended.")
+ printer.warning("'keep_excluded_entries' is set for 'get_Power_Demand', although nothing is excluded anyway - please check if this is intended.")
return dPower_Demand
@@ -169,10 +169,10 @@ def get_Power_Demand_KInRows(excel_file_path: str, keep_excluded_entries: bool =
:param fail_on_wrong_version: If True, raise an error if the version of the Excel file does not match the expected version
:return: dPower_Demand_KInRows
"""
- dPower_Demand_KInRows = __read_pivoted_file(excel_file_path, "v0.1.2", ['rp', 'k', 'i'], 'i', ['rp', 'k', 'dataPackage', 'dataSource', 'id'], False, False, fail_on_wrong_version)
+ dPower_Demand_KInRows = __read_pivoted_file(excel_file_path, "v0.1.4", ['rp', 'k', 'i'], 'i', ['rp', 'k', 'dataPackage', 'dataSource', 'id'], False, False, fail_on_wrong_version)
if keep_excluded_entries:
- printer.warning("'keep_excluded_entries' is set for 'get_dPower_Demand_KInRows', although nothing is excluded anyway - please check if this is intended.")
+ printer.warning("'keep_excluded_entries' is set for 'get_Power_Demand_KInRows', although nothing is excluded anyway - please check if this is intended.")
return dPower_Demand_KInRows
@@ -185,10 +185,10 @@ def get_Power_Hindex(excel_file_path: str, keep_excluded_entries: bool = False,
:param fail_on_wrong_version: If True, raise an error if the version of the Excel file does not match the expected version
:return: dPower_Hindex
"""
- dPower_Hindex = __read_non_pivoted_file(excel_file_path, "v0.1.2", ["p", "rp", "k"], False, False, fail_on_wrong_version)
+ dPower_Hindex = __read_non_pivoted_file(excel_file_path, "v0.1.3", ["p", "rp", "k"], False, False, fail_on_wrong_version)
if keep_excluded_entries:
- printer.warning("'keep_excluded_entries' is set for 'get_dPower_Hindex', although nothing is excluded anyway - please check if this is intended.")
+ printer.warning("'keep_excluded_entries' is set for 'get_Power_Hindex', although nothing is excluded anyway - please check if this is intended.")
return dPower_Hindex
@@ -201,14 +201,30 @@ def get_Power_Inflows(excel_file_path: str, keep_excluded_entries: bool = False,
:param fail_on_wrong_version: If True, raise an error if the version of the Excel file does not match the expected version
:return: dPower_Inflows
"""
- dPower_Inflows = __read_pivoted_file(excel_file_path, "v0.0.1", ['rp', 'k', 'g'], 'k', ['rp', 'g', 'dataPackage', 'dataSource', 'id'], False, False, fail_on_wrong_version)
+ dPower_Inflows = __read_pivoted_file(excel_file_path, "v0.1.0", ['rp', 'k', 'g'], 'k', ['rp', 'g', 'dataPackage', 'dataSource', 'id'], False, False, fail_on_wrong_version)
if keep_excluded_entries:
- printer.warning("'keep_excluded_entries' is set for 'get_dPower_Inflows', although nothing is excluded anyway - please check if this is intended.")
+ printer.warning("'keep_excluded_entries' is set for 'get_Power_Inflows', although nothing is excluded anyway - please check if this is intended.")
return dPower_Inflows
+def get_Power_Inflows_KInRows(excel_file_path: str, keep_excluded_entries: bool = False, fail_on_wrong_version: bool = False) -> pd.DataFrame:
+ """
+ Read the dPower_Inflows_KInRows data from the Excel file.
+ :param excel_file_path: Path to the Excel file
+ :param keep_excluded_entries: Unused but kept for compatibility with other functions
+ :param fail_on_wrong_version: If True, raise an error if the version of the Excel file does not match the expected version
+ :return: dPower_Inflows
+ """
+ dPower_Inflows_KInRows = __read_pivoted_file(excel_file_path, "v0.1.0", ['rp', 'k', 'g'], 'g', ['rp', 'k', 'dataPackage', 'dataSource', 'id'], False, False, fail_on_wrong_version)
+
+ if keep_excluded_entries:
+ printer.warning("'keep_excluded_entries' is set for 'get_Power_Inflows_KInRows', although nothing is excluded anyway - please check if this is intended.")
+
+ return dPower_Inflows_KInRows
+
+
def get_Power_Network(excel_file_path: str, keep_excluded_entries: bool = False, fail_on_wrong_version: bool = False) -> pd.DataFrame:
"""
Read the dPower_Network data from the Excel file.
@@ -230,7 +246,7 @@ def get_Power_Storage(excel_file_path: str, keep_excluded_entries: bool = False,
:param fail_on_wrong_version: If True, raise an error if the version of the Excel file does not match the expected version
:return: dPower_Storage
"""
- dPower_Storage = __read_non_pivoted_file(excel_file_path, "v0.0.1", ["g"], True, keep_excluded_entries, fail_on_wrong_version)
+ dPower_Storage = __read_non_pivoted_file(excel_file_path, "v0.0.2", ["g"], True, keep_excluded_entries, fail_on_wrong_version)
return dPower_Storage
@@ -269,10 +285,10 @@ def get_Power_VRESProfiles(excel_file_path: str, keep_excluded_entries: bool = F
:param fail_on_wrong_version: If True, raise an error if the version of the Excel file does not match the expected version
:return: dPower_VRESProfiles
"""
- dPower_VRESProfiles = __read_pivoted_file(excel_file_path, "v0.1.0", ['rp', 'k', 'g'], 'k', ['rp', 'g', 'dataPackage', 'dataSource', 'id'], False, False, fail_on_wrong_version)
+ dPower_VRESProfiles = __read_pivoted_file(excel_file_path, "v0.1.1", ['rp', 'k', 'g'], 'k', ['rp', 'g', 'dataPackage', 'dataSource', 'id'], False, False, fail_on_wrong_version)
if keep_excluded_entries:
- printer.warning("'keep_excluded_entries' is set for 'get_dPower_VRESProfiles', although nothing is excluded anyway - please check if this is intended.")
+ printer.warning("'keep_excluded_entries' is set for 'get_Power_VRESProfiles', although nothing is excluded anyway - please check if this is intended.")
return dPower_VRESProfiles
@@ -285,10 +301,10 @@ def get_Power_VRESProfiles_KInRows(excel_file_path: str, keep_excluded_entries:
:param fail_on_wrong_version: If True, raise an error if the version of the Excel file does not match the expected version
:return: dPower_VRESProfiles_KInRows
"""
- dPower_VRESProfiles_KInRows = __read_pivoted_file(excel_file_path, "v0.1.0", ['rp', 'k', 'g'], 'g', ['rp', 'k', 'dataPackage', 'dataSource', 'id'], False, False, fail_on_wrong_version)
+ dPower_VRESProfiles_KInRows = __read_pivoted_file(excel_file_path, "v0.1.1", ['rp', 'k', 'g'], 'g', ['rp', 'k', 'dataPackage', 'dataSource', 'id'], False, False, fail_on_wrong_version)
if keep_excluded_entries:
- printer.warning("'keep_excluded_entries' is set for 'get_dPower_VRESProfiles_KInRows', although nothing is excluded anyway - please check if this is intended.")
+ printer.warning("'keep_excluded_entries' is set for 'get_Power_VRESProfiles_KInRows', although nothing is excluded anyway - please check if this is intended.")
return dPower_VRESProfiles_KInRows
@@ -301,10 +317,10 @@ def get_Power_WeightsK(excel_file_path: str, keep_excluded_entries: bool = False
:param fail_on_wrong_version: If True, raise an error if the version of the Excel file does not match the expected version
:return: dPower_WeightsK
"""
- dPower_WeightsK = __read_non_pivoted_file(excel_file_path, "v0.1.3", ["k"], False, False, fail_on_wrong_version)
+ dPower_WeightsK = __read_non_pivoted_file(excel_file_path, "v0.1.4", ["k"], False, False, fail_on_wrong_version)
if keep_excluded_entries:
- printer.warning("'keep_excluded_entries' is set for 'get_dPower_WeightsK', although nothing is excluded anyway - please check if this is intended.")
+ printer.warning("'keep_excluded_entries' is set for 'get_Power_WeightsK', although nothing is excluded anyway - please check if this is intended.")
return dPower_WeightsK
@@ -320,7 +336,7 @@ def get_Power_WeightsRP(excel_file_path: str, keep_excluded_entries: bool = Fals
dPower_WeightsRP = __read_non_pivoted_file(excel_file_path, "v0.1.3", ["rp"], False, False, fail_on_wrong_version)
if keep_excluded_entries:
- printer.warning("'keep_excluded_entries' is set for 'get_dPower_WeightsRP', although nothing is excluded anyway - please check if this is intended.")
+ printer.warning("'keep_excluded_entries' is set for 'get_Power_WeightsRP', although nothing is excluded anyway - please check if this is intended.")
return dPower_WeightsRP
diff --git a/ExcelWriter.py b/ExcelWriter.py
index dff1d12..cd88198 100644
--- a/ExcelWriter.py
+++ b/ExcelWriter.py
@@ -2,6 +2,7 @@
import time
import xml.etree.ElementTree as ET
from copy import copy, deepcopy
+from pathlib import Path
import numpy as np
import openpyxl
@@ -11,6 +12,7 @@
import ExcelReader
import TableDefinition
+from CaseStudy import CaseStudy
from TableDefinition import CellStyle, Alignment, Font, Color, Text, Column, NumberFormat, TableDefinition
from printer import Printer
@@ -97,20 +99,26 @@ def _write_Excel_from_definition(self, data: pd.DataFrame, folder_path: str, exc
if column.db_name != "NOEXCL": # Skip first column if it is the (empty and thus unused) placeholder for the excl column
pivot_columns.append(column.db_name)
+ column_templates = copy(excel_definition.columns) # Create a copy of the column definitions and adapt this copy for pivoted data
if target_column is not None:
data.reset_index(inplace=True)
data = data.pivot(index=pivot_columns + ["scenario"], columns=target_column.db_name, values="value")
- excel_definition.columns.remove(target_column) # Remove the pivot column from the list of columns
+ column_templates.remove(target_column) # Remove the pivot column from the list of columns
for i, column in enumerate(data.columns):
col_definition = copy(target_column)
col_definition.db_name = column
col_definition.readable_name = column
if i != 0: # Remove description for all but the first pivoted column
col_definition.description = None
- excel_definition.columns.append(col_definition) # Add the new column definition to the list of columns
+ column_templates.append(col_definition) # Add the new column definition to the list of columns
data.reset_index(inplace=True)
+ if len(data) == 0:
+ printer.warning(f"No data found for Excel definition '{excel_definition_id}' - writing an empty file.")
+ data = pd.DataFrame(columns=[col.db_name for col in column_templates] + ["scenario"])
+ scenarios = ["ScenarioA"]
+
for scenario_index, scenario in enumerate(scenarios):
scenario_data = data[data["scenario"] == scenario]
@@ -126,11 +134,12 @@ def _write_Excel_from_definition(self, data: pd.DataFrame, folder_path: str, exc
ws.freeze_panes = "C8" # Freeze panes at row 8 (below the header)
# Prepare row heights
+ ws.row_dimensions[1].height = 24
ws.row_dimensions[5].height = excel_definition.description_row_height
ws.row_dimensions[6].height = 30 # Standard for database behavior row
# Prepare header columns
- for i, column in enumerate(excel_definition.columns):
+ for i, column in enumerate(column_templates):
if i == 1: # Column with title text & 'Format' text
ws.cell(row=1, column=i + 1, value=excel_definition.sheet_header)
ExcelWriter.__setCellStyle(self.cell_styles["title"], ws.cell(row=1, column=i + 1))
@@ -185,7 +194,7 @@ def _write_Excel_from_definition(self, data: pd.DataFrame, folder_path: str, exc
# Write data to Excel
scenario_data = scenario_data.reset_index()
for i, values in scenario_data.iterrows():
- for j, col in enumerate(excel_definition.columns):
+ for j, col in enumerate(column_templates):
if col.readable_name is None and j == 0: continue # Skip first column if it is empty, since it is the (unused) placeholder for the excl column
if col.db_name == "excl": # Excl. column is written by placing 'X' in lines which should be excluded
ws.cell(row=i + 8, column=j + 1, value='X' if isinstance(values[col.db_name], str) or not np.isnan(values[col.db_name]) else None)
@@ -193,13 +202,40 @@ def _write_Excel_from_definition(self, data: pd.DataFrame, folder_path: str, exc
ws.cell(row=i + 8, column=j + 1, value=values[col.db_name])
ExcelWriter.__setCellStyle(col.cell_style, ws.cell(row=i + 8, column=j + 1))
- path = folder_path + "/" + excel_definition.file_name + ".xlsx"
+ path = folder_path + ("/" if not folder_path.endswith("/") else "") + excel_definition.file_name + ".xlsx"
if not os.path.exists(os.path.dirname(path)) and os.path.dirname(path) != "":
printer.information(f"Creating folder '{os.path.dirname(path)}'")
os.makedirs(os.path.dirname(path)) # Create folder if it does not exist
wb.save(path)
printer.information(f"Saved Excel file to '{path}' after {time.time() - start_time:.2f} seconds")
+ def write_caseStudy(self, cs: CaseStudy, folder_path: str | Path) -> None:
+ """
+ Write the case study to a folder in LEGO-Excel format.
+ :param cs: CaseStudy object containing the data to be written.
+ :param folder_path: Path to the folder where the Excel files will be saved.
+ :return:
+ """
+ folder_path = str(folder_path)
+
+ self.write_Global_Scenarios(cs.dGlobal_Scenarios, folder_path)
+ self.write_Power_BusInfo(cs.dPower_BusInfo, folder_path)
+ self.write_Power_Demand(cs.dPower_Demand, folder_path)
+ self.write_Power_Hindex(cs.dPower_Hindex, folder_path)
+ if hasattr(cs, "dPower_Inflows"):
+ self.write_Power_Inflows(cs.dPower_Inflows, folder_path)
+ self.write_Power_Network(cs.dPower_Network, folder_path)
+ if hasattr(cs, "dPower_Storage"):
+ self.write_Power_Storage(cs.dPower_Storage, folder_path)
+ if hasattr(cs, "dPower_ThermalGen"):
+ self.write_Power_ThermalGen(cs.dPower_ThermalGen, folder_path)
+ if hasattr(cs, "dPower_VRES"):
+ self.write_Power_VRES(cs.dPower_VRES, folder_path)
+ if hasattr(cs, "dPower_VRESProfiles"):
+ self.write_Power_VRESProfiles(cs.dPower_VRESProfiles, folder_path)
+ self.write_Power_WeightsK(cs.dPower_WeightsK, folder_path)
+ self.write_Power_WeightsRP(cs.dPower_WeightsRP, folder_path)
+
def write_Data_Packages(self, dData_Packages: pd.DataFrame, folder_path: str) -> None:
"""
Write the dData_Packages DataFrame to an Excel file in LEGO format.
@@ -218,23 +254,23 @@ def write_Data_Sources(self, dData_Sources: pd.DataFrame, folder_path: str) -> N
"""
self._write_Excel_from_definition(dData_Sources, folder_path, "Data_Sources")
- def write_Power_BusInfo(self, dPower_BusInfo: pd.DataFrame, folder_path: str) -> None:
+ def write_Global_Scenarios(self, dGlobal_Scenarios: pd.DataFrame, folder_path: str) -> None:
"""
- Write the dPower_BusInfo DataFrame to an Excel file in LEGO format.
- :param dPower_BusInfo: DataFrame containing the dPower_BusInfo data.
+ Write the dGlobal_Scenarios DataFrame to an Excel file in LEGO format.
+ :param dGlobal_Scenarios: DataFrame containing the dGlobal_Scenarios data.
:param folder_path: Path to the folder where the Excel file will be saved.
:return: None
"""
- self._write_Excel_from_definition(dPower_BusInfo, folder_path, "Power_BusInfo")
+ self._write_Excel_from_definition(dGlobal_Scenarios, folder_path, "Global_Scenarios")
- def write_Global_Scenarios(self, dGlobal_Scenarios: pd.DataFrame, folder_path: str) -> None:
+ def write_Power_BusInfo(self, dPower_BusInfo: pd.DataFrame, folder_path: str) -> None:
"""
- Write the dGlobal_Scenarios DataFrame to an Excel file in LEGO format.
- :param dGlobal_Scenarios: DataFrame containing the dGlobal_Scenarios data.
+ Write the dPower_BusInfo DataFrame to an Excel file in LEGO format.
+ :param dPower_BusInfo: DataFrame containing the dPower_BusInfo data.
:param folder_path: Path to the folder where the Excel file will be saved.
:return: None
"""
- self._write_Excel_from_definition(dGlobal_Scenarios, folder_path, "Global_Scenarios")
+ self._write_Excel_from_definition(dPower_BusInfo, folder_path, "Power_BusInfo")
def write_Power_Demand(self, dPower_Demand: pd.DataFrame, folder_path: str) -> None:
"""
@@ -274,6 +310,15 @@ def write_Power_Inflows(self, dPower_Inflows: pd.DataFrame, folder_path: str) ->
"""
self._write_Excel_from_definition(dPower_Inflows, folder_path, "Power_Inflows")
+ def write_Power_Inflows_KInRows(self, dPower_Inflows_KInRows: pd.DataFrame, folder_path: str) -> None:
+ """
+ Write the dPower_Inflows_KInRows DataFrame to an Excel file in LEGO format.
+ :param dPower_Inflows_KInRows: DataFrame containing the dPower_Inflows_KInRows data.
+ :param folder_path: Path to the folder where the Excel file will be saved.
+ :return: None
+ """
+ self._write_Excel_from_definition(dPower_Inflows_KInRows, folder_path, "Power_Inflows_KInRows")
+
def write_Power_Network(self, dPower_Network: pd.DataFrame, folder_path: str) -> None:
"""
Write the dPower_Network DataFrame to an Excel file in LEGO format.
@@ -301,7 +346,7 @@ def write_Power_ThermalGen(self, dPower_ThermalGen: pd.DataFrame, folder_path: s
"""
self._write_Excel_from_definition(dPower_ThermalGen, folder_path, "Power_ThermalGen")
- def write_VRES(self, dPower_VRES: pd.DataFrame, folder_path: str) -> None:
+ def write_Power_VRES(self, dPower_VRES: pd.DataFrame, folder_path: str) -> None:
"""
Write the dPower_VRES DataFrame to an Excel file in LEGO format.
:param dPower_VRES: DataFrame containing the dPower_VRES data.
@@ -310,7 +355,7 @@ def write_VRES(self, dPower_VRES: pd.DataFrame, folder_path: str) -> None:
"""
self._write_Excel_from_definition(dPower_VRES, folder_path, "Power_VRES")
- def write_VRESProfiles(self, dPower_VRESProfiles: pd.DataFrame, folder_path: str) -> None:
+ def write_Power_VRESProfiles(self, dPower_VRESProfiles: pd.DataFrame, folder_path: str) -> None:
"""
Write the dPower_VRESProfiles DataFrame to an Excel file in LEGO format.
:param dPower_VRESProfiles: DataFrame containing the dPower_VRESProfiles data.
@@ -319,7 +364,7 @@ def write_VRESProfiles(self, dPower_VRESProfiles: pd.DataFrame, folder_path: str
"""
self._write_Excel_from_definition(dPower_VRESProfiles, folder_path, "Power_VRESProfiles")
- def write_VRESProfiles_KInRows(self, dPower_VRESProfiles_KInRows: pd.DataFrame, folder_path: str) -> None:
+ def write_Power_VRESProfiles_KInRows(self, dPower_VRESProfiles_KInRows: pd.DataFrame, folder_path: str) -> None:
"""
Write the dPower_VRESProfiles_KInRows DataFrame to an Excel file in LEGO format.
:param dPower_VRESProfiles_KInRows: DataFrame containing the dPower_VRESProfiles_KInRows data.
@@ -428,6 +473,9 @@ def model_to_excel(model: pyomo.core.Model, target_path: str) -> None:
printer.set_width(300)
+ if not args.caseStudyFolder.endswith("/"):
+ args.caseStudyFolder += "/"
+
printer.information(f"Loading case study from '{args.caseStudyFolder}'")
if args.excelDefinitionsPath is None:
@@ -446,12 +494,13 @@ def model_to_excel(model: pyomo.core.Model, target_path: str) -> None:
("Power_Demand_KInRows", f"{args.caseStudyFolder}Power_Demand_KInRows.xlsx", ExcelReader.get_Power_Demand_KInRows, ew.write_Power_Demand_KInRows),
("Power_Hindex", f"{args.caseStudyFolder}Power_Hindex.xlsx", ExcelReader.get_Power_Hindex, ew.write_Power_Hindex),
("Power_Inflows", f"{args.caseStudyFolder}Power_Inflows.xlsx", ExcelReader.get_Power_Inflows, ew.write_Power_Inflows),
+ ("Power_Inflows_KInRows", f"{args.caseStudyFolder}Power_Inflows_KInRows.xlsx", ExcelReader.get_Power_Inflows_KInRows, ew.write_Power_Inflows_KInRows),
("Power_Network", f"{args.caseStudyFolder}Power_Network.xlsx", ExcelReader.get_Power_Network, ew.write_Power_Network),
("Power_Storage", f"{args.caseStudyFolder}Power_Storage.xlsx", ExcelReader.get_Power_Storage, ew.write_Power_Storage),
("Power_ThermalGen", f"{args.caseStudyFolder}Power_ThermalGen.xlsx", ExcelReader.get_Power_ThermalGen, ew.write_Power_ThermalGen),
- ("Power_VRES", f"{args.caseStudyFolder}Power_VRES.xlsx", ExcelReader.get_Power_VRES, ew.write_VRES),
- ("Power_VRESProfiles", f"{args.caseStudyFolder}Power_VRESProfiles.xlsx", ExcelReader.get_Power_VRESProfiles, ew.write_VRESProfiles),
- ("Power_VRESProfiles_KInRows", f"{args.caseStudyFolder}Power_VRESProfiles_KInRows.xlsx", ExcelReader.get_Power_VRESProfiles_KInRows, ew.write_VRESProfiles_KInRows),
+ ("Power_VRES", f"{args.caseStudyFolder}Power_VRES.xlsx", ExcelReader.get_Power_VRES, ew.write_Power_VRES),
+ ("Power_VRESProfiles", f"{args.caseStudyFolder}Power_VRESProfiles.xlsx", ExcelReader.get_Power_VRESProfiles, ew.write_Power_VRESProfiles),
+ ("Power_VRESProfiles_KInRows", f"{args.caseStudyFolder}Power_VRESProfiles_KInRows.xlsx", ExcelReader.get_Power_VRESProfiles_KInRows, ew.write_Power_VRESProfiles_KInRows),
("Power_WeightsK", f"{args.caseStudyFolder}Power_WeightsK.xlsx", ExcelReader.get_Power_WeightsK, ew.write_Power_WeightsK),
("Power_WeightsRP", f"{args.caseStudyFolder}Power_WeightsRP.xlsx", ExcelReader.get_Power_WeightsRP, ew.write_Power_WeightsRP),
("Power_Wind_TechnicalDetails", f"{args.caseStudyFolder}Power_Wind_TechnicalDetails.xlsx", ExcelReader.get_Power_Wind_TechnicalDetails, ew.write_Power_Wind_TechnicalDetails)
diff --git a/TableDefinitions.xml b/TableDefinitions.xml
index 62a43e5..f3499a6 100644
--- a/TableDefinitions.xml
+++ b/TableDefinitions.xml
@@ -63,7 +63,7 @@
- v0.1.2
+ v0.1.4
Power - Demand
30.0
@@ -77,7 +77,7 @@
- v0.1.2
+ v0.1.4
Power - Demand
30.0
@@ -91,7 +91,7 @@
- v0.1.2
+ v0.1.3
Power - Relation among periods and representative periods
30.0
@@ -105,7 +105,7 @@
- v0.0.1
+ v0.1.0
Power - Inflows
60.0
@@ -118,6 +118,20 @@
+
+ v0.1.0
+ Power - Inflows
+ 60.0
+
+
+
+
+
+
+
+
+
+
v0.1.2
Power - Network
@@ -145,7 +159,7 @@
- v0.0.1
+ v0.0.2
Power - Storage
60.0
@@ -165,7 +179,7 @@
-
+
@@ -248,7 +262,7 @@
- v0.1.0
+ v0.1.1
Power - Variable Renewable Energy Sources Profiles
60.0
@@ -262,7 +276,7 @@
- v0.1.0
+ v0.1.1
Power - Variable Renewable Energy Sources Profiles
60.0
@@ -275,7 +289,33 @@
-
+
+ v0.1.4
+ Power - Weights of representative hours
+ 30.0
+
+
+
+
+
+
+
+
+
+
+ v0.1.3
+ Power - Weights of representative periods
+ 30.0
+
+
+
+
+
+
+
+
+
+
v0.1.0
Power - Wind Technical Details
60.0
@@ -310,32 +350,6 @@
-
- v0.1.3
- Power - Weights of representative hours
- 30.0
-
-
-
-
-
-
-
-
-
-
- v0.1.3
- Power - Weights of representative periods
- 30.0
-
-
-
-
-
-
-
-
-
@@ -514,9 +528,9 @@
20.0
rightFloat2
-
- IsLDS
- Whether the storage unit is a long-duration storage (1) or not (0)
+
+ IsLDES
+ Whether the storage unit is a long-duration energy storage (1) or not (0)
[0, 1]
20.0
rightInt
@@ -530,9 +544,9 @@
k
- Representative hour within rp
+ Representative time step within rp
[k]
- 15.0
+ 17.0
general
@@ -916,42 +930,50 @@
-
+
- k
- Capacity factor for each VRES unit at this k
+ g
+ Capacity factor at this generator g
[%, 0-1]
10.57
rightFloat2
-
+
- g
- Capacity factor for each VRES unit at this generator g
+ k
+ Capacity factor at this representative time step k
[%, 0-1]
10.57
rightFloat2
+
+
+ i
+ Demand at this node i
+ [MW]
+ 10.57
+ rightInt
+
k
- Demand at node for representative hour k
- [MWh]
+ Demand at this representative time step k
+ [MW]
10.57
rightInt
-
+
- i
- Demand at node at node i
+ g
+ Inflow at this generator g
[MWh]
- 10.57
- rightInt
+ 14.00
+ rightFloat2
k
- Inflow for each unit at this k
+ Inflow at this representative time step k
[MWh]
10.57
rightFloat2
diff --git a/Utilities.py b/Utilities.py
index 1843358..fc2aba9 100644
--- a/Utilities.py
+++ b/Utilities.py
@@ -4,6 +4,10 @@
import pandas as pd
import tsam.timeseriesaggregation as tsam
+from InOutModule.printer import Printer
+
+printer = Printer.getInstance()
+
def inflowsToCapacityFactors(inflows_df: pd.DataFrame, vres_df: pd.DataFrame, vresProfiles_df: pd.DataFrame) -> pd.DataFrame:
"""
@@ -27,7 +31,9 @@ def inflowsToCapacityFactors(inflows_df: pd.DataFrame, vres_df: pd.DataFrame, vr
df = df.join(maxProd, on='g', how='left')
if df['MaxProd'].isna().any() or (df['MaxProd'] == 0).any():
- raise ValueError("MaxProd is missing or zero for some generators in inflows.")
+ printer.warning(f"Some inflows correspond to generators which are not in Power_VRES (or have MaxProd=0). They will be ignored: {df[df['MaxProd'].isna() | (df['MaxProd'] == 0)].index.get_level_values('g').unique()}")
+ df = df.dropna(subset=['MaxProd'])
+ df = df[df['MaxProd'] != 0]
# Divide inflow value by MaxProd
df['value'] = df['value'] / df['MaxProd']
diff --git a/data/example/Data_Packages.xlsx b/data/example/Data_Packages.xlsx
index e6c2d5b..5256c4c 100644
Binary files a/data/example/Data_Packages.xlsx and b/data/example/Data_Packages.xlsx differ
diff --git a/data/example/Data_Sources.xlsx b/data/example/Data_Sources.xlsx
index 95cae24..e771983 100644
Binary files a/data/example/Data_Sources.xlsx and b/data/example/Data_Sources.xlsx differ
diff --git a/data/example/Global_Scenarios.xlsx b/data/example/Global_Scenarios.xlsx
index 557e4e0..0c0d807 100644
Binary files a/data/example/Global_Scenarios.xlsx and b/data/example/Global_Scenarios.xlsx differ
diff --git a/data/example/Power_BusInfo.xlsx b/data/example/Power_BusInfo.xlsx
index ec352b0..a942a25 100644
Binary files a/data/example/Power_BusInfo.xlsx and b/data/example/Power_BusInfo.xlsx differ
diff --git a/data/example/Power_Demand.xlsx b/data/example/Power_Demand.xlsx
index 3209069..c055d7d 100644
Binary files a/data/example/Power_Demand.xlsx and b/data/example/Power_Demand.xlsx differ
diff --git a/data/example/Power_Demand_KInRows.xlsx b/data/example/Power_Demand_KInRows.xlsx
index d25063d..9ad5a44 100644
Binary files a/data/example/Power_Demand_KInRows.xlsx and b/data/example/Power_Demand_KInRows.xlsx differ
diff --git a/data/example/Power_Hindex.xlsx b/data/example/Power_Hindex.xlsx
index 98bc8a2..c9d355d 100644
Binary files a/data/example/Power_Hindex.xlsx and b/data/example/Power_Hindex.xlsx differ
diff --git a/data/example/Power_Inflows.xlsx b/data/example/Power_Inflows.xlsx
index c58dab2..a5d2ebd 100644
Binary files a/data/example/Power_Inflows.xlsx and b/data/example/Power_Inflows.xlsx differ
diff --git a/data/example/Power_Inflows_KInRows.xlsx b/data/example/Power_Inflows_KInRows.xlsx
new file mode 100644
index 0000000..a1368b7
Binary files /dev/null and b/data/example/Power_Inflows_KInRows.xlsx differ
diff --git a/data/example/Power_Network.xlsx b/data/example/Power_Network.xlsx
index d2c543f..af21c57 100644
Binary files a/data/example/Power_Network.xlsx and b/data/example/Power_Network.xlsx differ
diff --git a/data/example/Power_Storage.xlsx b/data/example/Power_Storage.xlsx
index 007ded9..b67e3c7 100644
Binary files a/data/example/Power_Storage.xlsx and b/data/example/Power_Storage.xlsx differ
diff --git a/data/example/Power_ThermalGen.xlsx b/data/example/Power_ThermalGen.xlsx
index b04974b..62ddc10 100644
Binary files a/data/example/Power_ThermalGen.xlsx and b/data/example/Power_ThermalGen.xlsx differ
diff --git a/data/example/Power_VRES.xlsx b/data/example/Power_VRES.xlsx
index 6d823f0..adc8368 100644
Binary files a/data/example/Power_VRES.xlsx and b/data/example/Power_VRES.xlsx differ
diff --git a/data/example/Power_VRESProfiles.xlsx b/data/example/Power_VRESProfiles.xlsx
index 951546f..33740f4 100644
Binary files a/data/example/Power_VRESProfiles.xlsx and b/data/example/Power_VRESProfiles.xlsx differ
diff --git a/data/example/Power_VRESProfiles_KInRows.xlsx b/data/example/Power_VRESProfiles_KInRows.xlsx
index 618bb2c..b68fc05 100644
Binary files a/data/example/Power_VRESProfiles_KInRows.xlsx and b/data/example/Power_VRESProfiles_KInRows.xlsx differ
diff --git a/data/example/Power_WeightsK.xlsx b/data/example/Power_WeightsK.xlsx
index eea2801..4cf10f4 100644
Binary files a/data/example/Power_WeightsK.xlsx and b/data/example/Power_WeightsK.xlsx differ
diff --git a/data/example/Power_WeightsRP.xlsx b/data/example/Power_WeightsRP.xlsx
index 68a406b..40ea572 100644
Binary files a/data/example/Power_WeightsRP.xlsx and b/data/example/Power_WeightsRP.xlsx differ
diff --git a/data/example/Power_Wind_TechnicalDetails.xlsx b/data/example/Power_Wind_TechnicalDetails.xlsx
index af3736a..8e056ac 100644
Binary files a/data/example/Power_Wind_TechnicalDetails.xlsx and b/data/example/Power_Wind_TechnicalDetails.xlsx differ
diff --git a/tests/test_ExcelReaderWriter.py b/tests/test_ExcelReaderWriter.py
index 151e0d6..bd09443 100644
--- a/tests/test_ExcelReaderWriter.py
+++ b/tests/test_ExcelReaderWriter.py
@@ -17,15 +17,16 @@
("Power_Demand_KInRows", f"{case_study_folder}Power_Demand_KInRows.xlsx", ExcelReader.get_Power_Demand_KInRows, ew.write_Power_Demand_KInRows),
("Power_Hindex", f"{case_study_folder}Power_Hindex.xlsx", ExcelReader.get_Power_Hindex, ew.write_Power_Hindex),
("Power_Inflows", f"{case_study_folder}Power_Inflows.xlsx", ExcelReader.get_Power_Inflows, ew.write_Power_Inflows),
+ ("Power_Inflows_KInRows", f"{case_study_folder}Power_Inflows_KInRows.xlsx", ExcelReader.get_Power_Inflows_KInRows, ew.write_Power_Inflows_KInRows),
("Power_Network", f"{case_study_folder}Power_Network.xlsx", ExcelReader.get_Power_Network, ew.write_Power_Network),
("Power_Storage", f"{case_study_folder}Power_Storage.xlsx", ExcelReader.get_Power_Storage, ew.write_Power_Storage),
("Power_ThermalGen", f"{case_study_folder}Power_ThermalGen.xlsx", ExcelReader.get_Power_ThermalGen, ew.write_Power_ThermalGen),
- ("Power_VRES", f"{case_study_folder}Power_VRES.xlsx", ExcelReader.get_Power_VRES, ew.write_VRES),
- ("Power_VRESProfiles", f"{case_study_folder}Power_VRESProfiles.xlsx", ExcelReader.get_Power_VRESProfiles, ew.write_VRESProfiles),
- ("Power_VRESProfiles_KInRows", f"{case_study_folder}Power_VRESProfiles_KInRows.xlsx", ExcelReader.get_Power_VRESProfiles_KInRows, ew.write_VRESProfiles_KInRows),
+ ("Power_VRES", f"{case_study_folder}Power_VRES.xlsx", ExcelReader.get_Power_VRES, ew.write_Power_VRES),
+ ("Power_VRESProfiles", f"{case_study_folder}Power_VRESProfiles.xlsx", ExcelReader.get_Power_VRESProfiles, ew.write_Power_VRESProfiles),
+ ("Power_VRESProfiles_KInRows", f"{case_study_folder}Power_VRESProfiles_KInRows.xlsx", ExcelReader.get_Power_VRESProfiles_KInRows, ew.write_Power_VRESProfiles_KInRows),
("Power_WeightsK", f"{case_study_folder}Power_WeightsK.xlsx", ExcelReader.get_Power_WeightsK, ew.write_Power_WeightsK),
("Power_WeightsRP", f"{case_study_folder}Power_WeightsRP.xlsx", ExcelReader.get_Power_WeightsRP, ew.write_Power_WeightsRP),
- ("Power_Wind_TechnicalDetails", f"{case_study_folder}Power_Wind_TechnicalDetails.xlsx", ExcelReader.get_Power_Wind_TechnicalDetails, ew.write_Power_Wind_TechnicalDetails)
+ ("Power_Wind_TechnicalDetails", f"{case_study_folder}Power_Wind_TechnicalDetails.xlsx", ExcelReader.get_Power_Wind_TechnicalDetails, ew.write_Power_Wind_TechnicalDetails),
]