Compare commits

...

3 Commits

Author SHA1 Message Date
6020964899 Validation in progress 2024-10-17 06:13:23 +02:00
841a6136bb Validation in progress 2024-10-15 06:12:11 +02:00
68d2bef9ec Validation in progress 2024-10-15 05:24:33 +02:00
12 changed files with 138 additions and 55 deletions

View File

@ -8,6 +8,8 @@ Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concord
""" """
import copy import copy
import os import os
import shutil
import subprocess
from datetime import datetime from datetime import datetime
import hub.exports.building_energy.idf_helper as idf_cte import hub.exports.building_energy.idf_helper as idf_cte
@ -41,11 +43,12 @@ class CercIdf(IdfBase):
_schedules_added_to_idf = {} _schedules_added_to_idf = {}
_materials_added_to_idf = {} _materials_added_to_idf = {}
_windows_added_to_idf = {'glazing_index': 0} _windows_added_to_idf = {}
_constructions_added_to_idf = {} _constructions_added_to_idf = {}
_thermostat_added_to_idf = {} _thermostat_added_to_idf = {}
def __init__(self, city, output_path, idf_file_path, idd_file_path, epw_file_path, target_buildings=None): def __init__(self, city, output_path, idf_file_path, idd_file_path, epw_file_path, target_buildings=None):
self._start = datetime.now()
super().__init__(city, output_path, idf_file_path, idd_file_path, epw_file_path, target_buildings) super().__init__(city, output_path, idf_file_path, idd_file_path, epw_file_path, target_buildings)
self._add_surfaces = IdfSurfaces.add self._add_surfaces = IdfSurfaces.add
self._add_file_schedule = IdfFileSchedule.add self._add_file_schedule = IdfFileSchedule.add
@ -66,15 +69,18 @@ class CercIdf(IdfBase):
self._add_shading = IdfShading.add self._add_shading = IdfShading.add
self._add_windows = IdfWindow.add self._add_windows = IdfWindow.add
with open(self._idf_file_path, 'r') as base_idf: with open(self._idf_file_path, 'r', encoding='UTF-8') as base_idf:
lines = base_idf.readlines() lines = base_idf.readlines()
# Change city name
with open(self._output_file_path, 'w') as self._idf_file: comment = f' !- Name'
field = f' Buildings in {self._city.name},'.ljust(26, ' ')
lines[15] = f'{field}{comment}\n'
with open(self._output_file_path, 'w', encoding='UTF-8') as self._idf_file:
self._idf_file.writelines(lines) self._idf_file.writelines(lines)
self._export() self._export()
def _create_geometry_rules(self): def _create_geometry_rules(self):
file = self._files['zones'] file = self._files['constructions']
self._write_to_idf_format(file, idf_cte.GLOBAL_GEOMETRY_RULES) self._write_to_idf_format(file, idf_cte.GLOBAL_GEOMETRY_RULES)
self._write_to_idf_format(file, 'UpperLeftCorner', 'Starting Vertex Position') self._write_to_idf_format(file, 'UpperLeftCorner', 'Starting Vertex Position')
self._write_to_idf_format(file, 'CounterClockWise', 'Vertex Entry Direction') self._write_to_idf_format(file, 'CounterClockWise', 'Vertex Entry Direction')
@ -84,14 +90,14 @@ class CercIdf(IdfBase):
for file in self._files.values(): for file in self._files.values():
file.close() file.close()
for path in self._file_paths.values(): for path in self._file_paths.values():
with open(path, 'r') as file: with open(path, 'r', encoding='UTF-8') as file:
lines = file.readlines() lines = file.readlines()
self._idf_file.writelines(lines) self._idf_file.writelines(lines)
for path in self._file_paths.values(): for path in self._file_paths.values():
os.unlink(path) os.unlink(path)
def _add_outputs(self): def _add_outputs(self):
with open(self._outputs_file_path, 'r') as base_idf: with open(self._outputs_file_path, 'r', encoding='UTF-8') as base_idf:
lines = base_idf.readlines() lines = base_idf.readlines()
self._idf_file.writelines(lines) self._idf_file.writelines(lines)
@ -157,7 +163,7 @@ class CercIdf(IdfBase):
return [_schedule] return [_schedule]
def _export(self): def _export(self):
start = datetime.now()
for building in self._city.buildings: for building in self._city.buildings:
is_target = building.name in self._target_buildings or building.name in self._adjacent_buildings is_target = building.name in self._target_buildings or building.name in self._adjacent_buildings
for internal_zone in building.internal_zones: for internal_zone in building.internal_zones:
@ -198,8 +204,8 @@ class CercIdf(IdfBase):
self._add_material(self, thermal_boundary) self._add_material(self, thermal_boundary)
self._add_construction(self, thermal_boundary) self._add_construction(self, thermal_boundary)
for thermal_opening in thermal_boundary.thermal_openings: for thermal_opening in thermal_boundary.thermal_openings:
self._add_windows_material(self, thermal_opening) self._add_windows_material(self, thermal_boundary, thermal_opening)
self._add_windows_constructions(self) self._add_windows_constructions(self, thermal_boundary)
self._add_zone(self, thermal_zone, building.name) self._add_zone(self, thermal_zone, building.name)
self._add_occupancy(self, thermal_zone, building.name) self._add_occupancy(self, thermal_zone, building.name)
self._add_lighting(self, thermal_zone, building.name) self._add_lighting(self, thermal_zone, building.name)
@ -211,7 +217,7 @@ class CercIdf(IdfBase):
self._add_dhw(self, thermal_zone, building.name) self._add_dhw(self, thermal_zone, building.name)
if is_target: if is_target:
self._add_surfaces(self, building, building.name) self._add_surfaces(self, building, building.name)
self._add_windows(self, building, building.name) self._add_windows(self, building)
else: else:
self._add_shading(self, building) self._add_shading(self, building)
@ -223,4 +229,20 @@ class CercIdf(IdfBase):
# Merge files # Merge files
self._merge_files() self._merge_files()
self._add_outputs() self._add_outputs()
print(f'Export completed in: {datetime.now() - start}') print(f'{len(self._city.buildings)} buildings export completed in: {datetime.now() - self._start}')
@property
def _energy_plus(self):
return shutil.which('energyplus')
def run(self):
cmd = [self._energy_plus,
'--weather', self._epw_file_path,
'--output-directory', self._output_path,
'--idd', self._idd_file_path,
'--expandobjects',
'--readvars',
'--output-prefix', f'{self._city.name}_',
self._output_file_path]
print(cmd)
subprocess.run(cmd, cwd=self._output_path)

