Compare commits

...

15 Commits

10 changed files with 516 additions and 103851 deletions

1
.gitignore vendored
View File

@ -10,3 +10,4 @@
**/__pycache__/
**/.idea/
cerc_hub.egg-info
dist

View File

@ -190,7 +190,7 @@ class ComnetCatalog(Catalog):
schedules_key = {}
for j in range(0, number_usage_types-1):
usage_parameters = _extracted_data.iloc[j]
usage_type = usage_parameters[0]
usage_type = usage_parameters.iloc[0]
lighting_data[usage_type] = usage_parameters[1:6].values.tolist()
plug_loads_data[usage_type] = usage_parameters[8:13].values.tolist()
occupancy_data[usage_type] = usage_parameters[17:20].values.tolist()

View File

@ -359,6 +359,8 @@ class City:
Get city latitude in degrees
:return: None or float
"""
if self._latitude is None:
return self._get_location().climate_reference_city_latitude
return self._latitude
@latitude.setter
@ -376,6 +378,8 @@ class City:
Get city longitude in degrees
:return: None or float
"""
if self._longitude is None:
return self._get_location().climate_reference_city_longitude
return self._longitude
@longitude.setter

File diff suppressed because it is too large Load Diff

View File

@ -1,157 +0,0 @@
! Minimal.idf
! Basic file description: This is a minimal configuration necessary to run.
! Highlights: Illustrates minimal items necessary to perform run.
! BUILDING, SURFACEGEOMETRY, LOCATION and DESIGNDAY (or RUNPERIOD) are the absolute minimal required input objects.
! TIME STEP IN HOUR is included so as to not get warning error.
! Including two design days, Run Control object and RunPeriod to facilitate use.
! Although not incredibly useful, this could be used as a weather/solar calculator.
! Simulation Location/Run: Denver is included. Any could be used.
! Building: None.
!
! Internal gains description: None.
!
! HVAC: None.
!
Version,9.5;
Timestep,4;
Building,
None, !- Name
0.0000000E+00, !- North Axis {deg}
Suburbs, !- Terrain
0.04, !- Loads Convergence Tolerance Value {W}
0.40, !- Temperature Convergence Tolerance Value {deltaC}
FullInteriorAndExterior, !- Solar Distribution
25, !- Maximum Number of Warmup Days
6; !- Minimum Number of Warmup Days
GlobalGeometryRules,
UpperLeftCorner, !- Starting Vertex Position
CounterClockWise, !- Vertex Entry Direction
World; !- Coordinate System
Site:Location,
DENVER_STAPLETON_CO_USA_WMO_724690, !- Name
39.77, !- Latitude {deg}
-104.87, !- Longitude {deg}
-7.00, !- Time Zone {hr}
1611.00; !- Elevation {m}
! DENVER_STAPLETON_CO_USA Annual Heating Design Conditions Wind Speed=2.3m/s Wind Dir=180
! Coldest Month=December
! DENVER_STAPLETON_CO_USA Annual Heating 99.6%, MaxDB=-20°C
SizingPeriod:DesignDay,
DENVER_STAPLETON Ann Htg 99.6% Condns DB, !- Name
12, !- Month
21, !- Day of Month
WinterDesignDay, !- Day Type
-20, !- Maximum Dry-Bulb Temperature {C}
0.0, !- Daily Dry-Bulb Temperature Range {deltaC}
, !- Dry-Bulb Temperature Range Modifier Type
, !- Dry-Bulb Temperature Range Modifier Day Schedule Name
Wetbulb, !- Humidity Condition Type
-20, !- Wetbulb or DewPoint at Maximum Dry-Bulb {C}
, !- Humidity Condition Day Schedule Name
, !- Humidity Ratio at Maximum Dry-Bulb {kgWater/kgDryAir}
, !- Enthalpy at Maximum Dry-Bulb {J/kg}
, !- Daily Wet-Bulb Temperature Range {deltaC}
83411., !- Barometric Pressure {Pa}
2.3, !- Wind Speed {m/s}
180, !- Wind Direction {deg}
No, !- Rain Indicator
No, !- Snow Indicator
No, !- Daylight Saving Time Indicator
ASHRAEClearSky, !- Solar Model Indicator
, !- Beam Solar Day Schedule Name
, !- Diffuse Solar Day Schedule Name
, !- ASHRAE Clear Sky Optical Depth for Beam Irradiance (taub) {dimensionless}
, !- ASHRAE Clear Sky Optical Depth for Diffuse Irradiance (taud) {dimensionless}
0.00; !- Sky Clearness
! DENVER_STAPLETON Annual Cooling Design Conditions Wind Speed=4m/s Wind Dir=120
! Hottest Month=July
! DENVER_STAPLETON_CO_USA Annual Cooling (DB=>MWB) .4%, MaxDB=34.1°C MWB=15.8°C
SizingPeriod:DesignDay,
DENVER_STAPLETON Ann Clg .4% Condns DB=>MWB, !- Name
7, !- Month
21, !- Day of Month
SummerDesignDay, !- Day Type
34.1, !- Maximum Dry-Bulb Temperature {C}
15.2, !- Daily Dry-Bulb Temperature Range {deltaC}
, !- Dry-Bulb Temperature Range Modifier Type
, !- Dry-Bulb Temperature Range Modifier Day Schedule Name
Wetbulb, !- Humidity Condition Type
15.8, !- Wetbulb or DewPoint at Maximum Dry-Bulb {C}
, !- Humidity Condition Day Schedule Name
, !- Humidity Ratio at Maximum Dry-Bulb {kgWater/kgDryAir}
, !- Enthalpy at Maximum Dry-Bulb {J/kg}
, !- Daily Wet-Bulb Temperature Range {deltaC}
83411., !- Barometric Pressure {Pa}
4, !- Wind Speed {m/s}
120, !- Wind Direction {deg}
No, !- Rain Indicator
No, !- Snow Indicator
No, !- Daylight Saving Time Indicator
ASHRAEClearSky, !- Solar Model Indicator
, !- Beam Solar Day Schedule Name
, !- Diffuse Solar Day Schedule Name
, !- ASHRAE Clear Sky Optical Depth for Beam Irradiance (taub) {dimensionless}
, !- ASHRAE Clear Sky Optical Depth for Diffuse Irradiance (taud) {dimensionless}
1.00; !- Sky Clearness
RunPeriod,
Run Period 1, !- Name
1, !- Begin Month
1, !- Begin Day of Month
, !- Begin Year
12, !- End Month
31, !- End Day of Month
, !- End Year
Tuesday, !- Day of Week for Start Day
Yes, !- Use Weather File Holidays and Special Days
Yes, !- Use Weather File Daylight Saving Period
No, !- Apply Weekend Holiday Rule
Yes, !- Use Weather File Rain Indicators
Yes; !- Use Weather File Snow Indicators
SimulationControl,
No, !- Do Zone Sizing Calculation
No, !- Do System Sizing Calculation
No, !- Do Plant Sizing Calculation
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:Table:SummaryReports, AnnualBuildingUtilityPerformanceSummary,
DemandEndUseComponentsSummary,
SensibleHeatGainSummary,
InputVerificationandResultsSummary,
AdaptiveComfortSummary,
Standard62.1Summary,
ClimaticDataSummary,
EquipmentSummary,
EnvelopeSummary,
LightingSummary,
HVACSizingSummary,
SystemSummary,
ComponentSizingSummary,
OutdoorAirSummary,
ObjectCountSummary,
EndUseEnergyConsumptionOtherFuelsMonthly,
PeakEnergyEndUseOtherFuelsMonthly;
OutputControl:Table:Style, CommaAndHTML,JtoKWH;
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

