Merge branch 'idf_export' into 'master'

Idf export

See merge request Guille/libs!16
This commit is contained in:
Guillermo Gutierrez Morote 2022-05-02 19:51:21 +00:00
commit 6414ceedaf
4 changed files with 106 additions and 38 deletions

View File

@ -1,3 +1,4 @@
#
# Prepare your environment # Prepare your environment
Download the latest version of python and Microsoft c++ redistributable Download the latest version of python and Microsoft c++ redistributable

View File

@ -129,10 +129,10 @@ class ThermalZone:
self._indirectly_heated_area_ratio = float(value) self._indirectly_heated_area_ratio = float(value)
@property @property
def infiltration_rate_system_on(self) -> Union[None, Schedule]: def infiltration_rate_system_on(self):
""" """
Get thermal zone infiltration rate system on in air changes per hour (ACH) Get thermal zone infiltration rate system on in air changes per hour (ACH)
:return: None or Schedule :return: None or float
""" """
return self._infiltration_rate_system_on return self._infiltration_rate_system_on
@ -140,15 +140,15 @@ class ThermalZone:
def infiltration_rate_system_on(self, value): def infiltration_rate_system_on(self, value):
""" """
Set thermal zone infiltration rate system on in air changes per hour (ACH) Set thermal zone infiltration rate system on in air changes per hour (ACH)
:param value: Schedule :param value: float
""" """
self._infiltration_rate_system_on = value self._infiltration_rate_system_on = value
@property @property
def infiltration_rate_system_off(self) -> Union[None, Schedule]: def infiltration_rate_system_off(self):
""" """
Get thermal zone infiltration rate system off in air changes per hour (ACH) Get thermal zone infiltration rate system off in air changes per hour (ACH)
:return: None or Schedule :return: None or float
""" """
return self._infiltration_rate_system_off return self._infiltration_rate_system_off
@ -156,7 +156,7 @@ class ThermalZone:
def infiltration_rate_system_off(self, value): def infiltration_rate_system_off(self, value):
""" """
Set thermal zone infiltration rate system on in air changes per hour (ACH) Set thermal zone infiltration rate system on in air changes per hour (ACH)
:param value: Schedule :param value: float
""" """
self._infiltration_rate_system_off = value self._infiltration_rate_system_off = value

View File

