diff --git a/hub/imports/retrofit_factory.py b/hub/imports/retrofit_factory.py index ff3ec428..c4fa12cf 100644 --- a/hub/imports/retrofit_factory.py +++ b/hub/imports/retrofit_factory.py @@ -1,36 +1,90 @@ +# from hub.city_model_structure.building import Building +# from hub.city_model_structure.city import City +# import hub.helpers.constants as cte +# import copy + +# class RetrofitFactory: +# def __init__(self, retrofit_type, city): +# self._retrofit_type = retrofit_type +# self._city = city + +# def apply_retrofit(self, wall_u_value=None, roof_u_value=None, ground_u_value=None): +# for building in self._city.buildings: +# self._apply_retrofit_to_building(building, wall_u_value, roof_u_value, ground_u_value) + +# def _apply_retrofit_to_building(self, building: Building, wall_u_value, roof_u_value, ground_u_value): +# for thermal_zone in building.thermal_zones_from_internal_zones: +# for thermal_boundary in thermal_zone.thermal_boundaries: +# if wall_u_value is not None and thermal_boundary.type == cte.WALL and thermal_boundary.u_value > wall_u_value: +# self._change_thermal_resistance(thermal_boundary, wall_u_value) +# thermal_boundary.u_value = wall_u_value +# elif roof_u_value is not None and thermal_boundary.type == cte.ROOF: +# self._change_thermal_resistance(thermal_boundary, roof_u_value) +# thermal_boundary.u_value = roof_u_value +# elif ground_u_value is not None and thermal_boundary.type == cte.GROUND: +# self._change_thermal_resistance(thermal_boundary, ground_u_value) +# thermal_boundary.u_value = ground_u_value +# print("thermal_boundary.u_value",thermal_boundary.u_value, thermal_boundary.type) + + +# def _change_thermal_resistance(self, thermal_boundary, new_u_value): +# old_u_value = thermal_boundary.u_value +# new_u_value = new_u_value +# if new_u_value < old_u_value: +# delta_r = (1 / new_u_value) - (1 / old_u_value) +# for layer in thermal_boundary.layers: +# if "virtual_no_mass" in layer.material_name.lower(): +# new_thermal_resistance = layer.thermal_resistance + delta_r +# layer.thermal_resistance = new_thermal_resistance +# else: +# print("New U-value:", new_u_value, "is not less than old U-value:", old_u_value, "for thermal boundary type:", thermal_boundary.type) + +# def enrich(self, wall_u_value=None, roof_u_value=None, ground_u_value=None): +# # This method can be expanded to include different retrofit strategies +# if self._retrofit_type == 'basic': +# self.apply_retrofit(wall_u_value=0.247, roof_u_value=0.138, ground_u_value=0.156) +# elif self._retrofit_type == 'advanced': +# self.apply_retrofit(wall_u_value=0.11, roof_u_value=0.08, ground_u_value=0.1) +# #add option for custom wall only, roof only, ground only, the user choose the values +# elif self._retrofit_type == 'custom': +# # Use user-defined custom values (if provided) +# self.apply_retrofit(wall_u_value=wall_u_value, roof_u_value=roof_u_value, ground_u_value=ground_u_value) +# else: +# print("Invalid retrofit type:", self._retrofit_type) from hub.city_model_structure.building import Building from hub.city_model_structure.city import City import hub.helpers.constants as cte import copy class RetrofitFactory: - def __init__(self, retrofit_type, city): - self._retrofit_type = retrofit_type + def __init__(self, retrofit_types, city): + """ + :param retrofit_types: List of retrofit types to apply. Options are 'construction', 'windows', 'infiltration'. + :param city: The City object containing the buildings to retrofit. + """ + self._retrofit_types = retrofit_types self._city = city - def apply_retrofit(self, wall_u_value=None, roof_u_value=None, ground_u_value=None): + def apply_construction_retrofit(self, wall_u_value=None, roof_u_value=None, ground_u_value=None): for building in self._city.buildings: - self._apply_retrofit_to_building(building, wall_u_value, roof_u_value, ground_u_value) + self._apply_construction_retrofit_to_building(building, wall_u_value, roof_u_value, ground_u_value) - def _apply_retrofit_to_building(self, building: Building, wall_u_value, roof_u_value, ground_u_value): + def _apply_construction_retrofit_to_building(self, building: Building, wall_u_value, roof_u_value, ground_u_value): for thermal_zone in building.thermal_zones_from_internal_zones: for thermal_boundary in thermal_zone.thermal_boundaries: if wall_u_value is not None and thermal_boundary.type == cte.WALL and thermal_boundary.u_value > wall_u_value: self._change_thermal_resistance(thermal_boundary, wall_u_value) - print("u_value",thermal_boundary.u_value) thermal_boundary.u_value = wall_u_value - elif roof_u_value is not None and thermal_boundary.type == cte.ROOF: + elif roof_u_value is not None and thermal_boundary.type == cte.ROOF and thermal_boundary.u_value > roof_u_value: self._change_thermal_resistance(thermal_boundary, roof_u_value) thermal_boundary.u_value = roof_u_value - elif ground_u_value is not None and thermal_boundary.type == cte.GROUND: - self._change_thermal_resistance(thermal_boundary, ground_u_value) + elif ground_u_value is not None and thermal_boundary.type == cte.GROUND and thermal_boundary.u_value > ground_u_value: + self._change_thermal_resistance(thermal_boundary, ground_u_value) thermal_boundary.u_value = ground_u_value - print("thermal_boundary.u_value",thermal_boundary.u_value, thermal_boundary.type) + print("thermal_boundary.u_value", thermal_boundary.u_value, thermal_boundary.type) - def _change_thermal_resistance(self, thermal_boundary, new_u_value): - + def _change_thermal_resistance(self, thermal_boundary, new_u_value): old_u_value = thermal_boundary.u_value - new_u_value = new_u_value if new_u_value < old_u_value: delta_r = (1 / new_u_value) - (1 / old_u_value) for layer in thermal_boundary.layers: @@ -40,9 +94,54 @@ class RetrofitFactory: else: print("New U-value:", new_u_value, "is not less than old U-value:", old_u_value, "for thermal boundary type:", thermal_boundary.type) - def enrich(self): - # This method can be expanded to include different retrofit strategies - if self._retrofit_type == 'basic': - self.apply_retrofit(wall_u_value=0.247, roof_u_value=0.138, ground_u_value=0.156) - elif self._retrofit_type == 'advanced': - self.apply_retrofit(wall_u_value=0.11, roof_u_value=0.08, ground_u_value=0.1) + def reduce_infiltration_rate_by_percentage(self, percentage): + """ + Reduce the infiltration_rate_system_off of each thermal zone by the given percentage. + :param percentage: Percentage by which to reduce the infiltration rate (e.g., 20 for 20%) + """ + for building in self._city.buildings: + for thermal_zone in building.thermal_zones_from_internal_zones: + old_rate = thermal_zone.infiltration_rate_system_off + new_rate = old_rate * (1 - percentage / 100) + thermal_zone.infiltration_rate_system_off = new_rate + print(f"Reduced infiltration rate from {old_rate} to {new_rate} in thermal zone {thermal_zone}") + + def apply_window_retrofit(self, overall_u_value=None, g_value=None): + """ + Apply window retrofit by changing window overall U-value and g-value. + :param overall_u_value: New overall U-value for the windows (if not None). + :param g_value: New g-value (solar heat gain coefficient) for the windows (if not None). + """ + for building in self._city.buildings: + for thermal_zone in building.thermal_zones_from_internal_zones: + for thermal_boundary in thermal_zone.thermal_boundaries: + # Only apply to walls (and roofs if skylights are considered) + if thermal_boundary.type in [cte.WALL]: # Add cte.ROOF if skylights are present + if hasattr(thermal_boundary, 'thermal_openings') and thermal_boundary.thermal_openings: + for opening in thermal_boundary.thermal_openings: + if overall_u_value is not None and overall_u_value != 0: + old_u_value = opening.overall_u_value + opening.overall_u_value = overall_u_value + print(f"Changed window overall_u_value from {old_u_value} to {opening.overall_u_value} in opening {opening}") + if g_value is not None and g_value != 0: + old_g_value = opening.g_value + opening.g_value = g_value + print(f"Changed window g_value from {old_g_value} to {opening.g_value} in opening {opening}") + else: + print(f"No thermal openings in thermal boundary {thermal_boundary} of type {thermal_boundary.type}") + else: + # Skip thermal boundaries that are not walls or roofs + continue + + def enrich(self, wall_u_value=None, roof_u_value=None, ground_u_value=None, + infiltration_rate_percentage_reduction=None, + window_overall_u_value=None, window_g_value=None): + """ + Apply selected retrofits to the city. + """ + if 'construction' in self._retrofit_types: + self.apply_construction_retrofit(wall_u_value, roof_u_value, ground_u_value) + if 'infiltration' in self._retrofit_types and infiltration_rate_percentage_reduction is not None: + self.reduce_infiltration_rate_by_percentage(infiltration_rate_percentage_reduction) + if 'windows' in self._retrofit_types: + self.apply_window_retrofit(overall_u_value=window_overall_u_value, g_value=window_g_value) \ No newline at end of file diff --git a/hub/imports/retrofit_factory_virtual.py b/hub/imports/retrofit_factory_virtual.py index d344a2ef..61398117 100644 --- a/hub/imports/retrofit_factory_virtual.py +++ b/hub/imports/retrofit_factory_virtual.py @@ -1,63 +1,105 @@ +from hub.city_model_structure.building import Building from hub.city_model_structure.city import City -from hub.city_model_structure.building_demand.thermal_archetype import ThermalArchetype -from hub.city_model_structure.building_demand.construction import Construction -from hub.catalog_factories.construction_catalog_factory import ConstructionCatalogFactory +import hub.helpers.constants as cte -class RetrofitFactoryVirtual: - def __init__(self, retrofit_type, city): - self._retrofit_type = retrofit_type +class RetrofitFactory: + def __init__(self, retrofit_data, city): + """ + :param retrofit_data: Dictionary mapping building IDs to their retrofit data. + :param city: The City object containing the buildings to retrofit. + """ + self._retrofit_data = retrofit_data self._city = city - self._cerc_catalog = ConstructionCatalogFactory('cerc').catalog - - def apply_retrofit(self, **u_values): - for building in self._city.buildings: - for internal_zone in building.internal_zones: - self._apply_retrofit_to_thermal_archetype(internal_zone.thermal_archetype, u_values) - - def _apply_retrofit_to_thermal_archetype(self, thermal_archetype: ThermalArchetype, u_values): - for construction in thermal_archetype.constructions: - if construction.type in u_values and any(surface in construction.type.lower() for surface in ['wall', 'roof', 'ground']): - self._update_construction_u_value(construction, u_values[construction.type]) - - def _update_construction_u_value(self, construction: Construction, new_u_value): - virtual_no_mass_layer = next((layer for layer in construction.layers if "virtual_no_mass" in layer.material_name.lower()), None) - - if virtual_no_mass_layer: - old_r_value = 1 / construction.u_value - new_r_value = 1 / new_u_value - delta_r = new_r_value - old_r_value - - virtual_no_mass_layer.thermal_resistance += delta_r - print(f"Updated {construction.type} - New thermal resistance: {virtual_no_mass_layer.thermal_resistance}") - construction.u_value = new_u_value - - def modify_thermal_resistance(self, new_thermal_resistance): - import sys - - for building in self._city.buildings: - for zone in building.thermal_zones_from_internal_zones: - for boundary in zone.thermal_boundaries: - if any(surface in boundary.type.lower() for surface in ['wall', 'roof', 'ground']): - layers = getattr(boundary, 'get_layers', lambda: [])() - sys.stderr.write(f"Checking boundary type: {boundary.type}, found {len(layers)} layers\n") - for layer in layers: - if layer and hasattr(layer, 'material_name'): - sys.stderr.write(f"Checking layer: {layer.material_name}\n") - if "virtual_no_mass" in layer.material_name.lower(): - layer.thermal_resistance = new_thermal_resistance - sys.stderr.write(f"Set thermal resistance to {new_thermal_resistance} for {layer.material_name} in {boundary.type}\n") - sys.stderr.flush() def enrich(self): - if self._retrofit_type == 'basic': - self.apply_retrofit( - OutdoorsWall=0.85, - GroundWall=0.85, - OutdoorsRoofCeiling=0.95, - GroundRoofCeiling=0.95, - OutdoorsFloor=0.95, - GroundFloor=0.95 - ) - # After applying the retrofit, set a specific thermal resistance - self.modify_thermal_resistance(2.0) - # Add more retrofit types as needed \ No newline at end of file + for building in self._city.buildings: + building_id = str(building.name) # Convert ID to string to match JSON keys + if building_id in self._retrofit_data: + print(f"Applying retrofits to building ID {building_id}") + building_retrofit_data = self._retrofit_data[building_id] + retrofit_types = building_retrofit_data.get('retrofit_types', []) + self._apply_retrofits_to_building(building, retrofit_types, building_retrofit_data) + else: + print(f"No retrofit data for building ID {building_id}") + + def _apply_retrofits_to_building(self, building, retrofit_types, retrofit_params): + if 'construction' in retrofit_types: + self._apply_construction_retrofit_to_building(building, retrofit_params) + if 'infiltration' in retrofit_types: + self._reduce_infiltration_rate_by_percentage(building, retrofit_params) + if 'windows' in retrofit_types: + self._apply_window_retrofit_to_building(building, retrofit_params) + + def _apply_construction_retrofit_to_building(self, building: Building, retrofit_params): + wall_u_value = retrofit_params.get('wall_u_value') + roof_u_value = retrofit_params.get('roof_u_value') + ground_u_value = retrofit_params.get('ground_u_value') + + for thermal_zone in building.thermal_zones_from_internal_zones: + for thermal_boundary in thermal_zone.thermal_boundaries: + if wall_u_value is not None and thermal_boundary.type == cte.WALL and thermal_boundary.u_value > wall_u_value: + self._change_thermal_resistance(thermal_boundary, wall_u_value) + thermal_boundary.u_value = wall_u_value + print(f"Updated wall U-value to {wall_u_value} in building {building.name}") + elif roof_u_value is not None and thermal_boundary.type == cte.ROOF and thermal_boundary.u_value > roof_u_value: + self._change_thermal_resistance(thermal_boundary, roof_u_value) + thermal_boundary.u_value = roof_u_value + print(f"Updated roof U-value to {roof_u_value} in building {building.name}") + elif ground_u_value is not None and thermal_boundary.type == cte.GROUND and thermal_boundary.u_value > ground_u_value: + self._change_thermal_resistance(thermal_boundary, ground_u_value) + thermal_boundary.u_value = ground_u_value + print(f"Updated ground U-value to {ground_u_value} in building {building.name}") + + def _change_thermal_resistance(self, thermal_boundary, new_u_value): + old_u_value = thermal_boundary.u_value + if new_u_value < old_u_value: + delta_r = (1 / new_u_value) - (1 / old_u_value) + for layer in thermal_boundary.layers: + if "virtual_no_mass" in layer.material_name.lower(): + new_thermal_resistance = layer.thermal_resistance + delta_r + layer.thermal_resistance = new_thermal_resistance + print(f"Increased thermal resistance by {delta_r} in layer {layer.material_name}") + else: + print(f"New U-value {new_u_value} is not less than old U-value {old_u_value} for thermal boundary type {thermal_boundary.type}") + + def _reduce_infiltration_rate_by_percentage(self, building: Building, retrofit_params): + percentage = retrofit_params.get('infiltration_reduction') + if percentage is None: + return + for thermal_zone in building.thermal_zones_from_internal_zones: + #change infiltration rate in thermal_zone.parent_internal_zone.thermal_archetype. + thermal_archetype = thermal_zone.parent_internal_zone.thermal_archetype + old_rate = thermal_archetype.infiltration_rate_for_ventilation_system_off + new_rate = old_rate * (1 - percentage / 100) + thermal_archetype.infiltration_rate_for_ventilation_system_off = new_rate + print(f"Reduced infiltration rate from {old_rate} to {new_rate} in building {building.name}") + + + + def _apply_window_retrofit_to_building(self, building: Building, retrofit_params): + overall_u_value = retrofit_params.get('window_u_value') + g_value = retrofit_params.get('window_g_value') + + for thermal_zone in building.thermal_zones_from_internal_zones: + for thermal_boundary in thermal_zone.thermal_boundaries: + if thermal_boundary.type == cte.WALL: + #change window u_value and g_value in _construction_archetype + construction_archetype = thermal_boundary._construction_archetype + construction_archetype.window_overall_u_value = overall_u_value + construction_archetype.window_g_value = g_value + + if hasattr(thermal_boundary, 'thermal_openings') and thermal_boundary.thermal_openings: + for opening in thermal_boundary.thermal_openings: + if overall_u_value is not None and overall_u_value != 0: + old_u_value = opening.overall_u_value + opening.overall_u_value = overall_u_value + print(f"Changed window U-value from {old_u_value} to {opening.overall_u_value} in building {building.name}") + if g_value is not None and g_value != 0: + old_g_value = opening.g_value + opening.g_value = g_value + print(f"Changed window g-value from {old_g_value} to {opening.g_value} in building {building.name}") + else: + print(f"No thermal openings in thermal boundary {thermal_boundary} of type {thermal_boundary.type} in building {building.name}") + else: + # Skip thermal boundaries that are not walls + continue \ No newline at end of file diff --git a/input_files/retrofit_scenarios.json b/input_files/retrofit_scenarios.json new file mode 100644 index 00000000..65c3fb6e --- /dev/null +++ b/input_files/retrofit_scenarios.json @@ -0,0 +1,20 @@ +{ + "175785": { + "retrofit_types": ["construction", "windows", "infiltration"], + "wall_u_value": 0.2, + "roof_u_value": 0.15, + "ground_u_value": 0.18, + "infiltration_reduction": 30, + "window_u_value": 1.1, + "window_g_value": 0.6 + }, + "175786": { + "retrofit_types": ["windows"], + "window_u_value": 1.3, + "window_g_value": 0.5 + }, + "175787": { + "retrofit_types": ["infiltration"], + "infiltration_reduction": 25 + } + } \ No newline at end of file diff --git a/main.py b/main.py index 240fc748..d521c926 100644 --- a/main.py +++ b/main.py @@ -6,13 +6,17 @@ from hub.helpers.dictionaries import Dictionaries from hub.imports.construction_factory import ConstructionFactory from hub.imports.usage_factory import UsageFactory from hub.imports.weather_factory import WeatherFactory -from hub.imports.retrofit_factory import RetrofitFactory - +from hub.imports.retrofit_factory_virtual import RetrofitFactory +import json # Specify the GeoJSON file path input_files_path = (Path(__file__).parent / 'input_files') input_files_path.mkdir(parents=True, exist_ok=True) geojson_file = process_geojson(x=-73.5681295982132, y=45.49218262677643, diff=0.0001) geojson_file_path = input_files_path / 'input_one_buildings.geojson' + +retrofit_json_file_path = input_files_path / 'retrofit_scenarios.json' +with open(retrofit_json_file_path, 'r') as f: + retrofit_data = json.load(f) output_path = (Path(__file__).parent / 'out_files').resolve() output_path.mkdir(parents=True, exist_ok=True) # Create city object from GeoJSON file @@ -23,9 +27,10 @@ city = GeometryFactory('geojson', function_field='function', function_to_hub=Dictionaries().montreal_function_to_hub_function).city # Enrich city data -ConstructionFactory('nrcan', city).enrich() -UsageFactory('nrcan', city).enrich() -RetrofitFactory('basic', city).enrich() +ConstructionFactory('cerc', city).enrich() +UsageFactory('cerc', city).enrich() +# Apply retrofits based on JSON data using building IDs +RetrofitFactory(retrofit_data, city).enrich() WeatherFactory('epw', city).enrich() energy_plus_workflow(city)