diff --git a/hub/exports/building_energy/__pycache__/idf.cpython-39.pyc b/hub/exports/building_energy/__pycache__/idf.cpython-39.pyc index 952bb6b5..bea93db2 100644 Binary files a/hub/exports/building_energy/__pycache__/idf.cpython-39.pyc and b/hub/exports/building_energy/__pycache__/idf.cpython-39.pyc differ diff --git a/hub/exports/building_energy/idf.py b/hub/exports/building_energy/idf.py index ee0471ff..cf1646e3 100644 --- a/hub/exports/building_energy/idf.py +++ b/hub/exports/building_energy/idf.py @@ -54,14 +54,6 @@ class Idf: _SIZING_PERIODS = 'SIZINGPERIOD:DESIGNDAY' _LOCATION = 'SITE:LOCATION' _SIMPLE = 'Simple' - _EQUIPMENT_CONNECTIONS = 'ZONEHVAC:EQUIPMENTCONNECTIONS' - _NODE_LIST = 'NODELIST' - _BASEBOARD = 'ZONEHVAC:BASEBOARD:CONVECTIVE:ELECTRIC' - _AIR_TERMINAL_NO_REHEAT = 'AIRTERMINAL:SINGLEDUCT:CONSTANTVOLUME:NOREHEAT' - _AIR_DISTRIBUTION = 'ZONEHVAC:AIRDISTRIBUTIONUNIT' - _EQUIPMENT_LIST = 'ZONEHVAC:EQUIPMENTLIST' - _SIZING_ZONE = 'SIZING:ZONE' - _DESIGN_SPECIFICATION_OUTDOOR_AIR = 'DESIGNSPECIFICATION:OUTDOORAIR' idf_surfaces = { cte.WALL: 'wall', @@ -125,8 +117,7 @@ class Idf: if levels_of_detail.construction is None: raise AttributeError('Level of detail of construction not assigned') if levels_of_detail.construction < 2: - raise AttributeError( - f'Level of detail of construction = {levels_of_detail.construction}. Required minimum level 2') + raise AttributeError(f'Level of detail of construction = {levels_of_detail.construction}. Required minimum level 2') if levels_of_detail.usage is None: raise AttributeError('Level of detail of usage not assigned') if levels_of_detail.usage < 2: @@ -357,7 +348,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) @@ -390,86 +381,22 @@ class Idf: ) def _add_heating_system(self, thermal_zone, zone_name): - for air_system in self._idf.idfobjects[self._EQUIPMENT_CONNECTIONS]: + for air_system in self._idf.idfobjects[self._IDEAL_LOAD_AIR_SYSTEM]: if air_system.Zone_Name == zone_name: return thermostat = self._add_thermostat(thermal_zone) - self._idf.newidfobject(self._EQUIPMENT_CONNECTIONS, + self._idf.newidfobject(self._IDEAL_LOAD_AIR_SYSTEM, Zone_Name=zone_name, - Zone_Conditioning_Equipment_List_Name=f'{zone_name} Equipment List', - Zone_Air_Inlet_Node_or_NodeList_Name=f'{zone_name} Inlet Node List', - Zone_Air_Node_Name=f'Node 1', - Zone_Return_Air_Node_or_NodeList_Name=f'{zone_name} Return Node List') - - def _add_nodelist_system(self, thermal_zone, zone_name): - self._idf.newidfobject(self._NODE_LIST, - Name=f'{zone_name} Inlet Node List', - Node_1_Name='Node 2') - self._idf.newidfobject(self._NODE_LIST, - Name=f'{zone_name} Return Node List', - Node_1_Name='Node 3') - - def _add_baseboard_system(self, thermal_zone, zone_name): - for baseboard in self._idf.idfobjects[self._BASEBOARD]: - if baseboard.Zone_Name == zone_name: - return - self._idf.newidfobject(self._BASEBOARD, Name=f'Elec Baseboard',Availability_Schedule_Name='HVAC AVAIL') - - def _add_air_terminal_system(self, thermal_zone, zone_name): - """for air_terminal in self._idf.idfobjects[self._AIR_TERMINAL_NO_REHEAT]: - if air_terminal.Zone_Name == zone_name: - return""" - self._idf.newidfobject(self._AIR_TERMINAL_NO_REHEAT, Name=f'Diffuser', - Availability_Schedule_Name='HVAC AVAIL', - Air_Inlet_Node_Name='Node 4', - Air_Outlet_Node_Name='Node 2', - Maximum_Air_Flow_Rate='AutoSize') - - def _add_air_distribution_system(self, thermal_zone, zone_name): - for air_distribution in self._idf.idfobjects[self._AIR_DISTRIBUTION]: - if air_distribution.Zone_Name == zone_name: - return - self._idf.newidfobject(self._AIR_DISTRIBUTION, - Name='ADU Diffuser', - Air_Distribution_Unit_Outlet_Node_Name='Node 2', - Air_Terminal_Object_Type='AirTerminal:SingleDuct:ConstantVolume:NoReheat', - Air_Terminal_Name='Diffuser') - - def _add_equipment_list_system(self, thermal_zone, zone_name): - for air_distribution in self._idf.idfobjects[self._EQUIPMENT_LIST]: - if air_distribution.Zone_Name == zone_name: - return - self._idf.newidfobject(self._EQUIPMENT_LIST, - Name=f'{zone_name} Equipment List', - Load_Distribution_Scheme='SequentialLoad', - Zone_Equipment_1_Object_Type='ZoneHVAC:Baseboard:Convective:Electric', - Zone_Equipment_1_Name='Elec Baseboard', - Zone_Equipment_1_Cooling_Sequence='1', - Zone_Equipment_1_Heating_or_NoLoad_Sequence='1', - Zone_Equipment_2_Object_Type='ZoneHVAC:AirDistributionUnit', - Zone_Equipment_2_Name='ADU Diffuser', - Zone_Equipment_2_Cooling_Sequence='2', - Zone_Equipment_2_Heating_or_NoLoad_Sequence='2' - ) - - def _add_sizing_zone(self, thermal_zone, zone_name): - koa=self._idf.newidfobject(self._SIZING_ZONE, - Zone_or_ZoneList_Name=f'{zone_name}', - Zone_Cooling_Design_Supply_Air_Humidity_Ratio='0.0085', - Zone_Heating_Design_Supply_Air_Humidity_Ratio='0.008' - ) - - def _add_outdoor_air_design_specification(self, thermal_zone, zone_name): - self._idf.newidfobject(self._DESIGN_SPECIFICATION_OUTDOOR_AIR, - Name='MidriseApartment Apartment Ventilation', - Outdoor_Air_Method='Sum', - Outdoor_Air_Flow_per_Person='0.0169901079552') + System_Availability_Schedule_Name=f'HVAC AVAIL SCHEDULES {thermal_zone.usage_name}', + Heating_Availability_Schedule_Name=f'HVAC AVAIL SCHEDULES {thermal_zone.usage_name}', + Cooling_Availability_Schedule_Name=f'HVAC AVAIL SCHEDULES {thermal_zone.usage_name}', + Template_Thermostat_Name=thermostat.Name) def _add_occupancy(self, thermal_zone, zone_name): number_of_people = thermal_zone.occupancy.occupancy_density * thermal_zone.total_floor_area fraction_radiant = 0 total_sensible = ( - thermal_zone.occupancy.sensible_radiative_internal_gain + thermal_zone.occupancy.sensible_convective_internal_gain + thermal_zone.occupancy.sensible_radiative_internal_gain + thermal_zone.occupancy.sensible_convective_internal_gain ) if total_sensible != 0: fraction_radiant = thermal_zone.occupancy.sensible_radiative_internal_gain / total_sensible @@ -626,6 +553,10 @@ class Idf: self._add_zone(thermal_zone, building.name) self._add_heating_system(thermal_zone, building.name) self._add_infiltration(thermal_zone, building.name) + self._add_ventilation(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) self._add_dhw(thermal_zone, building.name) if self._export_type == "Surfaces": if building.name in self._target_buildings or building.name in self._adjacent_buildings: @@ -679,6 +610,7 @@ class Idf: windows_list.append(window) for window in windows_list: self._idf.removeidfobject(window) + self._idf.saveas(str(self._output_file)) return self._idf diff --git a/hub/exports/building_energy/idf_files/Minimal.idf b/hub/exports/building_energy/idf_files/Minimal.idf index 31ebb81e..b7a9a935 100644 --- a/hub/exports/building_energy/idf_files/Minimal.idf +++ b/hub/exports/building_energy/idf_files/Minimal.idf @@ -13,7 +13,7 @@ ! HVAC: None. ! - Version,24.1; + Version,23.2; Timestep,4; @@ -122,30 +122,36 @@ No, !- Do Zone Sizing Calculation No, !- Do System Sizing Calculation No, !- Do Plant Sizing Calculation - Yes, !- Run Simulation for Sizing Periods - No, !- Run Simulation for Weather File Run Periods + No, !- Run Simulation for Sizing Periods + Yes, !- Run Simulation for Weather File Run Periods No, !- Do HVAC Sizing Simulation for Sizing Periods 1; !- Maximum Number of HVAC Sizing Simulation Passes - Output:VariableDictionary,Regular; + Output:Table:SummaryReports, AnnualBuildingUtilityPerformanceSummary, + DemandEndUseComponentsSummary, + SensibleHeatGainSummary, + InputVerificationandResultsSummary, + AdaptiveComfortSummary, + Standard62.1Summary, + ClimaticDataSummary, + EquipmentSummary, + EnvelopeSummary, + LightingSummary, + HVACSizingSummary, + SystemSummary, + ComponentSizingSummary, + OutdoorAirSummary, + ObjectCountSummary, + EndUseEnergyConsumptionOtherFuelsMonthly, + PeakEnergyEndUseOtherFuelsMonthly; - Output:Variable,*,Site Outdoor Air Drybulb Temperature,Timestep; - Output:Variable,*,Site Outdoor Air Wetbulb Temperature,Timestep; + OutputControl:Table:Style, CommaAndHTML,JtoKWH; - Output:Variable,*,Site Outdoor Air Dewpoint Temperature,Timestep; - - Output:Variable,*,Site Solar Azimuth Angle,Timestep; - - Output:Variable,*,Site Solar Altitude Angle,Timestep; - - Output:Variable,*,Site Direct Solar Radiation Rate per Area,Timestep; - - Output:Variable,*,Site Diffuse Solar Radiation Rate per Area,Timestep; - - OutputControl:Table:Style, - HTML; !- Column Separator - - Output:Table:SummaryReports, - AllSummary; !- Report 1 Name + Output:Meter,DISTRICTHEATING:Facility,hourly; + Output:Meter,DISTRICTCOOLING:Facility,hourly; + Output:Meter,InteriorEquipment:Electricity,hourly; + Output:Meter,InteriorLights:Electricity,hourly; + OutputControl:IlluminanceMap:Style, + Comma; !- Column separator \ No newline at end of file diff --git a/main.py b/main.py index cfd422e7..b2441da1 100644 --- a/main.py +++ b/main.py @@ -29,7 +29,7 @@ city = GeometryFactory('geojson', function_to_hub=Dictionaries().montreal_function_to_hub_function).city # Enrich city data ConstructionFactory('nrcan', city).enrich() -UsageFactory('comnet', city).enrich() +UsageFactory('nrcan', city).enrich() WeatherFactory('epw', city).enrich() ExportsFactory('sra', city, output_path).export() sra_path = (output_path / f'{city.name}_sra.xml').resolve() diff --git a/scripts/energy_system_analysis_report.py b/scripts/energy_system_analysis_report.py index 3eccd568..09b88482 100644 --- a/scripts/energy_system_analysis_report.py +++ b/scripts/energy_system_analysis_report.py @@ -31,18 +31,18 @@ class EnergySystemAnalysisReport: building.name, str(building.year_of_construction), building.function, - str(format(building.heating_demand[cte.YEAR][0] / 1e6, '.2f')), - str(format(building.cooling_demand[cte.YEAR][0] / 1e6, '.2f')), + str(format(building.heating_demand[cte.YEAR][0] / 3.6e9, '.2f')), + str(format(building.cooling_demand[cte.YEAR][0] / 3.6e9, '.2f')), str(format(building.domestic_hot_water_heat_demand[cte.YEAR][0] / 1e6, '.2f')), str(format( - (building.lighting_electrical_demand[cte.YEAR][0] + building.appliances_electrical_demand[cte.YEAR][0]) / 1e6, - '.2f')), + (building.lighting_electrical_demand[cte.YEAR][0] + building.appliances_electrical_demand[cte.YEAR][0]) + / 1e6, '.2f')), ] intensity_data = [ building.name, str(format(total_floor_area, '.2f')), - str(format(building.heating_demand[cte.YEAR][0] / (1e3 * total_floor_area), '.2f')), - str(format(building.cooling_demand[cte.YEAR][0] / (1e3 * total_floor_area), '.2f')), + str(format(building.heating_demand[cte.YEAR][0] / (3.6e6 * total_floor_area), '.2f')), + str(format(building.cooling_demand[cte.YEAR][0] / (3.6e6 * total_floor_area), '.2f')), str(format( (building.lighting_electrical_demand[cte.YEAR][0] + building.appliances_electrical_demand[cte.YEAR][0]) / (1e3 * total_floor_area), '.2f')) @@ -109,8 +109,8 @@ class EnergySystemAnalysisReport: plt.close() building_names = [building.name for building in self.city.buildings] - yearly_heating_demand = [building.heating_demand[cte.YEAR][0] / 1e6 for building in self.city.buildings] - yearly_cooling_demand = [building.cooling_demand[cte.YEAR][0] / 1e6 for building in self.city.buildings] + yearly_heating_demand = [building.heating_demand[cte.YEAR][0] / 3.6e9 for building in self.city.buildings] + yearly_cooling_demand = [building.cooling_demand[cte.YEAR][0] / 3.6e9 for building in self.city.buildings] yearly_dhw_demand = [building.domestic_hot_water_heat_demand[cte.YEAR][0] / 1e6 for building in self.city.buildings] yearly_electricity_demand = [(building.lighting_electrical_demand[cte.YEAR][0] + @@ -139,14 +139,14 @@ class EnergySystemAnalysisReport: axs[0].bar(months, maximum_monthly_heating_load, color='red') # Plot on the first subplot axs[0].set_title('Maximum Monthly Heating Load') axs[0].set_xlabel('Month') - axs[0].set_ylabel('Load (kW)') + axs[0].set_ylabel('Load (kWh)') axs[0].tick_params(axis='x', rotation=45) # Plot maximum monthly cooling load axs[1].bar(months, maximum_monthly_cooling_load, color='blue') # Plot on the second subplot axs[1].set_title('Maximum Monthly Cooling Load') axs[1].set_xlabel('Month') - axs[1].set_ylabel('Load (kW)') + axs[1].set_ylabel('Load (kWh)') axs[1].tick_params(axis='x', rotation=45) plt.tight_layout() # Adjust layout to prevent overlapping @@ -156,8 +156,8 @@ class EnergySystemAnalysisReport: def load_duration_curves(self): save_directory = self.output_path for building in self.city.buildings: - heating_demand = [demand / 1000 for demand in building.heating_demand[cte.HOUR]] - cooling_demand = [demand / 1000 for demand in building.cooling_demand[cte.HOUR]] + heating_demand = [demand / 3.6e6 for demand in building.heating_demand[cte.HOUR]] + cooling_demand = [demand / 3.6e6 for demand in building.cooling_demand[cte.HOUR]] heating_demand_sorted = sorted(heating_demand, reverse=True) cooling_demand_sorted = sorted(cooling_demand, reverse=True) @@ -169,7 +169,7 @@ class EnergySystemAnalysisReport: # Plot sorted heating demand axs[0].plot(heating_demand_sorted, color='red', linewidth=2, label='Heating Demand') axs[0].set_xlabel('Hour', fontsize=14) - axs[0].set_ylabel('Heating Demand', fontsize=14) + axs[0].set_ylabel('Heating Demand (kWh)', fontsize=14) axs[0].set_title('Heating Load Duration Curve', fontsize=16) axs[0].grid(True) axs[0].legend(loc='upper right', fontsize=12) @@ -177,7 +177,7 @@ class EnergySystemAnalysisReport: # Plot sorted cooling demand axs[1].plot(cooling_demand_sorted, color='blue', linewidth=2, label='Cooling Demand') axs[1].set_xlabel('Hour', fontsize=14) - axs[1].set_ylabel('Cooling Demand', fontsize=14) + axs[1].set_ylabel('Cooling Demand (kWh)', fontsize=14) axs[1].set_title('Cooling Load Duration Curve', fontsize=16) axs[1].grid(True) axs[1].legend(loc='upper right', fontsize=12) @@ -204,7 +204,7 @@ class EnergySystemAnalysisReport: cooling_system = "-" dhw = "-" electricity = "Grid" - hvac_ec = format((building.heating_consumption[cte.YEAR][0] + building.cooling_consumption[cte.YEAR][0]) / 1e6, + hvac_ec = format((building.heating_consumption[cte.YEAR][0] + building.cooling_consumption[cte.YEAR][0]) / 3.6e9, '.2f') dhw_ec = format(building.domestic_hot_water_consumption[cte.YEAR][0] / 1e6, '.2f') on_site_generation = "-" @@ -258,11 +258,11 @@ class EnergySystemAnalysisReport: for generation_system in energy_system.generation_systems: consumption = 0 if demand_type == cte.HEATING: - consumption = building.heating_consumption[cte.YEAR][0] / 1e6 + consumption = building.heating_consumption[cte.YEAR][0] / 3.6e9 elif demand_type == cte.DOMESTIC_HOT_WATER: consumption = building.domestic_hot_water_consumption[cte.YEAR][0] / 1e6 elif demand_type == cte.COOLING: - consumption = building.cooling_consumption[cte.YEAR][0] / 1e6 + consumption = building.cooling_consumption[cte.YEAR][0] / 3.6e9 if generation_system.fuel_type == cte.ELECTRICITY: fuel_breakdown[demand_type]["Electricity"] += consumption