View File

@ -501,7 +501,7 @@ class Idf:
) )
def _rename_building(self, city_name): def _rename_building(self, city_name):
name = str(str(city_name.encode("utf-8"))) name = str(city_name.encode("utf-8"))
for building in self._idf.idfobjects[self._BUILDING]: for building in self._idf.idfobjects[self._BUILDING]:
building.Name = f'Buildings in {name}' building.Name = f'Buildings in {name}'
building['Solar_Distribution'] = 'FullExterior' building['Solar_Distribution'] = 'FullExterior'
@ -586,8 +586,7 @@ class Idf:
if self._export_type == "Surfaces": if self._export_type == "Surfaces":
if is_target: if is_target:
if building.thermal_zones_from_internal_zones is not None: if building.thermal_zones_from_internal_zones is not None:
pass self._add_surfaces(building, building.name)
# self._add_surfaces(building, building.name)
else: else:
self._add_pure_geometry(building, building.name) self._add_pure_geometry(building, building.name)
else: else:
@ -733,13 +732,8 @@ class Idf:
self._idf.set_wwr(wwr) self._idf.set_wwr(wwr)
def _add_surfaces(self, building, zone_name): def _add_surfaces(self, building, zone_name):
start = datetime.datetime.now()
print(f'thermal_zones {len(building.thermal_zones_from_internal_zones)}')
for thermal_zone in building.thermal_zones_from_internal_zones: for thermal_zone in building.thermal_zones_from_internal_zones:
print(f'thermal zone: {datetime.datetime.now() - start}')
for index, boundary in enumerate(thermal_zone.thermal_boundaries): for index, boundary in enumerate(thermal_zone.thermal_boundaries):
print(f'{index} boundary: {datetime.datetime.now() - start}')
idf_surface_type = self.idf_surfaces[boundary.parent_surface.type] idf_surface_type = self.idf_surfaces[boundary.parent_surface.type]
outside_boundary_condition = 'Outdoors' outside_boundary_condition = 'Outdoors'
sun_exposure = 'SunExposed' sun_exposure = 'SunExposed'
@ -768,13 +762,9 @@ class Idf:
_kwargs['Construction_Name'] = construction_name _kwargs['Construction_Name'] = construction_name
start = datetime.datetime.now() start = datetime.datetime.now()
surface = self._idf.newidfobject(self._SURFACE, **_kwargs) surface = self._idf.newidfobject(self._SURFACE, **_kwargs)
print(f'create surface: {datetime.datetime.now() - start}')
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)
print(f'set coords surface: {datetime.datetime.now() - start}')
if self._lod >= 3: if self._lod >= 3:
for internal_zone in building.internal_zones: for internal_zone in building.internal_zones:
for thermal_zone in internal_zone.thermal_zones_from_internal_zones: for thermal_zone in internal_zone.thermal_zones_from_internal_zones:
@ -786,7 +776,7 @@ class Idf:
for surface in building.surfaces: for surface in building.surfaces:
if surface.type == cte.WALL: if surface.type == cte.WALL:
wwr = surface.associated_thermal_boundaries[0].window_ratio wwr = surface.associated_thermal_boundaries[0].window_ratio
self._idf.set_wwr(wwr, construction='window_construction_1') #self._idf.set_wwr(wwr, construction='window_construction_1')
def _add_windows_by_vertices(self, boundary): def _add_windows_by_vertices(self, boundary):
raise NotImplementedError raise NotImplementedError

