honeybee-energy 1.116.106__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- honeybee_energy/__init__.py +24 -0
- honeybee_energy/__main__.py +4 -0
- honeybee_energy/_extend_honeybee.py +145 -0
- honeybee_energy/altnumber.py +21 -0
- honeybee_energy/baseline/__init__.py +2 -0
- honeybee_energy/baseline/create.py +608 -0
- honeybee_energy/baseline/data/__init__.py +1 -0
- honeybee_energy/baseline/data/constructions.csv +64 -0
- honeybee_energy/baseline/data/fen_ratios.csv +15 -0
- honeybee_energy/baseline/data/lpd_building.csv +21 -0
- honeybee_energy/baseline/data/pci_2016.csv +22 -0
- honeybee_energy/baseline/data/pci_2019.csv +22 -0
- honeybee_energy/baseline/data/pci_2022.csv +22 -0
- honeybee_energy/baseline/data/shw.csv +21 -0
- honeybee_energy/baseline/pci.py +512 -0
- honeybee_energy/baseline/result.py +371 -0
- honeybee_energy/boundarycondition.py +128 -0
- honeybee_energy/cli/__init__.py +69 -0
- honeybee_energy/cli/baseline.py +475 -0
- honeybee_energy/cli/edit.py +327 -0
- honeybee_energy/cli/lib.py +1154 -0
- honeybee_energy/cli/result.py +810 -0
- honeybee_energy/cli/setconfig.py +124 -0
- honeybee_energy/cli/settings.py +569 -0
- honeybee_energy/cli/simulate.py +380 -0
- honeybee_energy/cli/translate.py +1714 -0
- honeybee_energy/cli/validate.py +224 -0
- honeybee_energy/config.json +11 -0
- honeybee_energy/config.py +842 -0
- honeybee_energy/construction/__init__.py +1 -0
- honeybee_energy/construction/_base.py +374 -0
- honeybee_energy/construction/air.py +325 -0
- honeybee_energy/construction/dictutil.py +89 -0
- honeybee_energy/construction/dynamic.py +607 -0
- honeybee_energy/construction/opaque.py +460 -0
- honeybee_energy/construction/shade.py +319 -0
- honeybee_energy/construction/window.py +1096 -0
- honeybee_energy/construction/windowshade.py +847 -0
- honeybee_energy/constructionset.py +1655 -0
- honeybee_energy/dictutil.py +56 -0
- honeybee_energy/generator/__init__.py +5 -0
- honeybee_energy/generator/loadcenter.py +204 -0
- honeybee_energy/generator/pv.py +535 -0
- honeybee_energy/hvac/__init__.py +21 -0
- honeybee_energy/hvac/_base.py +124 -0
- honeybee_energy/hvac/_template.py +270 -0
- honeybee_energy/hvac/allair/__init__.py +22 -0
- honeybee_energy/hvac/allair/_base.py +349 -0
- honeybee_energy/hvac/allair/furnace.py +168 -0
- honeybee_energy/hvac/allair/psz.py +131 -0
- honeybee_energy/hvac/allair/ptac.py +163 -0
- honeybee_energy/hvac/allair/pvav.py +109 -0
- honeybee_energy/hvac/allair/vav.py +128 -0
- honeybee_energy/hvac/detailed.py +337 -0
- honeybee_energy/hvac/doas/__init__.py +28 -0
- honeybee_energy/hvac/doas/_base.py +345 -0
- honeybee_energy/hvac/doas/fcu.py +127 -0
- honeybee_energy/hvac/doas/radiant.py +329 -0
- honeybee_energy/hvac/doas/vrf.py +81 -0
- honeybee_energy/hvac/doas/wshp.py +91 -0
- honeybee_energy/hvac/heatcool/__init__.py +23 -0
- honeybee_energy/hvac/heatcool/_base.py +177 -0
- honeybee_energy/hvac/heatcool/baseboard.py +61 -0
- honeybee_energy/hvac/heatcool/evapcool.py +72 -0
- honeybee_energy/hvac/heatcool/fcu.py +92 -0
- honeybee_energy/hvac/heatcool/gasunit.py +53 -0
- honeybee_energy/hvac/heatcool/radiant.py +269 -0
- honeybee_energy/hvac/heatcool/residential.py +77 -0
- honeybee_energy/hvac/heatcool/vrf.py +54 -0
- honeybee_energy/hvac/heatcool/windowac.py +70 -0
- honeybee_energy/hvac/heatcool/wshp.py +62 -0
- honeybee_energy/hvac/idealair.py +699 -0
- honeybee_energy/internalmass.py +310 -0
- honeybee_energy/lib/__init__.py +1 -0
- honeybee_energy/lib/_loadconstructions.py +194 -0
- honeybee_energy/lib/_loadconstructionsets.py +117 -0
- honeybee_energy/lib/_loadmaterials.py +83 -0
- honeybee_energy/lib/_loadprogramtypes.py +125 -0
- honeybee_energy/lib/_loadschedules.py +87 -0
- honeybee_energy/lib/_loadtypelimits.py +64 -0
- honeybee_energy/lib/constructions.py +207 -0
- honeybee_energy/lib/constructionsets.py +95 -0
- honeybee_energy/lib/materials.py +67 -0
- honeybee_energy/lib/programtypes.py +125 -0
- honeybee_energy/lib/schedules.py +61 -0
- honeybee_energy/lib/scheduletypelimits.py +31 -0
- honeybee_energy/load/__init__.py +1 -0
- honeybee_energy/load/_base.py +190 -0
- honeybee_energy/load/daylight.py +397 -0
- honeybee_energy/load/dictutil.py +47 -0
- honeybee_energy/load/equipment.py +771 -0
- honeybee_energy/load/hotwater.py +543 -0
- honeybee_energy/load/infiltration.py +460 -0
- honeybee_energy/load/lighting.py +480 -0
- honeybee_energy/load/people.py +497 -0
- honeybee_energy/load/process.py +472 -0
- honeybee_energy/load/setpoint.py +816 -0
- honeybee_energy/load/ventilation.py +550 -0
- honeybee_energy/material/__init__.py +1 -0
- honeybee_energy/material/_base.py +166 -0
- honeybee_energy/material/dictutil.py +59 -0
- honeybee_energy/material/frame.py +367 -0
- honeybee_energy/material/gas.py +1087 -0
- honeybee_energy/material/glazing.py +854 -0
- honeybee_energy/material/opaque.py +1351 -0
- honeybee_energy/material/shade.py +1360 -0
- honeybee_energy/measure.py +472 -0
- honeybee_energy/programtype.py +723 -0
- honeybee_energy/properties/__init__.py +1 -0
- honeybee_energy/properties/aperture.py +333 -0
- honeybee_energy/properties/door.py +342 -0
- honeybee_energy/properties/extension.py +244 -0
- honeybee_energy/properties/face.py +274 -0
- honeybee_energy/properties/model.py +2640 -0
- honeybee_energy/properties/room.py +1747 -0
- honeybee_energy/properties/shade.py +314 -0
- honeybee_energy/properties/shademesh.py +262 -0
- honeybee_energy/reader.py +48 -0
- honeybee_energy/result/__init__.py +1 -0
- honeybee_energy/result/colorobj.py +648 -0
- honeybee_energy/result/emissions.py +290 -0
- honeybee_energy/result/err.py +101 -0
- honeybee_energy/result/eui.py +100 -0
- honeybee_energy/result/generation.py +160 -0
- honeybee_energy/result/loadbalance.py +890 -0
- honeybee_energy/result/match.py +202 -0
- honeybee_energy/result/osw.py +90 -0
- honeybee_energy/result/rdd.py +59 -0
- honeybee_energy/result/zsz.py +190 -0
- honeybee_energy/run.py +1577 -0
- honeybee_energy/schedule/__init__.py +1 -0
- honeybee_energy/schedule/day.py +626 -0
- honeybee_energy/schedule/dictutil.py +59 -0
- honeybee_energy/schedule/fixedinterval.py +1012 -0
- honeybee_energy/schedule/rule.py +619 -0
- honeybee_energy/schedule/ruleset.py +1867 -0
- honeybee_energy/schedule/typelimit.py +310 -0
- honeybee_energy/shw.py +315 -0
- honeybee_energy/simulation/__init__.py +1 -0
- honeybee_energy/simulation/control.py +214 -0
- honeybee_energy/simulation/daylightsaving.py +185 -0
- honeybee_energy/simulation/dictutil.py +51 -0
- honeybee_energy/simulation/output.py +646 -0
- honeybee_energy/simulation/parameter.py +606 -0
- honeybee_energy/simulation/runperiod.py +443 -0
- honeybee_energy/simulation/shadowcalculation.py +295 -0
- honeybee_energy/simulation/sizing.py +546 -0
- honeybee_energy/ventcool/__init__.py +5 -0
- honeybee_energy/ventcool/_crack_data.py +91 -0
- honeybee_energy/ventcool/afn.py +289 -0
- honeybee_energy/ventcool/control.py +269 -0
- honeybee_energy/ventcool/crack.py +126 -0
- honeybee_energy/ventcool/fan.py +493 -0
- honeybee_energy/ventcool/opening.py +365 -0
- honeybee_energy/ventcool/simulation.py +314 -0
- honeybee_energy/writer.py +1078 -0
- honeybee_energy-1.116.106.dist-info/METADATA +113 -0
- honeybee_energy-1.116.106.dist-info/RECORD +162 -0
- honeybee_energy-1.116.106.dist-info/WHEEL +5 -0
- honeybee_energy-1.116.106.dist-info/entry_points.txt +2 -0
- honeybee_energy-1.116.106.dist-info/licenses/LICENSE +661 -0
- honeybee_energy-1.116.106.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
"""Utilities for matching Model geometry with energy simulation results."""
|
|
3
|
+
from __future__ import division
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
|
|
7
|
+
from honeybee.typing import clean_ep_string
|
|
8
|
+
from honeybee.door import Door
|
|
9
|
+
from honeybee.aperture import Aperture
|
|
10
|
+
from honeybee.face import Face
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def match_rooms_to_data(
|
|
14
|
+
data_collections, rooms, invert_multiplier=False, space_based=False,
|
|
15
|
+
zone_correct_mult=True):
|
|
16
|
+
"""Match honeybee Rooms to the Zone-level data collections from SQLiteResult.
|
|
17
|
+
|
|
18
|
+
This method ensures that Room multipliers are correctly output for a given
|
|
19
|
+
EnergyPlus output.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
data_collections: An array of data collections that will matched to
|
|
23
|
+
simulation results. Data collections can be of any class (eg.
|
|
24
|
+
MonthlyCollection, DailyCollection) but they should all have headers
|
|
25
|
+
with metadata dictionaries with 'Zone' or 'System' keys. These keys
|
|
26
|
+
will be used to match the data in the collections to the input rooms.
|
|
27
|
+
rooms: An array of honeybee Rooms, which will be matched to the data_collections.
|
|
28
|
+
The length of these Rooms does not have to match the data_collections.
|
|
29
|
+
invert_multiplier: Boolean to note whether the output room multiplier should be
|
|
30
|
+
included when the data type values already account for the multiplier
|
|
31
|
+
(False) or when they do not (True). (Default: False).
|
|
32
|
+
space_based: Boolean to note whether the result is reported on the EnergyPlus
|
|
33
|
+
Space level instead of the Zone level. In this case, the matching to
|
|
34
|
+
the Room will account for the fact that the Space name is the Room
|
|
35
|
+
name with _Space added to it. (Default: False).
|
|
36
|
+
zone_correct_mult: Boolean to note whether the multiplier in the returned result
|
|
37
|
+
should be divided by the number of Rooms within each zone when
|
|
38
|
+
space_based is False. This is useful for ensuring that, overall,
|
|
39
|
+
values reported on the zone level and matched to Rooms are not counted
|
|
40
|
+
more than once for zones with multiple Rooms. Essentially, multiplying
|
|
41
|
+
each Room data collection by the multiplier before summing results
|
|
42
|
+
together ensures that the final summed result is accurate. Setting
|
|
43
|
+
this to False will make the multiplier in the result equal to the
|
|
44
|
+
Room.multiplier property, which may be useful for certain visualizations
|
|
45
|
+
where rooms are to be colored with the total result for their parent
|
|
46
|
+
zone. (Default: True).
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
An array of tuples that contain matched rooms and data collections. All
|
|
50
|
+
tuples have a length of 3 with the following:
|
|
51
|
+
|
|
52
|
+
- room -- A honeybee Room object.
|
|
53
|
+
|
|
54
|
+
- data_collection -- A data collection that matches the honeybee Room.
|
|
55
|
+
|
|
56
|
+
- multiplier -- An integer for the Room multiplier, which may be useful
|
|
57
|
+
for calculating total results.
|
|
58
|
+
"""
|
|
59
|
+
# extract the zone identifier from each of the data collections
|
|
60
|
+
zone_ids = []
|
|
61
|
+
use_mult = False
|
|
62
|
+
for data in data_collections:
|
|
63
|
+
if 'Zone' in data.header.metadata:
|
|
64
|
+
zone_ids.append(data.header.metadata['Zone'])
|
|
65
|
+
else: # it's HVAC system data and we need to see if it's matchable
|
|
66
|
+
hvac_id = data.header.metadata['System']
|
|
67
|
+
use_mult = True
|
|
68
|
+
if ' IDEAL LOADS AIR SYSTEM' in hvac_id: # convention of E+ HVAC Templates
|
|
69
|
+
zone_ids.append(hvac_id.split(' IDEAL LOADS AIR SYSTEM')[0])
|
|
70
|
+
elif '..' in hvac_id: # convention used for service hot water
|
|
71
|
+
zone_ids.append(hvac_id.split('..')[-1])
|
|
72
|
+
use_mult = False if 'Gain' in data.header.metadata['type'] else True
|
|
73
|
+
else:
|
|
74
|
+
use_mult = False
|
|
75
|
+
zone_ids.append(hvac_id)
|
|
76
|
+
if invert_multiplier:
|
|
77
|
+
use_mult = not use_mult
|
|
78
|
+
if space_based:
|
|
79
|
+
zone_ids = [zid.replace('_SPACE', '') for zid in zone_ids]
|
|
80
|
+
|
|
81
|
+
# count the number of rooms in each zone to zone-correct the multiplier
|
|
82
|
+
if not space_based and zone_correct_mult:
|
|
83
|
+
zone_counter = {}
|
|
84
|
+
for room in rooms:
|
|
85
|
+
z_id = clean_ep_string(room.zone)
|
|
86
|
+
try:
|
|
87
|
+
zone_counter[z_id] += 1
|
|
88
|
+
except KeyError: # first room found in the zone
|
|
89
|
+
zone_counter[z_id] = 1
|
|
90
|
+
|
|
91
|
+
# loop through the rooms and match the data to them
|
|
92
|
+
matched_tuples = [] # list of matched rooms and data collections
|
|
93
|
+
for room in rooms:
|
|
94
|
+
if space_based:
|
|
95
|
+
rm_id, zc = room.identifier.upper(), 1
|
|
96
|
+
else:
|
|
97
|
+
z_id = clean_ep_string(room.zone)
|
|
98
|
+
rm_id = z_id.upper()
|
|
99
|
+
zc = zone_counter[z_id] if zone_correct_mult else 1
|
|
100
|
+
for i, data_id in enumerate(zone_ids):
|
|
101
|
+
if data_id == rm_id:
|
|
102
|
+
mult = 1 if not use_mult else room.multiplier
|
|
103
|
+
matched_tuples.append((room, data_collections[i], mult / zc))
|
|
104
|
+
break
|
|
105
|
+
|
|
106
|
+
return matched_tuples
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def match_faces_to_data(data_collections, faces):
|
|
110
|
+
"""Match honeybee faces/sub-faces to data collections from SQLiteResult.
|
|
111
|
+
|
|
112
|
+
This method will correctly match triangulated apertures and doors with a
|
|
113
|
+
merged version of the relevant data_collections.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
data_collections: An array of data collections that will be matched to
|
|
117
|
+
the input faces Data collections can be of any class (eg.
|
|
118
|
+
MonthlyCollection, DailyCollection) but they should all have headers
|
|
119
|
+
with metadata dictionaries with 'Surface' keys. These keys will be
|
|
120
|
+
used to match the data in the collections to the input faces.
|
|
121
|
+
faces: An array of honeybee Faces, Apertures, and/or Doors which will be
|
|
122
|
+
matched to the data_collections. Note that a given input Face should
|
|
123
|
+
NOT have its child Apertures or Doors as a separate item.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
An array of tuples that contain matched faces/sub-faces and data collections.
|
|
127
|
+
All tuples have a length of 2 with the following:
|
|
128
|
+
|
|
129
|
+
- face -- A honeybee Face, Aperture, or Door object.
|
|
130
|
+
|
|
131
|
+
- data_collection -- A data collection that matches the Face,
|
|
132
|
+
Aperture, or Door.
|
|
133
|
+
"""
|
|
134
|
+
matched_tuples = [] # list of matched faces and data collections
|
|
135
|
+
|
|
136
|
+
flat_f = [] # flatten the list of nested apertures and doors in the faces
|
|
137
|
+
for face in faces:
|
|
138
|
+
if isinstance(face, Face):
|
|
139
|
+
flat_f.append(face)
|
|
140
|
+
for ap in face.apertures:
|
|
141
|
+
flat_f.append(ap)
|
|
142
|
+
for dr in face.doors:
|
|
143
|
+
flat_f.append(dr)
|
|
144
|
+
elif isinstance(face, (Aperture, Door)):
|
|
145
|
+
flat_f.append(face)
|
|
146
|
+
else:
|
|
147
|
+
raise ValueError('Expected honeybee Face, Aperture, Door or Shade '
|
|
148
|
+
'for match_faces_to_data. Got {}.'.format(type(face)))
|
|
149
|
+
|
|
150
|
+
# extract the surface id from each of the data collections
|
|
151
|
+
srf_ids = []
|
|
152
|
+
tri_srf_ids = {} # track data collections from triangulated apertures/doors
|
|
153
|
+
tri_pattern = re.compile(r".*\.\.\d")
|
|
154
|
+
for data in data_collections:
|
|
155
|
+
if 'Surface' in data.header.metadata:
|
|
156
|
+
srf_ids.append(data.header.metadata['Surface'])
|
|
157
|
+
if tri_pattern.match(data.header.metadata['Surface']) is not None:
|
|
158
|
+
base_name = re.sub(r'(\.\.\d*)', '', data.header.metadata['Surface'])
|
|
159
|
+
try:
|
|
160
|
+
tri_srf_ids[base_name].append(data)
|
|
161
|
+
except KeyError: # first triangulated piece found
|
|
162
|
+
tri_srf_ids[base_name] = [data]
|
|
163
|
+
|
|
164
|
+
# loop through the faces and match the data to them
|
|
165
|
+
for face in flat_f:
|
|
166
|
+
f_id = face.identifier.upper()
|
|
167
|
+
for i, data_id in enumerate(srf_ids):
|
|
168
|
+
if data_id == f_id:
|
|
169
|
+
matched_tuples.append((face, data_collections[i]))
|
|
170
|
+
break
|
|
171
|
+
else: # check to see if it's a triangulated sub-face
|
|
172
|
+
try:
|
|
173
|
+
data_colls = tri_srf_ids[f_id]
|
|
174
|
+
matched_tuples.append(
|
|
175
|
+
(face, _merge_collections(data_colls, f_id)))
|
|
176
|
+
except KeyError:
|
|
177
|
+
pass # the face could not be matched with any data
|
|
178
|
+
return matched_tuples
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _merge_collections(data_collections, surface_id):
|
|
182
|
+
"""Combine several data collections for a triangulated surface into one.
|
|
183
|
+
|
|
184
|
+
This method will automatically decide to either sum the collection data or
|
|
185
|
+
average it depending on whether the data collection data_type is cumulative.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
data_collections: A list of data collections to be merged.
|
|
189
|
+
surface_id: The identifier of the combined surface, encompassing
|
|
190
|
+
all triangulated pieces.
|
|
191
|
+
"""
|
|
192
|
+
# total all of the values in the data collections
|
|
193
|
+
merged_data = data_collections[0]
|
|
194
|
+
for data in data_collections[1:]:
|
|
195
|
+
merged_data = merged_data + data
|
|
196
|
+
# divide by the number of collections if the data type is not cumulative
|
|
197
|
+
if not data_collections[0].header.data_type.cumulative:
|
|
198
|
+
merged_data = merged_data / len(data_collections)
|
|
199
|
+
# create the final data collection
|
|
200
|
+
merged_data = merged_data.duplicate() # duplicate to avoid editing the header
|
|
201
|
+
merged_data.header.metadata['Surface'] = surface_id
|
|
202
|
+
return merged_data
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
"""Module for parsing OpenStudio Workflow (OSW) files."""
|
|
3
|
+
from __future__ import division
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import json
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class OSW(object):
|
|
10
|
+
"""Object for parsing OpenStudio Workflow (OSW) files.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
file_path: Full path to an OSW file.
|
|
14
|
+
|
|
15
|
+
Properties:
|
|
16
|
+
* file_path
|
|
17
|
+
* file_dict
|
|
18
|
+
* stdout
|
|
19
|
+
* warnings
|
|
20
|
+
* errors
|
|
21
|
+
* error_tracebacks
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(self, file_path):
|
|
25
|
+
"""Initialize OSW"""
|
|
26
|
+
assert os.path.isfile(file_path), 'No file was found at {}'.format(file_path)
|
|
27
|
+
assert file_path.endswith('.osw'), \
|
|
28
|
+
'{} is not an OSW file ending in .osw.'.format(file_path)
|
|
29
|
+
self._file_path = file_path
|
|
30
|
+
self._load_contents()
|
|
31
|
+
self._parse_contents()
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def file_path(self):
|
|
35
|
+
"""Get the path to the .osw file."""
|
|
36
|
+
return self._file_path
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def file_dict(self):
|
|
40
|
+
"""Get a dictionary of all contents in the file."""
|
|
41
|
+
return self._file_dict
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def stdout(self):
|
|
45
|
+
"""Get a list of strings for all of the stdout of each step of the .osw file."""
|
|
46
|
+
return self._stdout
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def warnings(self):
|
|
50
|
+
"""Get a list of strings for all of the warnings found in the .osw file."""
|
|
51
|
+
return self._warnings
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def errors(self):
|
|
55
|
+
"""Get a list of strings for all of the fatal errors found in the .osw file."""
|
|
56
|
+
return self._errors
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def error_tracebacks(self):
|
|
60
|
+
"""Get a list of strings for the tracebacks found in the .osw file."""
|
|
61
|
+
return self._error_tracebacks
|
|
62
|
+
|
|
63
|
+
def _load_contents(self):
|
|
64
|
+
"""Parse all of the contents of a file path."""
|
|
65
|
+
with open(self._file_path) as json_file:
|
|
66
|
+
self._file_dict = json.load(json_file)
|
|
67
|
+
|
|
68
|
+
def _parse_contents(self):
|
|
69
|
+
"""Sort the contents of the error file into warnings and errors."""
|
|
70
|
+
self._stdout = []
|
|
71
|
+
self._warnings = []
|
|
72
|
+
self._errors = []
|
|
73
|
+
self._error_tracebacks = []
|
|
74
|
+
try:
|
|
75
|
+
for step in self.file_dict['steps']:
|
|
76
|
+
result = step['result']
|
|
77
|
+
self._stdout.append(result['stdout'])
|
|
78
|
+
self._warnings.extend(result['step_warnings'])
|
|
79
|
+
for error in result['step_errors']:
|
|
80
|
+
self._error_tracebacks.append(error)
|
|
81
|
+
self._errors.append(error.split('\n')[0])
|
|
82
|
+
except KeyError: # no results yet
|
|
83
|
+
pass
|
|
84
|
+
|
|
85
|
+
def ToString(self):
|
|
86
|
+
"""Overwrite .NET ToString."""
|
|
87
|
+
return self.__repr__()
|
|
88
|
+
|
|
89
|
+
def __repr__(self):
|
|
90
|
+
return 'OSW: {}'.format(self.file_path)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
"""Module for parsing EnergyPlus Result Data Dictionary (RDD) files."""
|
|
3
|
+
from __future__ import division
|
|
4
|
+
|
|
5
|
+
from honeybee.search import filter_array_by_keywords
|
|
6
|
+
import os
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class RDD(object):
|
|
10
|
+
"""Object for parsing EnergyPlus Result Data Dictionary (RDD) files.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
file_path: Full path to a RDD file that was generated by EnergyPlus.
|
|
14
|
+
|
|
15
|
+
Properties:
|
|
16
|
+
* file_path
|
|
17
|
+
* output_names
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, file_path):
|
|
21
|
+
"""Initialize RDD"""
|
|
22
|
+
assert os.path.isfile(file_path), 'No file was found at {}'.format(file_path)
|
|
23
|
+
assert file_path.endswith('.rdd'), \
|
|
24
|
+
'{} is not an RDD file ending in .rdd.'.format(file_path)
|
|
25
|
+
self._file_path = file_path
|
|
26
|
+
self._output_names = None
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def file_path(self):
|
|
30
|
+
"""Get the path to the .rdd file."""
|
|
31
|
+
return self._file_path
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def output_names(self):
|
|
35
|
+
"""Get a list of all output names in the .rdd file."""
|
|
36
|
+
if not self._output_names:
|
|
37
|
+
self._parse_outputs()
|
|
38
|
+
return self._output_names
|
|
39
|
+
|
|
40
|
+
def filter_outputs_by_keywords(self, keywords):
|
|
41
|
+
"""Get a list of outputs in the RDD file filtered by keyword.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
keywords: A list of keywords that will be used to filter the output names.
|
|
45
|
+
"""
|
|
46
|
+
return filter_array_by_keywords(self.output_names, keywords, True)
|
|
47
|
+
|
|
48
|
+
def _parse_outputs(self):
|
|
49
|
+
"""Parse all of the outputs from the file."""
|
|
50
|
+
with open(self._file_path) as rdd_result:
|
|
51
|
+
self._output_names = tuple(line.split(',')[-2] for line in rdd_result
|
|
52
|
+
if not line.startswith('!'))
|
|
53
|
+
|
|
54
|
+
def ToString(self):
|
|
55
|
+
"""Overwrite .NET ToString."""
|
|
56
|
+
return self.__repr__()
|
|
57
|
+
|
|
58
|
+
def __repr__(self):
|
|
59
|
+
return 'Energy RDD Result: {}'.format(self.file_path)
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
"""Module for parsing EnergyPlus ZSZ csv result files into Ladybug DataCollections."""
|
|
3
|
+
from __future__ import division
|
|
4
|
+
|
|
5
|
+
from ladybug.futil import csv_to_matrix
|
|
6
|
+
from ladybug.datacollection import HourlyContinuousCollection
|
|
7
|
+
from ladybug.header import Header
|
|
8
|
+
from ladybug.analysisperiod import AnalysisPeriod
|
|
9
|
+
from ladybug.dt import Date
|
|
10
|
+
from ladybug.datatype.power import Power
|
|
11
|
+
from ladybug.datatype.massflowrate import MassFlowRate
|
|
12
|
+
|
|
13
|
+
import os
|
|
14
|
+
from datetime import datetime
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ZSZ(object):
|
|
18
|
+
"""Object for parsing EnergyPlus Zone Sizing (ZSZ) csv result files.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
file_path: Full path to a ZSZ csv file that was generated by EnergyPlus.
|
|
22
|
+
cooling_date: A ladybug Date object for the cooling design day. This is needed
|
|
23
|
+
as the ZSZ file contains no information about the dates of the loads.
|
|
24
|
+
heating_date: A ladybug Date object for the heating design day. This is needed
|
|
25
|
+
as the ZSZ file contains no information about the dates of the loads.
|
|
26
|
+
|
|
27
|
+
Properties:
|
|
28
|
+
* file_path
|
|
29
|
+
* cooling_date
|
|
30
|
+
* heating_date
|
|
31
|
+
* timestep
|
|
32
|
+
* cooling_load_data
|
|
33
|
+
* heating_load_data
|
|
34
|
+
* cooling_flow_data
|
|
35
|
+
* heating_flow_data
|
|
36
|
+
"""
|
|
37
|
+
# a list if keywords to identify design day names from room names in headers
|
|
38
|
+
DES_DAY_KEYWORDS = (' CLG ', ' HTG ', ' DESIGN DAY ')
|
|
39
|
+
|
|
40
|
+
def __init__(self, file_path, cooling_date=Date(1, 1), heating_date=Date(1, 1)):
|
|
41
|
+
"""Initialize ZSZ"""
|
|
42
|
+
# check that the file exists
|
|
43
|
+
assert os.path.isfile(file_path), 'No file was found at {}'.format(file_path)
|
|
44
|
+
assert file_path.endswith('.csv'), \
|
|
45
|
+
'{} is not an CSV file ending in .csv.'.format(file_path)
|
|
46
|
+
self._file_path = file_path
|
|
47
|
+
|
|
48
|
+
# parse the data in the file
|
|
49
|
+
data_mtx = csv_to_matrix(file_path)
|
|
50
|
+
|
|
51
|
+
# extract the header and the peak values
|
|
52
|
+
headers = data_mtx[0][:] # copy the list
|
|
53
|
+
del headers[0]
|
|
54
|
+
peak = data_mtx[-3][:] # copy the list
|
|
55
|
+
del peak[0]
|
|
56
|
+
del data_mtx[0]
|
|
57
|
+
for i in range(3):
|
|
58
|
+
del data_mtx[-1]
|
|
59
|
+
self._headers = headers
|
|
60
|
+
self._peak = peak
|
|
61
|
+
|
|
62
|
+
# process the timestep of the data
|
|
63
|
+
data_mtx = list(zip(*data_mtx))
|
|
64
|
+
time1 = datetime.strptime(data_mtx[0][0], '%H:%M:%S')
|
|
65
|
+
time2 = datetime.strptime(data_mtx[0][1], '%H:%M:%S')
|
|
66
|
+
t_delta = time2 - time1
|
|
67
|
+
self._timestep = int(3600 / t_delta.seconds)
|
|
68
|
+
self._cooling_date = cooling_date
|
|
69
|
+
self._heating_date = heating_date
|
|
70
|
+
self._cool_a_period = AnalysisPeriod(
|
|
71
|
+
cooling_date.month, cooling_date.day, 0,
|
|
72
|
+
cooling_date.month, cooling_date.day, 23, timestep=self._timestep)
|
|
73
|
+
self._heat_a_period = AnalysisPeriod(
|
|
74
|
+
heating_date.month, heating_date.day, 0,
|
|
75
|
+
heating_date.month, heating_date.day, 23, timestep=self._timestep)
|
|
76
|
+
|
|
77
|
+
# process the body of the data
|
|
78
|
+
del data_mtx[0]
|
|
79
|
+
self._data = data_mtx
|
|
80
|
+
|
|
81
|
+
# properties to be computed upon request
|
|
82
|
+
self._cooling_load_data = None
|
|
83
|
+
self._heating_load_data = None
|
|
84
|
+
self._cooling_flow_data = None
|
|
85
|
+
self._heating_flow_data = None
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def file_path(self):
|
|
89
|
+
"""Get the path to the .rdd file."""
|
|
90
|
+
return self._file_path
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def cooling_date(self):
|
|
94
|
+
"""Get the date object assigned to the cooling design day."""
|
|
95
|
+
return self._cooling_date
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def heating_date(self):
|
|
99
|
+
"""Get the date object assigned to the heating design day."""
|
|
100
|
+
return self._heating_date
|
|
101
|
+
|
|
102
|
+
@property
|
|
103
|
+
def timestep(self):
|
|
104
|
+
"""Get the timestep of the data in the file."""
|
|
105
|
+
return self._timestep
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def cooling_load_data(self):
|
|
109
|
+
"""Get a list of HourlyContinuousCollections for zone cooling load.
|
|
110
|
+
|
|
111
|
+
There will be one data collection per conditioned zone in the model. Data
|
|
112
|
+
collections are at the timestep of this object and values are in Watts.
|
|
113
|
+
"""
|
|
114
|
+
if self._cooling_load_data is None:
|
|
115
|
+
self._cooling_load_data = self._process_collections(
|
|
116
|
+
'Summer Design Day Sensible Cooling Load', 'Des Sens Cool Load [W]', 'W')
|
|
117
|
+
return self._cooling_load_data
|
|
118
|
+
|
|
119
|
+
@property
|
|
120
|
+
def heating_load_data(self):
|
|
121
|
+
"""Get a list of HourlyContinuousCollections for zone heating load.
|
|
122
|
+
|
|
123
|
+
There will be one data collection per conditioned zone in the model. Data
|
|
124
|
+
collections are at the timestep of this object and values are in Watts.
|
|
125
|
+
"""
|
|
126
|
+
if self._heating_load_data is None:
|
|
127
|
+
self._heating_load_data = self._process_collections(
|
|
128
|
+
'Winter Design Day Heating Load', 'Des Heat Load [W]', 'W')
|
|
129
|
+
return self._heating_load_data
|
|
130
|
+
|
|
131
|
+
@property
|
|
132
|
+
def cooling_flow_data(self):
|
|
133
|
+
"""Get a list of HourlyContinuousCollections for zone cooling mass flow.
|
|
134
|
+
|
|
135
|
+
There will be one data collection per conditioned zone in the model. Data
|
|
136
|
+
collections are at the timestep of this object and values are in m3/s.
|
|
137
|
+
"""
|
|
138
|
+
if self._cooling_flow_data is None:
|
|
139
|
+
self._cooling_flow_data = self._process_collections(
|
|
140
|
+
'Summer Design Day Cooling Mass Flow',
|
|
141
|
+
'Des Cool Mass Flow [kg/s]', 'kg/s')
|
|
142
|
+
return self._cooling_flow_data
|
|
143
|
+
|
|
144
|
+
@property
|
|
145
|
+
def heating_flow_data(self):
|
|
146
|
+
"""Get a list of HourlyContinuousCollections for zone heating mass flow.
|
|
147
|
+
|
|
148
|
+
There will be one data collection per conditioned zone in the model. Data
|
|
149
|
+
collections are at the timestep of this object and values are in m3/s.
|
|
150
|
+
"""
|
|
151
|
+
if self._heating_flow_data is None:
|
|
152
|
+
self._heating_flow_data = self._process_collections(
|
|
153
|
+
'Winter Design Day Heating Flow', 'Des Heat Mass Flow [kg/s]', 'kg/s')
|
|
154
|
+
return self._heating_flow_data
|
|
155
|
+
|
|
156
|
+
def _process_collections(self, description, data_type_text, unit):
|
|
157
|
+
"""Convert the raw data in the hidden _data property to data collections."""
|
|
158
|
+
data_type = Power() if unit == 'W' else MassFlowRate()
|
|
159
|
+
a_per = self._cool_a_period if 'Summer' in description else self._heat_a_period
|
|
160
|
+
collections = []
|
|
161
|
+
for i, col_head in enumerate(self._headers):
|
|
162
|
+
if data_type_text in col_head:
|
|
163
|
+
# try to differentiate the Zone name from the design day name
|
|
164
|
+
c_head, zone_names, end_found = col_head.split(':')[:-1], [], False
|
|
165
|
+
for name in c_head:
|
|
166
|
+
for kwrd in self.DES_DAY_KEYWORDS:
|
|
167
|
+
if kwrd in name:
|
|
168
|
+
end_found = True
|
|
169
|
+
break
|
|
170
|
+
else:
|
|
171
|
+
zone_names.append(name)
|
|
172
|
+
if end_found:
|
|
173
|
+
break
|
|
174
|
+
if len(zone_names) == len(c_head): # we did not find it
|
|
175
|
+
zone_name = c_head[0]
|
|
176
|
+
else:
|
|
177
|
+
zone_name = ':'.join(zone_names)
|
|
178
|
+
# create the header
|
|
179
|
+
metadata = {'type': description, 'Zone': zone_name}
|
|
180
|
+
head = Header(data_type, unit, a_per, metadata)
|
|
181
|
+
collections.append(HourlyContinuousCollection(
|
|
182
|
+
head, [float(val) for val in self._data[i]]))
|
|
183
|
+
return collections
|
|
184
|
+
|
|
185
|
+
def ToString(self):
|
|
186
|
+
"""Overwrite .NET ToString."""
|
|
187
|
+
return self.__repr__()
|
|
188
|
+
|
|
189
|
+
def __repr__(self):
|
|
190
|
+
return 'Energy ZSZ Result: {}'.format(self.file_path)
|