diff --git a/hub/exports/building_energy/idf.py b/hub/exports/building_energy/idf.py index 7fb866a6..e084effc 100644 --- a/hub/exports/building_energy/idf.py +++ b/hub/exports/building_energy/idf.py @@ -81,7 +81,7 @@ class Idf: } def __init__(self, city, output_path, idf_file_path, idd_file_path, epw_file_path, export_type="Surfaces", - target_buildings=None, adjacent_buildings=None): + target_buildings=None): self._city = city self._output_path = str(output_path.resolve()) self._output_file = str((output_path / f'{city.name}.idf').resolve()) @@ -97,11 +97,13 @@ class Idf: self._idf.newidfobject(self._SCHEDULE_LIMIT, Name=self._ON_OFF, Lower_Limit_Value=0, Upper_Limit_Value=1, Numeric_Type=self._DISCRETE) self._target_buildings = target_buildings + self._adjacent_buildings = [] if target_buildings is None: self._target_buildings = [building.name for building in self._city.buildings] - self._adjacent_buildings = adjacent_buildings - if self._adjacent_buildings is None: - self._adjacent_buildings = [] + else: + for building_name in target_buildings: + building = city.city_object(building_name) + self._adjacent_buildings += building.neighbours self._export() @staticmethod @@ -149,6 +151,72 @@ class Idf: Visible_Absorptance=layer.material.visible_absorptance ) + @staticmethod + def _create_infiltration_schedules(thermal_zone): + _infiltration_schedules = [] + if thermal_zone.thermal_control is None: + return [] + for hvac_availability_schedule in thermal_zone.thermal_control.hvac_availability_schedules: + _schedule = Schedule() + _schedule.type = cte.INFILTRATION + _schedule.data_type = cte.FRACTION + _schedule.time_step = cte.HOUR + _schedule.time_range = cte.DAY + _schedule.day_types = copy.deepcopy(hvac_availability_schedule.day_types) + _infiltration_values = [] + for hvac_value in hvac_availability_schedule.values: + if hvac_value == 0: + _infiltration_values.append(1.0) + else: + if thermal_zone.infiltration_rate_system_off == 0: + _infiltration_values.append(0.0) + else: + _infiltration_values.append( + thermal_zone.infiltration_rate_system_on / thermal_zone.infiltration_rate_system_off) + _schedule.values = _infiltration_values + _infiltration_schedules.append(_schedule) + return _infiltration_schedules + + @staticmethod + def _create_yearly_values_schedules(schedule_type, values): + _schedule = Schedule() + _schedule.type = schedule_type + _schedule.data_type = cte.ANY_NUMBER + _schedule.time_step = cte.HOUR + _schedule.time_range = cte.YEAR + _schedule.day_types = ['monday', + 'tuesday', + 'wednesday', + 'thursday', + 'friday', + 'saturday', + 'sunday', + 'holiday', + 'winter_design_day', + 'summer_design_day'] + _schedule.values = values + return [_schedule] + + @staticmethod + def _create_constant_value_schedules(schedule_type, value): + _schedule = Schedule() + _schedule.type = schedule_type + _schedule.data_type = cte.ANY_NUMBER + _schedule.time_step = cte.HOUR + _schedule.time_range = cte.DAY + _schedule.day_types = ['monday', + 'tuesday', + 'wednesday', + 'thursday', + 'friday', + 'saturday', + 'sunday', + 'holiday', + 'winter_design_day', + 'summer_design_day'] + _schedule.values = [value for _ in range(0, 24)] + return [_schedule] + def _add_standard_compact_hourly_schedule(self, usage, schedule_type, schedules): for schedule in self._idf.idfobjects[self._COMPACT_SCHEDULE]: if schedule.Name == f'{schedule_type} schedules {usage}': @@ -189,52 +257,6 @@ class Idf: _schedule.Interpolate_to_Timestep = 'No' _schedule.Minutes_per_Item = 60 - def _add_infiltration_schedules(self, thermal_zone): - _infiltration_schedules = [] - if thermal_zone.thermal_control is None: - return - for hvac_availability_schedule in thermal_zone.thermal_control.hvac_availability_schedules: - _schedule = Schedule() - _schedule.type = cte.INFILTRATION - _schedule.data_type = cte.FRACTION - _schedule.time_step = cte.HOUR - _schedule.time_range = cte.DAY - _schedule.day_types = copy.deepcopy(hvac_availability_schedule.day_types) - _infiltration_values = [] - for hvac_value in hvac_availability_schedule.values: - if hvac_value == 0: - _infiltration_values.append(1.0) - else: - if thermal_zone.infiltration_rate_system_off == 0: - _infiltration_values.append(0.0) - else: - _infiltration_values.append( - thermal_zone.infiltration_rate_system_on / thermal_zone.infiltration_rate_system_off) - _schedule.values = _infiltration_values - _infiltration_schedules.append(_schedule) - for schedule in self._idf.idfobjects[self._HOURLY_SCHEDULE]: - if schedule.Name == f'Infiltration schedules {thermal_zone.usage_name}': - return - return self._add_standard_compact_hourly_schedule(thermal_zone.usage_name, 'Infiltration', _infiltration_schedules) - - def _add_people_activity_level_schedules(self, thermal_zone): - _occ = thermal_zone.occupancy - if _occ.occupancy_density == 0: - _total_heat = 0 - else: - _total_heat = (_occ.sensible_convective_internal_gain + _occ.sensible_radiative_internal_gain - + _occ.latent_internal_gain) / _occ.occupancy_density - for schedule in self._idf.idfobjects[self._COMPACT_SCHEDULE]: - if schedule.Name == f'Activity Level schedules {thermal_zone.usage_name}': - return - _kwargs = {'Name': f'Activity Level schedules {thermal_zone.usage_name}', - 'Schedule_Type_Limits_Name': self.idf_type_limits[cte.ANY_NUMBER], - 'Field_1': 'Through: 12/31', - 'Field_2': 'For AllDays', - 'Field_3': f'Until: 24:00,{_total_heat}'} - self._idf.newidfobject(self._COMPACT_SCHEDULE, **_kwargs) - return - def _add_schedules(self, usage, schedule_type, new_schedules): schedule_from_file = False for schedule in new_schedules: @@ -252,25 +274,6 @@ class Idf: return return self._add_standard_compact_hourly_schedule(usage, schedule_type, new_schedules) - def _add_constant_hourly_year_schedules(self, thermal_zone, value, schedule_type): - _schedule = Schedule() - _schedule.type = schedule_type - _schedule.data_type = cte.ANY_NUMBER - _schedule.time_step = cte.HOUR - _schedule.time_range = cte.DAY - _schedule.day_types = ['monday', - 'tuesday', - 'wednesday', - 'thursday', - 'friday', - 'saturday', - 'sunday', - 'holiday', - 'winter_design_day', - 'summer_design_day'] - _schedule.values = [value for _ in range(0, 24)] - return self._add_standard_compact_hourly_schedule(thermal_zone.usage_name, schedule_type, [_schedule]) - def _add_construction(self, thermal_boundary): for construction in self._idf.idfobjects[self._CONSTRUCTION]: if thermal_boundary.parent_surface.vegetation is not None: @@ -328,7 +331,6 @@ class Idf: for thermostat in self._idf.idfobjects[self._THERMOSTAT]: if thermostat.Name == thermostat_name: return thermostat - # todo: change schedules to schedule name and create schedules using the add_schedule function return self._idf.newidfobject(self._THERMOSTAT, Name=thermostat_name, Heating_Setpoint_Schedule_Name=f'Heating thermostat schedules {thermal_zone.usage_name}', @@ -416,6 +418,7 @@ class Idf: Design_Flow_Rate_Calculation_Method='AirChanges/Hour', Air_Changes_per_Hour=thermal_zone.infiltration_rate_system_off ) + def _add_ventilation(self, thermal_zone, zone_name): # for zone in self._idf.idfobjects["ZONE"]: # if zone.Name == f'{zone_name}_infiltration': @@ -432,7 +435,7 @@ class Idf: Flow_Rate_per_Zone_Floor_Area=thermal_zone.infiltration_rate_system_off ) - def _add_dhw(self, building, thermal_zone, zone_name): + def _add_dhw(self, thermal_zone, zone_name): peak_flow_rate = thermal_zone.domestic_hot_water.peak_flow * thermal_zone.total_floor_area self._idf.newidfobject(self._DHW, Name=f'DHW {zone_name}', @@ -440,7 +443,7 @@ class Idf: Flow_Rate_Fraction_Schedule_Name=f'DHW_prof schedules {thermal_zone.usage_name}', Target_Temperature_Schedule_Name=f'DHW_temp schedules {thermal_zone.usage_name}', Hot_Water_Supply_Temperature_Schedule_Name=f'DHW_temp schedules {thermal_zone.usage_name}', - Cold_Water_Supply_Temperature_Schedule_Name=f'cold_temp schedules {building.name}', + Cold_Water_Supply_Temperature_Schedule_Name=f'cold_temp schedules {zone_name}', EndUse_Subcategory=f'DHW {zone_name}', Zone_Name=zone_name ) @@ -486,7 +489,8 @@ class Idf: self._add_window_construction_and_material(thermal_opening) 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) + _new_schedules = self._create_infiltration_schedules(thermal_zone) + self._add_schedules(usage, 'Infiltration', _new_schedules) 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) @@ -494,10 +498,20 @@ class Idf: self._add_schedules(usage, 'Lighting', thermal_zone.lighting.schedules) self._add_schedules(usage, 'Appliance', thermal_zone.appliances.schedules) self._add_schedules(usage, 'DHW_prof', thermal_zone.domestic_hot_water.schedules) - # todo self._add_service_temp_schedules(thermal_zone) - value = int(thermal_zone.domestic_hot_water.service_temperature) - self._add_constant_hourly_year_schedules(thermal_zone, value, 'DHW_temp') - self._add_people_activity_level_schedules(thermal_zone) + _new_schedules = self._create_yearly_values_schedules('cold_temp', + building.cold_water_temperature[cte.HOUR]['epw']) + self._add_schedules(building.name, 'cold_temp', _new_schedules) + value = thermal_zone.domestic_hot_water.service_temperature + _new_schedules = self._create_constant_value_schedules('DHW_temp', value) + self._add_schedules(usage, 'DHW_temp', _new_schedules) + _occ = thermal_zone.occupancy + if _occ.occupancy_density == 0: + _total_heat = 0 + else: + _total_heat = (_occ.sensible_convective_internal_gain + _occ.sensible_radiative_internal_gain + + _occ.latent_internal_gain) / _occ.occupancy_density + _new_schedules = self._create_constant_value_schedules('Activity Level', _total_heat) + self._add_schedules(usage, 'Activity Level', _new_schedules) self._add_zone(thermal_zone, building.name) self._add_heating_system(thermal_zone, building.name) self._add_infiltration(thermal_zone, building.name) @@ -515,9 +529,6 @@ class Idf: self._add_shading(building) 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: - continue self._idf.newidfobject( "OUTPUT:VARIABLE",