@ -1,20 +1,22 @@
""" """
TestOccupancyFactory test and validate the city model structure schedules parameters Idf exports one building to idf format
SPDX - License - Identifier: LGPL - 3.0 - or -later SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group Copyright © 2022 Concordia CERC group
Project Coder Guille Guillermo.GutierrezMorote@concordia.ca Project Coder Guille Guillermo.GutierrezMorote@concordia.ca
Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
Soroush Samareh Abolhassani soroush.samarehabolhassani@mail.concordia.ca Soroush Samareh Abolhassani soroush.samarehabolhassani@mail.concordia.ca
""" """
import copy
import math
from pathlib import Path from pathlib import Path
from geomeppy import IDF from geomeppy import IDF
import helpers.constants as cte import helpers.constants as cte
from city_model_structure.attributes.schedule import Schedule
class Idf: class Idf:
""" """
Export city to IDF Exports city to IDF
""" """
_THERMOSTAT = 'HVACTEMPLATE:THERMOSTAT' _THERMOSTAT = 'HVACTEMPLATE:THERMOSTAT'
_IDEAL_LOAD_AIR_SYSTEM = 'HVACTEMPLATE:ZONE:IDEALLOADSAIRSYSTEM' _IDEAL_LOAD_AIR_SYSTEM = 'HVACTEMPLATE:ZONE:IDEALLOADSAIRSYSTEM'
@ -29,7 +31,6 @@ class Idf:
_ZONE = 'ZONE' _ZONE = 'ZONE'
_LIGHTS = 'LIGHTS' _LIGHTS = 'LIGHTS'
_PEOPLE = 'PEOPLE' _PEOPLE = 'PEOPLE'
_ELECTRIC_EQUIPMENT = 'ELECTRICEQUIPMENT'
_INFILTRATION = 'ZONEINFILTRATION:DESIGNFLOWRATE' _INFILTRATION = 'ZONEINFILTRATION:DESIGNFLOWRATE'
_BUILDING_SURFACE = 'BuildingSurfaceDetailed' _BUILDING_SURFACE = 'BuildingSurfaceDetailed'
_SCHEDULE_LIMIT = 'SCHEDULETYPELIMITS' _SCHEDULE_LIMIT = 'SCHEDULETYPELIMITS'
@ -38,6 +39,11 @@ class Idf:
_ANY_NUMBER = 'Any Number' _ANY_NUMBER = 'Any Number'
_CONTINUOUS = 'Continuous' _CONTINUOUS = 'Continuous'
_DISCRETE = 'Discrete' _DISCRETE = 'Discrete'
_BUILDING = 'BUILDING'
_SIZING_PERIODS = 'SIZINGPERIOD:DESIGNDAY'
_LOCATION = 'SITE:LOCATION'
_WINDOW_MATERIAL_SIMPLE = 'WINDOWMATERIAL:SIMPLEGLAZINGSYSTEM'
_WINDOW = 'WINDOW'
idf_surfaces = { idf_surfaces = {
# todo: make an enum for all the surface types # todo: make an enum for all the surface types
@ -162,9 +168,6 @@ class Idf:
_kwargs[f'Field_{j * 25 + 3 + i}'] = f'Until: {i + 1:02d}:00,{_val[i]}' _kwargs[f'Field_{j * 25 + 3 + i}'] = f'Until: {i + 1:02d}:00,{_val[i]}'
self._idf.newidfobject(self._COMPACT_SCHEDULE, **_kwargs) self._idf.newidfobject(self._COMPACT_SCHEDULE, **_kwargs)
def _add_non_hourly_schedule(self, usage, schedules):
raise NotImplementedError
def _write_schedules_file(self, usage, schedule): def _write_schedules_file(self, usage, schedule):
file_name = str((Path(self._output_path) / f'{schedule.type} schedules {usage}.dat').resolve()) file_name = str((Path(self._output_path) / f'{schedule.type} schedules {usage}.dat').resolve())
with open(file_name, 'w') as file: with open(file_name, 'w') as file:
@ -173,7 +176,6 @@ class Idf:
return file_name return file_name
def _add_file_schedule(self, usage, schedule, file_name): def _add_file_schedule(self, usage, schedule, file_name):
print(file_name)
_schedule = self._idf.newidfobject(self._FILE_SCHEDULE, Name=f'{schedule.type} schedules {usage}') _schedule = self._idf.newidfobject(self._FILE_SCHEDULE, Name=f'{schedule.type} schedules {usage}')
_schedule.Schedule_Type_Limits_Name = self.idf_type_limits[schedule.data_type] _schedule.Schedule_Type_Limits_Name = self.idf_type_limits[schedule.data_type]
_schedule.File_Name = file_name _schedule.File_Name = file_name
@ -184,6 +186,28 @@ class Idf:
_schedule.Interpolate_to_Timestep = 'No' _schedule.Interpolate_to_Timestep = 'No'
_schedule.Minutes_per_Item = 60 _schedule.Minutes_per_Item = 60
def _add_infiltration_schedules(self, thermal_zone):
_infiltration_schedules = []
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(thermal_zone.infiltration_rate_system_off)
else:
_infiltration_values.append(thermal_zone.infiltration_rate_system_on)
_schedule.values = _infiltration_values
_infiltration_schedules.append(_schedule)
for schedule in self._idf.idfobjects[self._HOURLY_SCHEDULE]:
if schedule.Name == f'{_infiltration_schedules[0].type} schedules {thermal_zone.usage}':
return
return self._add_standard_compact_hourly_schedule(thermal_zone.usage, _infiltration_schedules)
def _add_schedules(self, usage, new_schedules, schedule_from_file=False): def _add_schedules(self, usage, new_schedules, schedule_from_file=False):
if schedule_from_file: if schedule_from_file:
new_schedule = new_schedules[0] new_schedule = new_schedules[0]
@ -218,6 +242,22 @@ class Idf:
_kwargs[f'Layer_{i + 1}'] = layers[1].material.name _kwargs[f'Layer_{i + 1}'] = layers[1].material.name
self._idf.newidfobject(self._CONSTRUCTION, **_kwargs) self._idf.newidfobject(self._CONSTRUCTION, **_kwargs)
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:
return
order = str(len(self._idf.idfobjects[self._WINDOW_MATERIAL_SIMPLE]) + 1)
material_name = 'glazing_' + order
_kwargs = {'Name': material_name, 'UFactor': thermal_opening.overall_u_value,
'Solar_Heat_Gain_Coefficient': thermal_opening.g_value}
self._idf.newidfobject(self._WINDOW_MATERIAL_SIMPLE, **_kwargs)
window_construction_name = 'window_construction_' + order
_kwargs = {'Name': window_construction_name, 'Outside_Layer': material_name}
self._idf.newidfobject(self._CONSTRUCTION, **_kwargs)
def _add_zone(self, thermal_zone): def _add_zone(self, thermal_zone):
for zone in self._idf.idfobjects['ZONE']: for zone in self._idf.idfobjects['ZONE']:
if zone.Name == thermal_zone.id: if zone.Name == thermal_zone.id:
@ -264,52 +304,56 @@ class Idf:
Activity_Level_Schedule_Name=f'Occupancy schedules {thermal_zone.usage}' Activity_Level_Schedule_Name=f'Occupancy schedules {thermal_zone.usage}'
) )
def _add_equipment(self, usage_zone): def _add_infiltration(self, thermal_zone):
self._idf.newidfobject(self._ELECTRIC_EQUIPMENT,
Name=f'{usage_zone.id}_electricload',
Zone_or_ZoneList_Name=usage_zone.id,
Schedule_Name=f'Electrical schedules {usage_zone.usage}', # todo: add electrical schedules
Design_Level_Calculation_Method='EquipmentLevel',
Design_Level=566000 # todo: change it from usage catalog
)
def _add_infiltration(self, usage_zone):
for zone in self._idf.idfobjects["ZONE"]: for zone in self._idf.idfobjects["ZONE"]:
if zone.Name == f'{usage_zone.id}_infiltration': if zone.Name == f'{thermal_zone.id}_infiltration':
return return
self._idf.newidfobject(self._INFILTRATION, self._idf.newidfobject(self._INFILTRATION,
Name=f'{usage_zone.id}_infiltration', Name=f'{thermal_zone.id}_infiltration',
Zone_or_ZoneList_Name=usage_zone.id, Zone_or_ZoneList_Name=thermal_zone.id,
Schedule_Name=f'Infiltration schedules {usage_zone.usage}', Schedule_Name=f'Infiltration schedules {thermal_zone.usage}',
Design_Flow_Rate_Calculation_Method='AirChanges/Hour', Design_Flow_Rate_Calculation_Method='AirChanges/Hour',
Air_Changes_per_Hour=usage_zone.mechanical_air_change, Air_Changes_per_Hour=thermal_zone.mechanical_air_change
Constant_Term_Coefficient=0.606, # todo: change it from usage catalog
Temperature_Term_Coefficient=3.6359996E-02, # todo: change it from usage catalog
Velocity_Term_Coefficient=0.1177165, # todo: change it from usage catalog
Velocity_Squared_Term_Coefficient=0.0000000E+00 # todo: change it from usage catalog
) )
def _rename_building(self, b):
for building in self._idf.idfobjects[self._BUILDING]:
building.Name = b.name
building['Solar_Distribution'] = 'FullExterior'
def _remove_sizing_periods(self):
while len(self._idf.idfobjects[self._SIZING_PERIODS]) > 0:
self._idf.popidfobject(self._SIZING_PERIODS, 0)
def _remove_location(self):
self._idf.popidfobject(self._LOCATION, 0)
def _export(self): def _export(self):
""" """
Export the idf file into the given path Export the idf file into the given path
export type = "Surfaces|Block" export type = "Surfaces|Block"
""" """
self._remove_location()
self._remove_sizing_periods()
for building in self._city.buildings: for building in self._city.buildings:
self._rename_building(building)
for internal_zone in building.internal_zones: for internal_zone in building.internal_zones:
for thermal_zone in internal_zone.thermal_zones: for thermal_zone in internal_zone.thermal_zones:
for thermal_boundary in thermal_zone.thermal_boundaries: for thermal_boundary in thermal_zone.thermal_boundaries:
self._add_construction(thermal_boundary) self._add_construction(thermal_boundary)
for thermal_opening in thermal_boundary.thermal_openings:
self._add_window_construction_and_material(thermal_opening)
usage = thermal_zone.usage usage = thermal_zone.usage
# todo: infiltration can be written with two values (system on and system off) in E+? Or just as schedule? self._add_infiltration_schedules(thermal_zone)
# self._add_schedule(usage, "Infiltration") # todo: why is this schedule unused?
self._add_schedules(usage, thermal_zone.lighting.schedules) self._add_schedules(usage, thermal_zone.lighting.schedules)
self._add_schedules(usage, thermal_zone.occupancy.occupancy_schedules, schedule_from_file=True) self._add_schedules(usage, thermal_zone.occupancy.occupancy_schedules, schedule_from_file=True)
self._add_schedules(usage, thermal_zone.thermal_control.hvac_availability_schedules) self._add_schedules(usage, thermal_zone.thermal_control.hvac_availability_schedules)
self._add_zone(thermal_zone) self._add_zone(thermal_zone)
self._add_heating_system(thermal_zone) self._add_heating_system(thermal_zone)
# self._add_infiltration(usage_zone) self._add_infiltration(thermal_zone)
self._add_occupancy(thermal_zone) self._add_occupancy(thermal_zone)
if self._export_type == "Surfaces": if self._export_type == "Surfaces":
@ -356,14 +400,36 @@ class Idf:
self._idf.intersect_match() self._idf.intersect_match()
def _add_surfaces(self, building): def _add_surfaces(self, building):
for internal_zone in building.internal_zones: for internal_zone in building.internal_zones:
for thermal_zone in internal_zone.thermal_zones: for thermal_zone in internal_zone.thermal_zones:
for boundary in thermal_zone.thermal_boundaries: for boundary in thermal_zone.thermal_boundaries:
idf_surface_type = self.idf_surfaces[boundary.parent_surface.type] idf_surface_type = self.idf_surfaces[boundary.parent_surface.type]
# todo: thermal boundary vs. surfaces??
surface = self._idf.newidfobject(self._SURFACE, Name=f'{boundary.parent_surface.name}', surface = self._idf.newidfobject(self._SURFACE, Name=f'{boundary.parent_surface.name}',
Surface_Type=idf_surface_type, Zone_Name=thermal_zone.id, Surface_Type=idf_surface_type, Zone_Name=thermal_zone.id,
Construction_Name=boundary.construction_name) Construction_Name=boundary.construction_name)
coordinates = self._matrix_to_list(boundary.parent_surface.solid_polygon.coordinates, coordinates = self._matrix_to_list(boundary.parent_surface.solid_polygon.coordinates,
self._city.lower_corner) self._city.lower_corner)
surface.setcoords(coordinates) surface.setcoords(coordinates)
self._add_windows(boundary)
def _add_windows(self, boundary):
for opening in boundary.thermal_openings:
for construction in self._idf.idfobjects[self._CONSTRUCTION]:
if construction['Outside_Layer'].split('_')[0] == 'glazing':
window_construction = construction
if self._compare_window_constructions(window_construction, opening):
opening_name = 'window_' + str(len(self._idf.idfobjects[self._WINDOW]) + 1)
opening_length = math.sqrt(opening.area)
self._idf.newidfobject(self._WINDOW, Name=f'{opening_name}', Construction_Name=window_construction['Name'],
Building_Surface_Name=boundary.parent_surface.name, Multiplier='1',
Length=opening_length, Height=opening_length)
def _compare_window_constructions(self, window_construction, opening):
glazing = window_construction['Outside_Layer']
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:
return True
return False

View File

@ -36,7 +36,8 @@ class UsPhysicsParameters(NrelPhysicsInterface):
archetype = self._search_archetype(building.function, building.year_of_construction, self._climate_zone) archetype = self._search_archetype(building.function, building.year_of_construction, self._climate_zone)
except KeyError: except KeyError:
sys.stderr.write(f'Building {building.name} has unknown archetype for building function: {building.function} ' sys.stderr.write(f'Building {building.name} has unknown archetype for building function: {building.function} '
f'and building year of construction: {building.year_of_construction}\n') f'and building year of construction: {building.year_of_construction} '
f'and climate zone reference norm {self._climate_zone}\n')
return return
# if building has no thermal zones defined from geometry, one thermal zone per storey is assigned # if building has no thermal zones defined from geometry, one thermal zone per storey is assigned