forked from s_ranjbar/city_retrofit
Merge branch 'idf_export' into 'master'
Idf export See merge request Guille/libs!16
This commit is contained in:
commit
6414ceedaf
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue
Block a user