View File

@ -13,7 +13,7 @@ SimulationControl,
1; !- Maximum Number of HVAC Sizing Simulation Passes 1; !- Maximum Number of HVAC Sizing Simulation Passes
Building, Building,
Buildings in b'Montreal', !- Name Buildings in #CITY#, !- Name
0, !- North Axis 0, !- North Axis
Suburbs, !- Terrain Suburbs, !- Terrain
0.04, !- Loads Convergence Tolerance Value 0.04, !- Loads Convergence Tolerance Value

View File

@ -1,6 +1,7 @@
import hub.helpers.constants as cte import hub.helpers.constants as cte
BUILDING_SURFACE = '\nBUILDINGSURFACE:DETAILED,\n' BUILDING_SURFACE = '\nBUILDINGSURFACE:DETAILED,\n'
WINDOW_SURFACE = '\nFENESTRATIONSURFACE:DETAILED,\n'
COMPACT_SCHEDULE = '\nSCHEDULE:COMPACT,\n' COMPACT_SCHEDULE = '\nSCHEDULE:COMPACT,\n'
FILE_SCHEDULE = '\nSCHEDULE:FILE,\n' FILE_SCHEDULE = '\nSCHEDULE:FILE,\n'
NOMASS_MATERIAL = '\nMATERIAL:NOMASS,\n' NOMASS_MATERIAL = '\nMATERIAL:NOMASS,\n'

View File

