diff --git a/hub/exports/building_energy/idf.py b/hub/exports/building_energy/idf.py index 9693fcbb..ab9cd0af 100644 --- a/hub/exports/building_energy/idf.py +++ b/hub/exports/building_energy/idf.py @@ -11,7 +11,6 @@ from pathlib import Path from geomeppy import IDF import hub.helpers.constants as cte from hub.city_model_structure.attributes.schedule import Schedule -from hub.city_model_structure.building_demand.thermal_zone import ThermalZone class Idf: @@ -21,7 +20,6 @@ class Idf: _BUILDING = 'BUILDING' _ZONE = 'ZONE' _LIGHTS = 'LIGHTS' - _APPLIANCES = 'OTHEREQUIPMENT' _PEOPLE = 'PEOPLE' _THERMOSTAT = 'HVACTEMPLATE:THERMOSTAT' _IDEAL_LOAD_AIR_SYSTEM = 'HVACTEMPLATE:ZONE:IDEALLOADSAIRSYSTEM' @@ -276,7 +274,7 @@ class Idf: def _add_window_construction_and_material(self, thermal_opening): for window_material in self._idf.idfobjects[self._WINDOW_MATERIAL_SIMPLE]: if window_material['UFactor'] == thermal_opening.overall_u_value and \ - window_material['Solar_Heat_Gain_Coefficient'] == thermal_opening.g_value: + window_material['Solar_Heat_Gain_Coefficient'] == thermal_opening.g_value: return order = str(len(self._idf.idfobjects[self._WINDOW_MATERIAL_SIMPLE]) + 1) @@ -322,7 +320,6 @@ class Idf: def _add_occupancy(self, thermal_zone, zone_name): number_of_people = thermal_zone.occupancy.occupancy_density * thermal_zone.total_floor_area - print(thermal_zone.occupancy.occupancy_density) fraction_radiant = 0 total_sensible = thermal_zone.occupancy.sensible_radiative_internal_gain + \ thermal_zone.occupancy.sensible_convective_internal_gain @@ -339,54 +336,6 @@ class Idf: Activity_Level_Schedule_Name=f'Activity Level schedules {thermal_zone.usage_name}' ) - def _add_lighting(self, thermal_zone: ThermalZone, zone_name: str): - fraction_radiant = thermal_zone.lighting.radiative_fraction - # todo: fraction visible should come from catalog - fraction_visible = 0.3 - method = 'Watts/Area' - factor_size = thermal_zone.total_floor_area / thermal_zone.footprint_area - watts_per_zone_floor_area = thermal_zone.lighting.density*factor_size - # todo: fraction replaceable should come from catalog - fraction_replaceable = 1 - subcategory = f'ELECTRIC EQUIPMENT#{zone_name}#GeneralLights' - - self._idf.newidfobject(self._LIGHTS, - Name=f'{zone_name}_lights', - Zone_or_ZoneList_Name=zone_name, - Schedule_Name=f'Lighting schedules {thermal_zone.usage_name}', - Design_Level_Calculation_Method=method, - Watts_per_Zone_Floor_Area=watts_per_zone_floor_area, - Fraction_Radiant=fraction_radiant, - Fraction_Visible=fraction_visible, - Fraction_Replaceable=fraction_replaceable, - EndUse_Subcategory=subcategory - ) - - def _add_appliances(self, thermal_zone, zone_name): - fuel_type = 'Electricity' - fraction_radiant = thermal_zone.appliances.radiative_fraction - fraction_convective = thermal_zone.appliances.convective_fraction - fraction_latent = 0 - method = 'Watts/Area' - factor_size = thermal_zone.total_floor_area / thermal_zone.footprint_area - watts_per_zone_floor_area = thermal_zone.appliances.density*factor_size - print(thermal_zone.appliances.density) - print(watts_per_zone_floor_area) - subcategory = f'ELECTRIC EQUIPMENT#{zone_name}#InteriorEquipment' - # _object = self._idf.newidfobject(self._APPLIANCES) - # print(vars(_object)) - self._idf.newidfobject(self._APPLIANCES, - Fuel_Type=fuel_type, - Name=f'{zone_name}_appliances', - Zone_or_ZoneList_Name=zone_name, - Schedule_Name=f'Appliance schedules {thermal_zone.usage_name}', - Design_Level_Calculation_Method=method, - Power_per_Zone_Floor_Area=watts_per_zone_floor_area, - Fraction_Latent=fraction_latent, - Fraction_Radiant=fraction_radiant, - EndUse_Subcategory=subcategory - ) - def _add_infiltration(self, thermal_zone, zone_name): for zone in self._idf.idfobjects["ZONE"]: if zone.Name == f'{zone_name}_infiltration': @@ -439,23 +388,16 @@ class Idf: usage = thermal_zone.usage_name if building.name in self._target_buildings or building.name in self._adjacent_buildings: self._add_infiltration_schedules(thermal_zone) - self._add_schedules(usage, 'Occupancy', thermal_zone.occupancy.occupancy_schedules) self._add_schedules(usage, 'HVAC AVAIL', thermal_zone.thermal_control.hvac_availability_schedules) self._add_schedules(usage, 'Heating thermostat', thermal_zone.thermal_control.heating_set_point_schedules) self._add_schedules(usage, 'Cooling thermostat', thermal_zone.thermal_control.cooling_set_point_schedules) - self._add_schedules(usage, 'Lighting', thermal_zone.lighting.schedules) - self._add_schedules(usage, 'Appliances', thermal_zone.appliances.schedules) - self._add_people_activity_level_schedules(thermal_zone) self._add_zone(thermal_zone, building.name) self._add_heating_system(thermal_zone, building.name) self._add_infiltration(thermal_zone, building.name) self._add_occupancy(thermal_zone, building.name) - self._add_lighting(thermal_zone, building.name) - self._add_appliances(thermal_zone, building.name) - if self._export_type == "Surfaces": if building.name in self._target_buildings or building.name in self._adjacent_buildings: self._add_surfaces(building, building.name) @@ -464,19 +406,24 @@ class Idf: else: self._add_block(building) # todo: this should change to specific variables per zone to process only the ones in the buildings_to_calculate - for _ in self._target_buildings: + for building in self._target_buildings: continue self._idf.newidfobject( - "OUTPUT:VARIABLE", - Variable_Name="Zone Ideal Loads Supply Air Total Heating Energy", - Reporting_Frequency="Hourly", - ) + "OUTPUT:VARIABLE", + Variable_Name="Zone Ideal Loads Supply Air Total Heating Energy", + Reporting_Frequency="Hourly", + ) self._idf.newidfobject( - "OUTPUT:VARIABLE", - Variable_Name="Zone Ideal Loads Supply Air Total Cooling Energy", - Reporting_Frequency="Hourly", + "OUTPUT:VARIABLE", + Variable_Name="Zone Ideal Loads Supply Air Total Cooling Energy", + Reporting_Frequency="Hourly", + ) + + self._idf.newidfobject( + "OUTPUTCONTROL:TABLE:STYLE", + Variable_Name="CommaAndHTML, JtoKWH", ) self._idf.match() @@ -588,7 +535,7 @@ class Idf: for material in self._idf.idfobjects[self._WINDOW_MATERIAL_SIMPLE]: if material['Name'] == glazing: if material['UFactor'] == opening.overall_u_value and \ - material['Solar_Heat_Gain_Coefficient'] == opening.g_value: + material['Solar_Heat_Gain_Coefficient'] == opening.g_value: return True return False diff --git a/hub/exports/building_energy/insel/insel_monthly_energy_balance.py b/hub/exports/building_energy/insel/insel_monthly_energy_balance.py index 22565ba9..3ccb2a63 100644 --- a/hub/exports/building_energy/insel/insel_monthly_energy_balance.py +++ b/hub/exports/building_energy/insel/insel_monthly_energy_balance.py @@ -33,6 +33,7 @@ class InselMonthlyEnergyBalance(Insel): self._weather_format = weather_format self._contents = [] self._insel_files_paths = [] + self._sanity_check() for building in city.buildings: self._insel_files_paths.append(building.name + '.insel') file_name_out = building.name + '.out' @@ -46,7 +47,7 @@ class InselMonthlyEnergyBalance(Insel): f'Monthly Energy Balance cannot be processed\n') break self._contents.append( - self.generate_meb_template(building, output_path, self._radiation_calculation_method,self._weather_format) + self._generate_meb_template(building, output_path, self._radiation_calculation_method,self._weather_format) ) self._export() @@ -57,8 +58,30 @@ class InselMonthlyEnergyBalance(Insel): insel_file.write(content) return + def _sanity_check(self): + levels_of_detail = self._city.level_of_detail + if levels_of_detail.geometry is None: + raise Exception(f'Level of detail of geometry not assigned') + if levels_of_detail.geometry < 1: + raise Exception(f'Level of detail of geometry = {levels_of_detail.geometry}. Required minimum level 1') + if levels_of_detail.construction is None: + raise Exception(f'Level of detail of construction not assigned') + if levels_of_detail.construction < 1: + raise Exception(f'Level of detail of construction = {levels_of_detail.construction}. Required minimum level 1') + if levels_of_detail.usage is None: + raise Exception(f'Level of detail of usage not assigned') + if levels_of_detail.usage < 1: + raise Exception(f'Level of detail of usage = {levels_of_detail.usage}. Required minimum level 1') + for building in self._city.buildings: + if cte.MONTH not in building.external_temperature: + raise Exception(f'Building {building.name} does not have external temperature assigned') + for surface in building.surfaces: + if surface.type != cte.GROUND: + if cte.MONTH not in surface.global_irradiance: + raise Exception(f'Building {building.name} does not have global irradiance on surfaces assigned') + @staticmethod - def generate_meb_template(building, insel_outputs_path, radiation_calculation_method, weather_format): + def _generate_meb_template(building, insel_outputs_path, radiation_calculation_method, weather_format): file = "" i_block = 1 parameters = ["1", "12", "1"] @@ -100,16 +123,38 @@ class InselMonthlyEnergyBalance(Insel): for ig in usage.internal_gains: total_internal_gain += ig.average_internal_gain * (ig.convective_fraction + ig.radiative_fraction) parameters.append(f'{total_internal_gain} % BP(12) #2 Internal gains of zone {i + 1}') - parameters.append(f'{usage.thermal_control.mean_heating_set_point} % BP(13) #3 Heating setpoint temperature ' + parameters.append(f'{usage.thermal_control.mean_heating_set_point+1} % BP(13) #3 Heating setpoint temperature ' f'zone {i + 1} (degree Celsius)') - parameters.append(f'{usage.thermal_control.heating_set_back} % BP(14) #4 Heating setback temperature ' + parameters.append(f'{usage.thermal_control.heating_set_back+1} % BP(14) #4 Heating setback temperature ' f'zone {i + 1} (degree Celsius)') - parameters.append(f'{usage.thermal_control.mean_cooling_set_point + 3} % BP(15) #5 Cooling setpoint temperature ' + parameters.append(f'{usage.thermal_control.mean_cooling_set_point+4} % BP(15) #5 Cooling setpoint temperature ' f'zone {i + 1} (degree Celsius)') parameters.append(f'{usage.hours_day} % BP(16) #6 Usage hours per day zone {i + 1}') parameters.append(f'{usage.days_year} % BP(17) #7 Usage days per year zone {i + 1}') - ventilation_infiltration = usage.mechanical_air_change + internal_zone.thermal_zones[0].infiltration_rate_system_off + ventilation = 0 + infiltration = 0 + for schedule in usage.thermal_control.hvac_availability_schedules: + ventilation_day = 0 + infiltration_day = 0 + for value in schedule.values: + if value == 0: + infiltration_day += internal_zone.thermal_zones[0].infiltration_rate_system_off / 24 + ventilation_day += 0 + else: + ventilation_value = usage.mechanical_air_change * value + infiltration_value = internal_zone.thermal_zones[0].infiltration_rate_system_off * value + if ventilation_value >= infiltration_value: + ventilation_day += ventilation_value / 24 + infiltration_day += 0 + else: + ventilation_day += 0 + infiltration_day += infiltration_value / 24 + for day_type in schedule.day_types: + infiltration += infiltration_day * cte.DAYS_A_YEAR[day_type] / 365 + ventilation += ventilation_day * cte.DAYS_A_YEAR[day_type] / 365 + + ventilation_infiltration = ventilation + infiltration parameters.append(f'{ventilation_infiltration} % BP(18) #8 Minimum air change rate zone {i + 1} (ACH)') parameters.append(f'{len(thermal_zone.thermal_boundaries)} % Number of surfaces = BP(11+8z) \n' @@ -127,7 +172,7 @@ class InselMonthlyEnergyBalance(Insel): for thermal_boundary in thermal_zone.thermal_boundaries: type_code = _CONSTRUCTION_CODE[thermal_boundary.type] wall_area = thermal_boundary.opaque_area * (1 + thermal_boundary.window_ratio) - if thermal_boundary.type == cte.WALL and thermal_boundary.parent_surface.percentage_shared is not None: + if thermal_boundary.type == cte.WALL: wall_area = wall_area * (1 - thermal_boundary.parent_surface.percentage_shared) window_area = wall_area * thermal_boundary.window_ratio