View File

@ -0,0 +1,317 @@
"""
export a city into B18 (TRNSYS 18) format
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
"""
import json
import math
from datetime import datetime
from pathlib import Path
from urllib.error import URLError
from urllib.request import urlopen
import pytz
from timezonefinder import TimezoneFinder
from hub.version import __version__
import hub.helpers.constants as cte
class Trnsys:
"""
TRNSYS class
"""
def __init__(self, city, path, target_buildings=None):
self._city = city
self._path = path
self._buildings = self._city.buildings
self._tf = TimezoneFinder()
if target_buildings is not None:
buildings = []
for building in target_buildings:
buildings.append(self._city.city_object(building))
self._buildings = buildings
print("trnsys constructor completed")
self.export()
def _header(self, f, building):
f.write('***************************************\n')
f.write(f'* Cerc Hub {__version__}\n')
f.write('***************************************\n')
f.write('* Building descriptions file TRNSYS\n')
location = (
self._city.latitude, self._city.longitude
)
f.write(f'* Export: {self._city.location.city} {self._city.location.country} {location}\n')
f.write(f'* For building: {building.name}')
if building.aliases is not None:
f.write(f' {building.aliases})')
f.write('\n')
f.write(f'* Total floors: {building.storeys_above_ground}\n')
f.write(f'* Year build: {building.year_of_construction}\n')
f.write('***************************************\n')
def _properties(self, f):
latitude = self._city.latitude
longitude = self._city.longitude
utc_offset = datetime.now(
pytz.timezone(self._tf.timezone_at(lng=longitude, lat=latitude))
).utcoffset().total_seconds()/60/60
elevation = 200
try:
elevation = json.loads(
urlopen(f'https://api.open-elevation.com/api/v1/lookup?locations={latitude},{longitude}').read()
)['results'][0]['elevation']
except URLError:
pass
f.write('***************************************\n')
f.write(f'* Properties\n')
f.write('***************************************\n')
f.write('PROPERTIES\n')
# todo: from where does this values come from?
f.write(f' DENSITY=1.204 : CAPACITY=1.012 : PRESSURE=101325.000 : HVAPOR=2454.0 : SIGMA=2.041e-007 : RTEMP=293.15\n')
f.write('* Convective heat transfer coefficient calculation\n')
f.write(' KFLOORUP=7.2 : EFLOORUP=0.31 : KFLOORDOWN=3.888 : EFLOORDOWN=0.31\n')
f.write(' KCEILUP=7.2 : ECEILUP=0.31 : KCEILDOWN=3.888 : ECEILDOWN=0.31\n')
f.write(' KVERTICAL=5.76 : EVERTICAL=0.3\n')
f.write('* Radiance parameters\n')
f.write(' SCENE_ROTATION_ANGLE=0 : GROUND_IDS= : GROUND_REFLECTANCE=0.2 : SHADER_REFLECTANCE=0\n')
f.write(f' CALC_MODE=RAD : LATITUDE={latitude} : LONGITUDE={longitude} : TIME_ZONE={utc_offset} : SITE_ELEVATION={elevation}\n')
f.write(' AB=5 : AD=1000 : AS=20 : AR=300 : AA=0.1\n')
f.write(' LR=6 : ST=0.15 : SJ=1 : LW=0.004 : DJ=0 : DS=0.2 : DR=2 : DP=512\n')
f.write('* Comfort parameters\n')
f.write(' DIAM-SENSOR=0.07 : EPS-SENSOR=0.82 : REFL-SENSOR=0.47 : ELV_AIRSPEED1=0.3 : ELV_AIRSPEED2=0.7 : ELV_AIRSPEED3=1.2\n')
f.write('* Other parameters\n')
f.write(' FSCAL_TREGENZA=MEDIUM : SHM_MODE=0 : SURFGRID=0.2\n')
f.write('* Daylight parameters\n')
f.write(' UDIMIN=100 : UDIMAX=2000 : DAMIN=300\n')
@staticmethod
def _types(f, building):
f.write('***************************************\n')
f.write(f'* Types\n')
f.write('***************************************\n')
f.write(f'* Layers\n')
f.write('***************************************\n')
layers = {}
for thermal_zone in building.thermal_zones_from_internal_zones:
for thermal_boundary in thermal_zone.thermal_boundaries:
for layer in thermal_boundary.layers:
capacity = 0
conductivity = 0
density = 0
if layer.thermal_absorptance is not None:
capacity = layer.thermal_absorptance
if layer.conductivity is not None:
conductivity = layer.conductivity
if layer.density is not None:
density = layer.density
layers[layer.material_name] = {
'conductivity': conductivity,
'capacity': capacity, # todo: transform units
'density': density,
'pert': '0',
'penrt': '0'
}
f.write('PROPERTIES\n')
for name, values in layers.items():
f.write(f'LAYER {name.replace(" ", "_").upper()}\n')
for attribute, value in values.items():
f.write(f'{attribute.upper()}=\t{value}\t')
f.write('\n')
@staticmethod
def _inputs(f):
f.write('***************************************\n')
f.write(f'* Inputs\n')
f.write('***************************************\n')
f.write('INPUTS TGROUND TBOUNDARY SHADE_CLOSE SHADE_OPEN MAX_ISHADE MAX_ESHADE\n')
f.write('INPUTS_DESCRIPTION\n')
f.write('TGROUND : C : Ground Temperature (boundary temperature used for floors adjacent to the ground)\n')
f.write('TBOUNDARY : C : Boundary Temperature (boundary temperature used for boundary floors, walls, ceilings)\n')
f.write('SHADE_CLOSE : kJ/hr.m^2 : threshold of total radiation on facade where shading device is activated\n')
f.write('SHADE_OPEN : kJ/hr.m^2 : threshold of total radiation on facade where shading device is deactivated\n')
f.write('MAX_ISHADE : any : max shading factor of internal shading\n')
f.write('MAX_ESHADE : any : max shading factor of external shading\n')
@staticmethod
def _add_schedule(f, schedule, name):
if schedule.time_step is cte.HOUR:
for day_type in schedule.day_types:
f.write(f'{name} {day_type.upper()}\n')
hours = 'HOURS= '
values = 'VALUES= '
previous = math.inf
for hour, value in enumerate(schedule.values):
if previous != value:
hours = f'{hours} {hour}.000'
values = f'{values} {value}'
previous = value
f.write(f'{hours} 24.0\n')
f.write(f'{values} {schedule.values[0]}\n')
@staticmethod
def _schedules(f, building):
f.write('***************************************\n')
f.write(f'* Schedules\n')
f.write('***************************************\n')
for thermal_zone in building.thermal_zones_from_internal_zones:
for usage in thermal_zone.usages:
for schedule in usage.occupancy.occupancy_schedules:
Trnsys._add_schedule(f, schedule, 'SCHEDULE OCCUPANCY')
for schedule in usage.lighting.schedules:
Trnsys._add_schedule(f, schedule, 'SCHEDULE LIGHTING ')
@staticmethod
def _constructions(f, building):
f.write('***************************************\n')
f.write(f'* Construction\n')
f.write('***************************************\n')
constructions = {}
for thermal_zone in building.thermal_zones_from_internal_zones:
for thermal_boundary in thermal_zone.thermal_boundaries:
constructions[thermal_boundary.construction_name] = {
'layers': '',
'thickness': '',
'absortance-front': thermal_boundary.layers[0].solar_absorptance,
'absortance-back': thermal_boundary.layers[-1].solar_absorptance,
'resistance-front': 1 - thermal_boundary.layers[0].thermal_absorptance,
'resistance-back': 1 - thermal_boundary.layers[-1].thermal_absorptance,
'he': thermal_boundary.he,
'hi': thermal_boundary.hi
}
for layer in thermal_boundary.layers:
constructions[thermal_boundary.construction_name]['layers'] = f'{constructions[thermal_boundary.construction_name]["layers"]} {layer.material_name.replace(" ", "_").upper()} '
constructions[thermal_boundary.construction_name]['thickness'] = f'{constructions[thermal_boundary.construction_name]["thickness"]} {layer.thickness} '
for name, values in constructions.items():
f.write(f'CONSTRUCTION {name.replace(" ", "_").upper()}\n')
f.write(f'LAYERS = {values["layers"]}\n')
f.write(f'THICKNESS = {values["thickness"]}\n')
f.write(f'ABS-FRONT = {values["absortance-front"]} : ABS-BACK = {values["absortance-back"]}\n')
f.write(f'EPS-FRONT = {values["resistance-front"]} : EPS-BACK = {values["resistance-back"]}\n')
f.write(f'HFRONT = {values["he"]} : HBACK = {values["hi"]}\n')
f.write('\n')
@staticmethod
def _windows(f, building):
f.write('***************************************\n')
f.write(f'* Windows\n')
f.write('***************************************\n')
windows = {}
for thermal_zone in building.thermal_zones_from_internal_zones:
for thermal_boundary in thermal_zone.thermal_boundaries:
polygon = thermal_boundary.parent_surface.solid_polygon
min_z = math.inf
max_z = -math.inf
Z = 2
for point in polygon.points:
min_z = min(min_z, point.coordinates[Z])
max_z = max(max_z, point.coordinates[Z])
height = max_z - min_z
if height == 0:
height = 1
for opening_id, thermal_opening in enumerate(thermal_boundary.thermal_openings):
absortance = 0.6
if thermal_opening.conductivity is not None:
absortance = 1 - thermal_opening.conductivity
windows[f'{thermal_opening.construction_name}'] = {
'window_id': opening_id,
'hi': thermal_opening.hi,
'he': thermal_opening.he,
'slope': -999,
'space_id': 3,
'width': thermal_opening.area / height, # todo: this is just an estimation as the surfaces may not be squares.
'height': height,
'frame_f': 0,
'frame_u': thermal_opening.overall_u_value,
'abs_frame': absortance,
'ri_shade': 0,
're_shade': 0,
're_fli_shade': 0.5,
're_flo_shade': 0.5,
'cci_shade': 0.5,
'eps_frame': 0.9,
'epsi_shade': 0.9,
'it_shade_close': 0,
'it_shade_open': 0,
'flow_to_air_node': 1,
'pert': 0,
'penrt': 0,
'rad_material': 'undefined',
'rad_material_shd1': 'undefined'
}
for name, values in windows.items():
f.write(f'WINDOW {name.replace(" ", "_").upper()}\n')
f.write(f' WINID={values["window_id"]} :')
f.write(f' HINSIDE={values["hi"]} :')
f.write(f' HOUTSIDE={values["he"]} :')
f.write(f' SLOPE={values["slope"]} :')
f.write(f' SPACID={values["space_id"]} :')
f.write(f' WWID={values["width"]} :')
f.write(f' WHEIG={values["height"]} :')
f.write(f' FFRAME={values["frame_f"]} :')
f.write(f' UFRAME={values["frame_u"]} :')
f.write(f' ABSFRAME={values["abs_frame"]} :')
f.write(f' RISHADE={values["ri_shade"]} :')
f.write(f' RESHADE={values["re_shade"]} :')
f.write(f' REFLISHADE={values["re_fli_shade"]} :')
f.write(f' REFLOSHADE={values["re_flo_shade"]} :')
f.write(f' CCISHADE={values["cci_shade"]} :')
f.write(f' EPSFRAME={values["eps_frame"]} :')
f.write(f' EPSIFRAME={values["epsi_shade"]} :')
f.write(f' ITSHADECLOSE={values["it_shade_close"]} :')
f.write(f' ITSHADEOPEN={values["it_shade_open"]} :')
f.write(f' FLOWTOAIRNODE={values["flow_to_air_node"]} :')
f.write(f' PERT={values["pert"]} :')
f.write(f' PENRT={values["penrt"]} :')
f.write(f' RADMATERIAL={values["rad_material"]} :')
f.write(f' RADMATERIAL_SHD1={values["rad_material_shd1"]}\n')
@staticmethod
def _empty_sections(f):
f.write('***************************************\n')
f.write(f'* Gains / Losses\n')
f.write('***************************************\n')
f.write('***************************************\n')
f.write(f'* Confort\n')
f.write('***************************************\n')
f.write('***************************************\n')
f.write(f'* Infiltration\n')
f.write('***************************************\n')
f.write('***************************************\n')
f.write(f'* Ventilation\n')
f.write('***************************************\n')
f.write('***************************************\n')
f.write(f'* Cooling\n')
f.write('***************************************\n')
f.write('***************************************\n')
f.write(f'* Heating\n')
f.write('***************************************\n')
f.write('***************************************\n')
f.write(f'* Daylight control\n')
f.write('***************************************\n')
f.write('***************************************\n')
f.write(f'* Confort\n')
f.write('***************************************\n')
def export(self):
for building in self._buildings:
path = Path(self._path / f'{building.name}.b18')
with open(path, 'w', encoding='utf-8') as f:
print(path)
self._header(f, building)
self._properties(f)
Trnsys._types(f, building)
Trnsys._inputs(f)
Trnsys._schedules(f, building)
Trnsys._constructions(f, building)
Trnsys._windows(f, building)
Trnsys._empty_sections(f)