@ -18,10 +18,11 @@ class IdfBase:
'window_materials': str((output_path / 'window_materials.idf').resolve()), 'window_materials': str((output_path / 'window_materials.idf').resolve()),
'constructions': str((output_path / 'constructions.idf').resolve()), 'constructions': str((output_path / 'constructions.idf').resolve()),
'zones': str((output_path / 'zones.idf').resolve()), 'zones': str((output_path / 'zones.idf').resolve()),
'surfaces': str((output_path / 'surfaces.idf').resolve()),
'fenestration': str((output_path / 'fenestration.idf').resolve()),
'occupancy': str((output_path / 'occupancy.idf').resolve()), 'occupancy': str((output_path / 'occupancy.idf').resolve()),
'lighting': str((output_path / 'lights.idf').resolve()), 'lighting': str((output_path / 'lights.idf').resolve()),
'appliances': str((output_path / 'appliances.idf').resolve()), 'appliances': str((output_path / 'appliances.idf').resolve()),
'surfaces': str((output_path / 'surfaces.idf').resolve()),
'shading': str((output_path / 'shading.idf').resolve()), 'shading': str((output_path / 'shading.idf').resolve()),
'infiltration': str((output_path / 'infiltration.idf').resolve()), 'infiltration': str((output_path / 'infiltration.idf').resolve()),
'ventilation': str((output_path / 'ventilation.idf').resolve()), 'ventilation': str((output_path / 'ventilation.idf').resolve()),
@ -31,7 +32,7 @@ class IdfBase:
} }
self._files = {} self._files = {}
for key, value in self._file_paths.items(): for key, value in self._file_paths.items():
self._files[key] = open(value, 'w') self._files[key] = open(value, 'w', encoding='UTF-8')
self._idd_file_path = str(idd_file_path) self._idd_file_path = str(idd_file_path)
self._idf_file_path = str(idf_file_path) self._idf_file_path = str(idf_file_path)

View File

@ -1,5 +1,5 @@
from pathlib import Path from pathlib import Path
import hub.helpers.constants as cte
import hub.exports.building_energy.idf_helper as idf_cte import hub.exports.building_energy.idf_helper as idf_cte
from hub.exports.building_energy.idf_helper.idf_base import IdfBase from hub.exports.building_energy.idf_helper.idf_base import IdfBase
@ -14,8 +14,8 @@ class IdfFileSchedule(IdfBase):
file_name = str( file_name = str(
(Path(self._output_path) / f'{schedule_type} schedules {usage.replace("/", "_")}.csv').resolve()) (Path(self._output_path) / f'{schedule_type} schedules {usage.replace("/", "_")}.csv').resolve())
with open(file_name, 'w', encoding='utf8') as file: with open(file_name, 'w', encoding='utf8') as file:
for value in schedule.values: for value in schedule.values[0]:
file.write(f'{str(value)},\n') file.write(f'{value},\n')
file = self._files['file_schedules'] file = self._files['file_schedules']
self._write_to_idf_format(file, idf_cte.FILE_SCHEDULE) self._write_to_idf_format(file, idf_cte.FILE_SCHEDULE)
self._write_to_idf_format(file, schedule_name, 'Name') self._write_to_idf_format(file, schedule_name, 'Name')

View File

@ -6,7 +6,6 @@ from hub.exports.building_energy.idf_helper.idf_base import IdfBase
class IdfSurfaces(IdfBase): class IdfSurfaces(IdfBase):
@staticmethod @staticmethod
def add(self, building, zone_name): def add(self, building, zone_name):
# Verify if create building surfaces "by hand" it's faster wwr it's missing
zone_name = f'{zone_name}' zone_name = f'{zone_name}'
file = self._files['surfaces'] file = self._files['surfaces']
for thermal_zone in building.thermal_zones_from_internal_zones: for thermal_zone in building.thermal_zones_from_internal_zones:

View File

