From 9995f7a321cbbfa4081b789d5e15153b37908ec7 Mon Sep 17 00:00:00 2001 From: "fabian.wuellhorst" Date: Fri, 4 Oct 2024 08:16:09 +0200 Subject: [PATCH 1/5] add functions --- teaser/logic/retrofit.py | 80 ++++++++++++++++++++++++++++++++++++++++ teaser/project.py | 27 ++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 teaser/logic/retrofit.py diff --git a/teaser/logic/retrofit.py b/teaser/logic/retrofit.py new file mode 100644 index 000000000..295efb651 --- /dev/null +++ b/teaser/logic/retrofit.py @@ -0,0 +1,80 @@ +import itertools + +from teaser.logic.buildingobjects.building import Building +import teaser.data.utilities as datahandling + + +def generate_buildings_for_all_element_combinations( + project_add_building_function: callable, + add_building_function_kwargs: dict, + elements: list = None, + retrofit_choices: list = None +): + construction_data = datahandling.ConstructionData( + add_building_function_kwargs["construction_data"] + ) + if not construction_data.is_tabula_de() and not construction_data.is_tabula_dk(): + raise ValueError( + "Given option to retrofit all combinations " + "is only implemented for TABULA archetypes." + ) + + # Define mapping for later naming + retrofit_dict = {'standard': 0, 'retrofit': 1, 'adv_retrofit': 2} + possible_elements = ['outer_walls', 'windows', 'rooftops', "ground_floors"] + + if elements is None: + elements = ['outer_walls', 'windows', 'rooftops', "ground_floors"] + if retrofit_choices is None: + retrofit_choices = list(retrofit_dict.keys()) + unsupported_elements = set(elements).difference(possible_elements) + if unsupported_elements: + raise ValueError( + "The following elements are not supported: " + ", ".join(unsupported_elements) + ) + unsupported_choices = set(retrofit_choices).difference(retrofit_dict.keys()) + if unsupported_choices: + raise ValueError( + "The following retrofit_choices are not supported: " + ", ".join(unsupported_choices) + ) + + # Generate all possible combinations of retrofit statuses for each element + combinations = itertools.product(retrofit_choices, repeat=len(elements)) + + # Create a list to store the resulting dictionaries + combinations = [ + { + element: status + for element, status in zip(elements, combo) + } + for combo in combinations + ] + + generated_building_names = [] + for element_retrofit_stats in combinations: + # Code for retrofit status OiWiRiGi with i from 0 to 2 + retrofit_code = ''.join( + f"{element[0]}{retrofit_dict[retrofit_option]}" + for element, retrofit_option in element_retrofit_stats.items() + ) + modified_function_kwargs = add_building_function_kwargs.copy() + modified_function_kwargs["name"] += f"_{retrofit_code}" + building = project_add_building_function(**modified_function_kwargs) + component_based_retrofit(building=building, element_retrofit_stats=element_retrofit_stats) + generated_building_names.append(modified_function_kwargs["name"]) + return generated_building_names + + +def component_based_retrofit(building: Building, element_retrofit_stats: dict): + for zone in building.thermal_zones: + for element, retrofit_option in element_retrofit_stats.items(): + if retrofit_option != "standard": + continue + for wall_count in getattr(zone, element): + wall_count.load_type_element( + year=building.year_of_construction, + construction=wall_count.construction_type.replace( + "standard", + retrofit_option + ) + ) diff --git a/teaser/project.py b/teaser/project.py index 295c79fa2..b1d3a795b 100644 --- a/teaser/project.py +++ b/teaser/project.py @@ -12,6 +12,7 @@ from teaser.data.dataclass import DataClass from teaser.logic.archetypebuildings.tabula.de.singlefamilyhouse import SingleFamilyHouse from teaser.logic.simulation.modelicainfo import ModelicaInfo +from teaser.logic.retrofit import generate_buildings_for_all_element_combinations class Project(object): @@ -598,6 +599,32 @@ def add_residential( ) return type_bldg + def add_non_residential_retrofit_combinations( + self, + elements: list, + retrofit_choices: list, + add_residential_kwargs: dict + ): + generate_buildings_for_all_element_combinations( + project_add_building_function=self.add_non_residential, + add_building_function_kwargs=add_residential_kwargs, + elements=elements, + retrofit_choices=retrofit_choices, + ) + + def add_residential_retrofit_combinations( + self, + elements: list, + retrofit_choices: list, + add_residential_kwargs: dict + ): + generate_buildings_for_all_element_combinations( + project_add_building_function=self.add_residential, + add_building_function_kwargs=add_residential_kwargs, + elements=elements, + retrofit_choices=retrofit_choices, + ) + def save_project(self, file_name=None, path=None): """Saves the project to a JSON file From d88bacf82ec98003b5ad213a01c6c4ebf98434f2 Mon Sep 17 00:00:00 2001 From: "fabian.wuellhorst" Date: Fri, 4 Oct 2024 08:22:24 +0200 Subject: [PATCH 2/5] add docstrings --- teaser/logic/retrofit.py | 54 ++++++++++++++++++++++++++++++++++++++ teaser/project.py | 56 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+) diff --git a/teaser/logic/retrofit.py b/teaser/logic/retrofit.py index 295efb651..9b58788f4 100644 --- a/teaser/logic/retrofit.py +++ b/teaser/logic/retrofit.py @@ -10,6 +10,40 @@ def generate_buildings_for_all_element_combinations( elements: list = None, retrofit_choices: list = None ): + """ + Generate buildings for all possible combinations of + retrofit statuses for specified building elements. + + This function creates multiple variations of a building based + on different retrofit options for specified building elements. + It's designed to work with TABULA archetypes. + + Parameters + ---------- + project_add_building_function : callable + Function to add a building to the project. + add_building_function_kwargs : dict + Keyword arguments for the add_building function. + elements : list, optional + List of building elements to consider for retrofit. + Defaults to ['outer_walls', 'windows', 'rooftops', 'ground_floors']. + retrofit_choices : list, optional + List of retrofit options to consider. + Defaults to ['standard', 'retrofit', 'adv_retrofit']. + + Returns + ------- + list: A list of names of the generated buildings. + + Raises + ------ + ValueError: If unsupported elements or retrofit choices are provided, or if the + construction data is not from TABULA DE or DK. + + Note + ---- + This function only works with TABULA DE or DK construction data. + """ construction_data = datahandling.ConstructionData( add_building_function_kwargs["construction_data"] ) @@ -66,6 +100,26 @@ def generate_buildings_for_all_element_combinations( def component_based_retrofit(building: Building, element_retrofit_stats: dict): + """ + Apply component-based retrofits to a building based on specified retrofit statuses. + + This function applies retrofits to specific building elements (e.g., walls, windows) + based on the provided retrofit statuses. + + Parameters + ---------- + building : Building + The building object to be retrofitted. + element_retrofit_stats : dict + A dictionary specifying the retrofit status for each + building element. Keys are element names (e.g., 'outer_walls'), + and values are retrofit options (e.g., 'standard', 'retrofit'). + + Note + ---- + This function modifies the building object in-place. + Only elements with 'standard' retrofit option are processed. + """ for zone in building.thermal_zones: for element, retrofit_option in element_retrofit_stats.items(): if retrofit_option != "standard": diff --git a/teaser/project.py b/teaser/project.py index b1d3a795b..782d78e48 100644 --- a/teaser/project.py +++ b/teaser/project.py @@ -605,6 +605,34 @@ def add_non_residential_retrofit_combinations( retrofit_choices: list, add_residential_kwargs: dict ): + """ + Generate non-residential buildings for all possible combinations of + retrofit statuses for specified building elements. + + This function creates multiple variations of a non-residential building based + on different retrofit options for specified building elements. + It's designed to work with TABULA archetypes. + + Parameters + ---------- + add_residential_kwargs : dict + Keyword arguments for the function add_residential. + elements : list, optional + List of building elements to consider for retrofit. + Defaults to ['outer_walls', 'windows', 'rooftops', 'ground_floors']. + retrofit_choices : list, optional + List of retrofit options to consider. + Defaults to ['standard', 'retrofit', 'adv_retrofit']. + + Raises + ------ + ValueError: If unsupported elements or retrofit choices are provided, or if the + construction data is not from TABULA DE or DK. + + Note + ---- + This function only works with TABULA DE or DK construction data. + """ generate_buildings_for_all_element_combinations( project_add_building_function=self.add_non_residential, add_building_function_kwargs=add_residential_kwargs, @@ -618,6 +646,34 @@ def add_residential_retrofit_combinations( retrofit_choices: list, add_residential_kwargs: dict ): + """ + Generate residential buildings for all possible combinations of + retrofit statuses for specified building elements. + + This function creates multiple variations of a residential building based + on different retrofit options for specified building elements. + It's designed to work with TABULA archetypes. + + Parameters + ---------- + add_residential_kwargs : dict + Keyword arguments for the function add_residential. + elements : list, optional + List of building elements to consider for retrofit. + Defaults to ['outer_walls', 'windows', 'rooftops', 'ground_floors']. + retrofit_choices : list, optional + List of retrofit options to consider. + Defaults to ['standard', 'retrofit', 'adv_retrofit']. + + Raises + ------ + ValueError: If unsupported elements or retrofit choices are provided, or if the + construction data is not from TABULA DE or DK. + + Note + ---- + This function only works with TABULA DE or DK construction data. + """ generate_buildings_for_all_element_combinations( project_add_building_function=self.add_residential, add_building_function_kwargs=add_residential_kwargs, From 52333055248e4089f9a113ae71713349061a001c Mon Sep 17 00:00:00 2001 From: "fabian.wuellhorst" Date: Fri, 4 Oct 2024 08:28:21 +0200 Subject: [PATCH 3/5] Add return of generated building names --- teaser/project.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/teaser/project.py b/teaser/project.py index 782d78e48..0d01cb139 100644 --- a/teaser/project.py +++ b/teaser/project.py @@ -624,6 +624,10 @@ def add_non_residential_retrofit_combinations( List of retrofit options to consider. Defaults to ['standard', 'retrofit', 'adv_retrofit']. + Returns + ------- + list: A list of names of the generated buildings. + Raises ------ ValueError: If unsupported elements or retrofit choices are provided, or if the @@ -633,7 +637,7 @@ def add_non_residential_retrofit_combinations( ---- This function only works with TABULA DE or DK construction data. """ - generate_buildings_for_all_element_combinations( + return generate_buildings_for_all_element_combinations( project_add_building_function=self.add_non_residential, add_building_function_kwargs=add_residential_kwargs, elements=elements, @@ -665,6 +669,10 @@ def add_residential_retrofit_combinations( List of retrofit options to consider. Defaults to ['standard', 'retrofit', 'adv_retrofit']. + Returns + ------- + list: A list of names of the generated buildings. + Raises ------ ValueError: If unsupported elements or retrofit choices are provided, or if the @@ -674,7 +682,7 @@ def add_residential_retrofit_combinations( ---- This function only works with TABULA DE or DK construction data. """ - generate_buildings_for_all_element_combinations( + return generate_buildings_for_all_element_combinations( project_add_building_function=self.add_residential, add_building_function_kwargs=add_residential_kwargs, elements=elements, From 008c07ab1c4b8e33b065b59237f6a753a40951c5 Mon Sep 17 00:00:00 2001 From: "fabian.wuellhorst" Date: Fri, 4 Oct 2024 09:50:25 +0200 Subject: [PATCH 4/5] Fix bad continue --- teaser/logic/retrofit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/teaser/logic/retrofit.py b/teaser/logic/retrofit.py index 9b58788f4..7af6074df 100644 --- a/teaser/logic/retrofit.py +++ b/teaser/logic/retrofit.py @@ -122,7 +122,7 @@ def component_based_retrofit(building: Building, element_retrofit_stats: dict): """ for zone in building.thermal_zones: for element, retrofit_option in element_retrofit_stats.items(): - if retrofit_option != "standard": + if retrofit_option == "standard": continue for wall_count in getattr(zone, element): wall_count.load_type_element( From 69778ec3e35ba95ba568bc63810e8ced0f1acdab Mon Sep 17 00:00:00 2001 From: "fabian.wuellhorst" Date: Mon, 11 Nov 2024 12:08:58 +0100 Subject: [PATCH 5/5] fix type->data, add test, remove non-residential retrofit as TABULA only supports residential buildings --- teaser/__init__.py | 2 +- teaser/logic/retrofit.py | 2 +- teaser/project.py | 51 ++-------------------------------- tests/test_partial_retrofit.py | 39 ++++++++++++++++++++++++++ 4 files changed, 44 insertions(+), 50 deletions(-) create mode 100644 tests/test_partial_retrofit.py diff --git a/teaser/__init__.py b/teaser/__init__.py index 1830d50fa..75c88998a 100644 --- a/teaser/__init__.py +++ b/teaser/__init__.py @@ -7,7 +7,7 @@ import sys import os -__version__ = "1.0.0" +__version__ = "1.0.1" new_path = os.path.join(os.path.expanduser('~'), ("TEASEROutput")) diff --git a/teaser/logic/retrofit.py b/teaser/logic/retrofit.py index 7af6074df..9d0f99e36 100644 --- a/teaser/logic/retrofit.py +++ b/teaser/logic/retrofit.py @@ -127,7 +127,7 @@ def component_based_retrofit(building: Building, element_retrofit_stats: dict): for wall_count in getattr(zone, element): wall_count.load_type_element( year=building.year_of_construction, - construction=wall_count.construction_type.replace( + construction=wall_count.construction_data.replace( "standard", retrofit_option ) diff --git a/teaser/project.py b/teaser/project.py index 0d01cb139..2d012ee60 100644 --- a/teaser/project.py +++ b/teaser/project.py @@ -599,56 +599,11 @@ def add_residential( ) return type_bldg - def add_non_residential_retrofit_combinations( - self, - elements: list, - retrofit_choices: list, - add_residential_kwargs: dict - ): - """ - Generate non-residential buildings for all possible combinations of - retrofit statuses for specified building elements. - - This function creates multiple variations of a non-residential building based - on different retrofit options for specified building elements. - It's designed to work with TABULA archetypes. - - Parameters - ---------- - add_residential_kwargs : dict - Keyword arguments for the function add_residential. - elements : list, optional - List of building elements to consider for retrofit. - Defaults to ['outer_walls', 'windows', 'rooftops', 'ground_floors']. - retrofit_choices : list, optional - List of retrofit options to consider. - Defaults to ['standard', 'retrofit', 'adv_retrofit']. - - Returns - ------- - list: A list of names of the generated buildings. - - Raises - ------ - ValueError: If unsupported elements or retrofit choices are provided, or if the - construction data is not from TABULA DE or DK. - - Note - ---- - This function only works with TABULA DE or DK construction data. - """ - return generate_buildings_for_all_element_combinations( - project_add_building_function=self.add_non_residential, - add_building_function_kwargs=add_residential_kwargs, - elements=elements, - retrofit_choices=retrofit_choices, - ) - def add_residential_retrofit_combinations( self, - elements: list, - retrofit_choices: list, - add_residential_kwargs: dict + elements: list = None, + retrofit_choices: list = None, + **add_residential_kwargs: dict ): """ Generate residential buildings for all possible combinations of diff --git a/tests/test_partial_retrofit.py b/tests/test_partial_retrofit.py new file mode 100644 index 000000000..88a94c396 --- /dev/null +++ b/tests/test_partial_retrofit.py @@ -0,0 +1,39 @@ +import itertools + +from teaser.project import Project + + +class Test_tabula_sfh(object): + + def test_tabula_de_sfh_area_1859(self): + """ + Test for area estimation of tabula sfh + """ + for elements, retrofit_choices in itertools.product( + [ + None, + ["outer_walls", "windows", "rooftops", "ground_floors"], + ["outer_walls", "windows"] + ], + [ + None, + ["standard", "retrofit", "adv_retrofit"], + ["standard", "retrofit"] + ] + ): + prj = Project(False) + prj.add_residential_retrofit_combinations( + elements=elements, + retrofit_choices=retrofit_choices, + construction_data='tabula_de_standard', + geometry_data='tabula_de_single_family_house', + name="ResidentialBuilding", + year_of_construction=1858, + number_of_floors=2, + height_of_floors=3.2, + net_leased_area=219 + ) + len_elements = len(elements) if elements is not None else 4 + len_choices = len(retrofit_choices) if retrofit_choices is not None else 3 + assert len(prj.buildings) == len_choices ** len_elements, \ + "Number of generated buildings does not match expectation"