View File

@ -12,6 +12,7 @@ import requests
from hub.exports.building_energy.energy_ade import EnergyAde
from hub.exports.building_energy.idf import Idf
from hub.exports.building_energy.insel.insel_monthly_energy_balance import InselMonthlyEnergyBalance
from hub.exports.building_energy.trnsys import Trnsys
from hub.helpers.utils import validate_import_export_type
from hub.imports.weather.helpers.weather import Weather as wh
@ -60,6 +61,17 @@ class EnergyBuildingsExportsFactory:
return Idf(self._city, self._path, (idf_data_path / 'Minimal.idf'), (idf_data_path / 'Energy+.idd'), weather_path,
target_buildings=self._target_buildings)
@property
def _trnsys(self):
"""
Export the city to TRNSYS 18 format
Currently only b18 files will be generated
:return: None
"""
return Trnsys(self._city, self._path, target_buildings=self._target_buildings)
@property
def _insel_monthly_energy_balance(self):
"""

View File

@ -24,3 +24,4 @@ triangle
psycopg2-binary
Pillow
pathlib
timezonefinder

View File

@ -147,3 +147,22 @@ class TestExports(TestCase):
EnergyBuildingsExportsFactory('idf', city, self._output_path).export()
except Exception:
self.fail("Idf ExportsFactory raised ExceptionType unexpectedly!")
def test_trnsys_export(self):
"""
export to Trnsys
"""
file = 'test.geojson'
file_path = (self._example_path / file).resolve()
city = GeometryFactory('geojson',
path=file_path,
height_field='citygml_me',
year_of_construction_field='ANNEE_CONS',
function_field='CODE_UTILI',
function_to_hub=Dictionaries().montreal_function_to_hub_function).city
self.assertIsNotNone(city, 'city is none')
ConstructionFactory('nrcan', city).enrich()
UsageFactory('nrcan', city).enrich()
WeatherFactory('epw', city).enrich()
EnergyBuildingsExportsFactory('trnsys', city, self._output_path).export()