@ -1,9 +1,64 @@
import logging
import hub.exports.building_energy.idf_helper as idf_cte import hub.exports.building_energy.idf_helper as idf_cte
import hub.helpers.constants as cte import hub.helpers.constants as cte
from hub.exports.building_energy.idf_helper.idf_base import IdfBase from hub.exports.building_energy.idf_helper.idf_base import IdfBase
class IdfWindow(IdfBase): class IdfWindow(IdfBase):
@staticmethod @staticmethod
def add(self, building, zone_name): def _to_window_surface(self, surface):
pass window_ratio = surface.associated_thermal_boundaries[0].window_ratio
x = 0
y = 1
z = 2
coordinates = self._matrix_to_list(surface.solid_polygon.coordinates, self._city.lower_corner)
min_z = surface.lower_corner[z]
max_z = surface.upper_corner[z]
middle = (max_z - min_z) / 2
distance = (max_z - min_z) * window_ratio
new_max_z = middle + distance / 2
new_min_z = middle - distance / 2
for index, coordinate in enumerate(coordinates):
if coordinate[z] == max_z:
coordinates[index] = (coordinate[x], coordinate[y], new_max_z)
elif coordinate[z] == min_z:
coordinates[index] = (coordinate[x], coordinate[y], new_min_z)
else:
logging.warning('Z coordinate not in top or bottom during window creation')
return coordinates
@staticmethod
def add(self, building):
file = self._files['fenestration']
for thermal_zone in building.thermal_zones_from_internal_zones:
for index, boundary in enumerate(thermal_zone.thermal_boundaries):
building_surface_name = f'Building_{building.name}_surface_{index}'
is_exposed = boundary.parent_surface.type == cte.WALL
if boundary.parent_surface.percentage_shared is not None and boundary.parent_surface.percentage_shared > 0.5 or boundary.window_ratio == 0:
is_exposed = False
if not is_exposed:
continue
name = f'Building_{building.name}_window_{index}'
construction_name = f'{boundary.construction_name}_window_construction'
self._write_to_idf_format(file, idf_cte.WINDOW_SURFACE)
self._write_to_idf_format(file, name, 'Name')
self._write_to_idf_format(file, 'Window', 'Surface Type')
self._write_to_idf_format(file, construction_name, 'Construction Name')
self._write_to_idf_format(file, building_surface_name, 'Building Surface Name')
self._write_to_idf_format(file, idf_cte.EMPTY, 'Outside Boundary Condition Object')
self._write_to_idf_format(file, idf_cte.AUTOCALCULATE, 'View Factor to Ground')
self._write_to_idf_format(file, idf_cte.EMPTY, 'Frame and Divider Name')
self._write_to_idf_format(file, '1.0', 'Multiplier')
self._write_to_idf_format(file, idf_cte.AUTOCALCULATE, 'Number of Vertices')
coordinates = IdfWindow._to_window_surface(self, boundary.parent_surface)
eol = ','
coordinates_length = len(coordinates)
for i, coordinate in enumerate(coordinates):
vertex = i + 1
if vertex == coordinates_length:
eol = ';'
self._write_to_idf_format(file, coordinate[0], f'Vertex {vertex} Xcoordinate')
self._write_to_idf_format(file, coordinate[1], f'Vertex {vertex} Ycoordinate')
self._write_to_idf_format(file, coordinate[2], f'Vertex {vertex} Zcoordinate', eol)

View File

@ -4,14 +4,14 @@ from hub.exports.building_energy.idf_helper.idf_base import IdfBase
class IdfWindowsConstructions(IdfBase): class IdfWindowsConstructions(IdfBase):
@staticmethod @staticmethod
def add(self): def add(self, thermal_boundary):
glazing_index = self._windows_added_to_idf['glazing_index'] + 1 name = f'{thermal_boundary.construction_name}_window'
for i in range(1, glazing_index): if name not in self._windows_added_to_idf:
construction_name = f'window_construction_{i}' return # Material not added or already assigned to construction
material_name = f'glazing_{i}' construction_name = f'{thermal_boundary.construction_name}_window_construction'
if construction_name not in self._constructions_added_to_idf: if construction_name not in self._constructions_added_to_idf:
self._constructions_added_to_idf[construction_name] = True self._constructions_added_to_idf[construction_name] = True
file = self._files['constructions'] file = self._files['constructions']
self._write_to_idf_format(file, idf_cte.CONSTRUCTION) self._write_to_idf_format(file, idf_cte.CONSTRUCTION)
self._write_to_idf_format(file, construction_name, 'Name') self._write_to_idf_format(file, construction_name, 'Name')
self._write_to_idf_format(file, material_name, 'Outside Layer', ';') self._write_to_idf_format(file, name, 'Outside Layer', ';')

