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), ]