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 new file mode 100644 index 000000000..9d0f99e36 --- /dev/null +++ b/teaser/logic/retrofit.py @@ -0,0 +1,134 @@ +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 +): + """ + 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"] + ) + 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): + """ + 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": + continue + for wall_count in getattr(zone, element): + wall_count.load_type_element( + year=building.year_of_construction, + construction=wall_count.construction_data.replace( + "standard", + retrofit_option + ) + ) diff --git a/teaser/project.py b/teaser/project.py index 295c79fa2..2d012ee60 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,51 @@ def add_residential( ) return type_bldg + def add_residential_retrofit_combinations( + self, + elements: list = None, + retrofit_choices: list = None, + **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']. + + 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_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 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"