View File

@ -4,14 +4,12 @@ from hub.exports.building_energy.idf_helper.idf_base import IdfBase
class IdfWindowsMaterial(IdfBase): class IdfWindowsMaterial(IdfBase):
@staticmethod @staticmethod
def add(self, thermal_opening): def add(self, thermal_boundary, thermal_opening):
name = f'{thermal_opening.overall_u_value}_{thermal_opening.g_value}' name = f'{thermal_boundary.construction_name}_window'
if name not in self._windows_added_to_idf: if name not in self._windows_added_to_idf:
glazing_index = self._windows_added_to_idf['glazing_index'] + 1
self._windows_added_to_idf[name] = True self._windows_added_to_idf[name] = True
self._windows_added_to_idf['glazing_index'] = glazing_index # increase the count
file = self._files['window_materials'] file = self._files['window_materials']
self._write_to_idf_format(file, idf_cte.WINDOW_MATERIAL) self._write_to_idf_format(file, idf_cte.WINDOW_MATERIAL)
self._write_to_idf_format(file, f'glazing_{glazing_index}', 'Name') self._write_to_idf_format(file, name, 'Name')
self._write_to_idf_format(file, thermal_opening.overall_u_value, 'UFactor') self._write_to_idf_format(file, thermal_opening.overall_u_value, 'UFactor')
self._write_to_idf_format(file, thermal_opening.g_value, 'Solar Heat Gain Coefficient', ';') self._write_to_idf_format(file, thermal_opening.g_value, 'Solar Heat Gain Coefficient', ';')

View File

@ -128,15 +128,31 @@ class TestExports(TestCase):
""" """
export to IDF export to IDF
""" """
file = 'test.geojson' file = '1588_v1.geojson'
# file = 'test.geojson'
file_path = (self._example_path / file).resolve() file_path = (self._example_path / file).resolve()
"""
city = GeometryFactory('geojson', city = GeometryFactory('geojson',
path=file_path, path=file_path,
height_field='citygml_me', height_field='citygml_me',
year_of_construction_field='ANNEE_CONS', year_of_construction_field='ANNEE_CONS',
function_field='CODE_UTILI', function_field='CODE_UTILI',
function_to_hub=Dictionaries().montreal_function_to_hub_function).city function_to_hub=Dictionaries().montreal_function_to_hub_function).city
"""
"""=-043
"name": "01043081",
"address": "avenue de l' H\u00f4tel-de-Ville (MTL) 3751",
"function": "6911",
"height": 21,
"year_of_construction": 1964,
"""
city = GeometryFactory('geojson',
path=file_path,
height_field='height',
year_of_construction_field='year_of_construction',
function_field='function',
function_to_hub=Dictionaries().montreal_function_to_hub_function).city
# """
self.assertIsNotNone(city, 'city is none') self.assertIsNotNone(city, 'city is none')
#EnergyBuildingsExportsFactory('idf', city, self._output_path).export() #EnergyBuildingsExportsFactory('idf', city, self._output_path).export()
ConstructionFactory('nrcan', city).enrich() ConstructionFactory('nrcan', city).enrich()
@ -144,7 +160,8 @@ class TestExports(TestCase):
UsageFactory('nrcan', city).enrich() UsageFactory('nrcan', city).enrich()
WeatherFactory('epw', city).enrich() WeatherFactory('epw', city).enrich()
try: try:
EnergyBuildingsExportsFactory('cerc_idf', city, self._output_path)._cerc_idf idf = EnergyBuildingsExportsFactory('cerc_idf', city, self._output_path).export()
idf.run()
except Exception: except Exception:
self.fail("Idf ExportsFactory raised ExceptionType unexpectedly!") self.fail("Idf ExportsFactory raised ExceptionType unexpectedly!")