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.
Files changed (162) hide show
  1. honeybee_energy/__init__.py +24 -0
  2. honeybee_energy/__main__.py +4 -0
  3. honeybee_energy/_extend_honeybee.py +145 -0
  4. honeybee_energy/altnumber.py +21 -0
  5. honeybee_energy/baseline/__init__.py +2 -0
  6. honeybee_energy/baseline/create.py +608 -0
  7. honeybee_energy/baseline/data/__init__.py +1 -0
  8. honeybee_energy/baseline/data/constructions.csv +64 -0
  9. honeybee_energy/baseline/data/fen_ratios.csv +15 -0
  10. honeybee_energy/baseline/data/lpd_building.csv +21 -0
  11. honeybee_energy/baseline/data/pci_2016.csv +22 -0
  12. honeybee_energy/baseline/data/pci_2019.csv +22 -0
  13. honeybee_energy/baseline/data/pci_2022.csv +22 -0
  14. honeybee_energy/baseline/data/shw.csv +21 -0
  15. honeybee_energy/baseline/pci.py +512 -0
  16. honeybee_energy/baseline/result.py +371 -0
  17. honeybee_energy/boundarycondition.py +128 -0
  18. honeybee_energy/cli/__init__.py +69 -0
  19. honeybee_energy/cli/baseline.py +475 -0
  20. honeybee_energy/cli/edit.py +327 -0
  21. honeybee_energy/cli/lib.py +1154 -0
  22. honeybee_energy/cli/result.py +810 -0
  23. honeybee_energy/cli/setconfig.py +124 -0
  24. honeybee_energy/cli/settings.py +569 -0
  25. honeybee_energy/cli/simulate.py +380 -0
  26. honeybee_energy/cli/translate.py +1714 -0
  27. honeybee_energy/cli/validate.py +224 -0
  28. honeybee_energy/config.json +11 -0
  29. honeybee_energy/config.py +842 -0
  30. honeybee_energy/construction/__init__.py +1 -0
  31. honeybee_energy/construction/_base.py +374 -0
  32. honeybee_energy/construction/air.py +325 -0
  33. honeybee_energy/construction/dictutil.py +89 -0
  34. honeybee_energy/construction/dynamic.py +607 -0
  35. honeybee_energy/construction/opaque.py +460 -0
  36. honeybee_energy/construction/shade.py +319 -0
  37. honeybee_energy/construction/window.py +1096 -0
  38. honeybee_energy/construction/windowshade.py +847 -0
  39. honeybee_energy/constructionset.py +1655 -0
  40. honeybee_energy/dictutil.py +56 -0
  41. honeybee_energy/generator/__init__.py +5 -0
  42. honeybee_energy/generator/loadcenter.py +204 -0
  43. honeybee_energy/generator/pv.py +535 -0
  44. honeybee_energy/hvac/__init__.py +21 -0
  45. honeybee_energy/hvac/_base.py +124 -0
  46. honeybee_energy/hvac/_template.py +270 -0
  47. honeybee_energy/hvac/allair/__init__.py +22 -0
  48. honeybee_energy/hvac/allair/_base.py +349 -0
  49. honeybee_energy/hvac/allair/furnace.py +168 -0
  50. honeybee_energy/hvac/allair/psz.py +131 -0
  51. honeybee_energy/hvac/allair/ptac.py +163 -0
  52. honeybee_energy/hvac/allair/pvav.py +109 -0
  53. honeybee_energy/hvac/allair/vav.py +128 -0
  54. honeybee_energy/hvac/detailed.py +337 -0
  55. honeybee_energy/hvac/doas/__init__.py +28 -0
  56. honeybee_energy/hvac/doas/_base.py +345 -0
  57. honeybee_energy/hvac/doas/fcu.py +127 -0
  58. honeybee_energy/hvac/doas/radiant.py +329 -0
  59. honeybee_energy/hvac/doas/vrf.py +81 -0
  60. honeybee_energy/hvac/doas/wshp.py +91 -0
  61. honeybee_energy/hvac/heatcool/__init__.py +23 -0
  62. honeybee_energy/hvac/heatcool/_base.py +177 -0
  63. honeybee_energy/hvac/heatcool/baseboard.py +61 -0
  64. honeybee_energy/hvac/heatcool/evapcool.py +72 -0
  65. honeybee_energy/hvac/heatcool/fcu.py +92 -0
  66. honeybee_energy/hvac/heatcool/gasunit.py +53 -0
  67. honeybee_energy/hvac/heatcool/radiant.py +269 -0
  68. honeybee_energy/hvac/heatcool/residential.py +77 -0
  69. honeybee_energy/hvac/heatcool/vrf.py +54 -0
  70. honeybee_energy/hvac/heatcool/windowac.py +70 -0
  71. honeybee_energy/hvac/heatcool/wshp.py +62 -0
  72. honeybee_energy/hvac/idealair.py +699 -0
  73. honeybee_energy/internalmass.py +310 -0
  74. honeybee_energy/lib/__init__.py +1 -0
  75. honeybee_energy/lib/_loadconstructions.py +194 -0
  76. honeybee_energy/lib/_loadconstructionsets.py +117 -0
  77. honeybee_energy/lib/_loadmaterials.py +83 -0
  78. honeybee_energy/lib/_loadprogramtypes.py +125 -0
  79. honeybee_energy/lib/_loadschedules.py +87 -0
  80. honeybee_energy/lib/_loadtypelimits.py +64 -0
  81. honeybee_energy/lib/constructions.py +207 -0
  82. honeybee_energy/lib/constructionsets.py +95 -0
  83. honeybee_energy/lib/materials.py +67 -0
  84. honeybee_energy/lib/programtypes.py +125 -0
  85. honeybee_energy/lib/schedules.py +61 -0
  86. honeybee_energy/lib/scheduletypelimits.py +31 -0
  87. honeybee_energy/load/__init__.py +1 -0
  88. honeybee_energy/load/_base.py +190 -0
  89. honeybee_energy/load/daylight.py +397 -0
  90. honeybee_energy/load/dictutil.py +47 -0
  91. honeybee_energy/load/equipment.py +771 -0
  92. honeybee_energy/load/hotwater.py +543 -0
  93. honeybee_energy/load/infiltration.py +460 -0
  94. honeybee_energy/load/lighting.py +480 -0
  95. honeybee_energy/load/people.py +497 -0
  96. honeybee_energy/load/process.py +472 -0
  97. honeybee_energy/load/setpoint.py +816 -0
  98. honeybee_energy/load/ventilation.py +550 -0
  99. honeybee_energy/material/__init__.py +1 -0
  100. honeybee_energy/material/_base.py +166 -0
  101. honeybee_energy/material/dictutil.py +59 -0
  102. honeybee_energy/material/frame.py +367 -0
  103. honeybee_energy/material/gas.py +1087 -0
  104. honeybee_energy/material/glazing.py +854 -0
  105. honeybee_energy/material/opaque.py +1351 -0
  106. honeybee_energy/material/shade.py +1360 -0
  107. honeybee_energy/measure.py +472 -0
  108. honeybee_energy/programtype.py +723 -0
  109. honeybee_energy/properties/__init__.py +1 -0
  110. honeybee_energy/properties/aperture.py +333 -0
  111. honeybee_energy/properties/door.py +342 -0
  112. honeybee_energy/properties/extension.py +244 -0
  113. honeybee_energy/properties/face.py +274 -0
  114. honeybee_energy/properties/model.py +2640 -0
  115. honeybee_energy/properties/room.py +1747 -0
  116. honeybee_energy/properties/shade.py +314 -0
  117. honeybee_energy/properties/shademesh.py +262 -0
  118. honeybee_energy/reader.py +48 -0
  119. honeybee_energy/result/__init__.py +1 -0
  120. honeybee_energy/result/colorobj.py +648 -0
  121. honeybee_energy/result/emissions.py +290 -0
  122. honeybee_energy/result/err.py +101 -0
  123. honeybee_energy/result/eui.py +100 -0
  124. honeybee_energy/result/generation.py +160 -0
  125. honeybee_energy/result/loadbalance.py +890 -0
  126. honeybee_energy/result/match.py +202 -0
  127. honeybee_energy/result/osw.py +90 -0
  128. honeybee_energy/result/rdd.py +59 -0
  129. honeybee_energy/result/zsz.py +190 -0
  130. honeybee_energy/run.py +1577 -0
  131. honeybee_energy/schedule/__init__.py +1 -0
  132. honeybee_energy/schedule/day.py +626 -0
  133. honeybee_energy/schedule/dictutil.py +59 -0
  134. honeybee_energy/schedule/fixedinterval.py +1012 -0
  135. honeybee_energy/schedule/rule.py +619 -0
  136. honeybee_energy/schedule/ruleset.py +1867 -0
  137. honeybee_energy/schedule/typelimit.py +310 -0
  138. honeybee_energy/shw.py +315 -0
  139. honeybee_energy/simulation/__init__.py +1 -0
  140. honeybee_energy/simulation/control.py +214 -0
  141. honeybee_energy/simulation/daylightsaving.py +185 -0
  142. honeybee_energy/simulation/dictutil.py +51 -0
  143. honeybee_energy/simulation/output.py +646 -0
  144. honeybee_energy/simulation/parameter.py +606 -0
  145. honeybee_energy/simulation/runperiod.py +443 -0
  146. honeybee_energy/simulation/shadowcalculation.py +295 -0
  147. honeybee_energy/simulation/sizing.py +546 -0
  148. honeybee_energy/ventcool/__init__.py +5 -0
  149. honeybee_energy/ventcool/_crack_data.py +91 -0
  150. honeybee_energy/ventcool/afn.py +289 -0
  151. honeybee_energy/ventcool/control.py +269 -0
  152. honeybee_energy/ventcool/crack.py +126 -0
  153. honeybee_energy/ventcool/fan.py +493 -0
  154. honeybee_energy/ventcool/opening.py +365 -0
  155. honeybee_energy/ventcool/simulation.py +314 -0
  156. honeybee_energy/writer.py +1078 -0
  157. honeybee_energy-1.116.106.dist-info/METADATA +113 -0
  158. honeybee_energy-1.116.106.dist-info/RECORD +162 -0
  159. honeybee_energy-1.116.106.dist-info/WHEEL +5 -0
  160. honeybee_energy-1.116.106.dist-info/entry_points.txt +2 -0
  161. honeybee_energy-1.116.106.dist-info/licenses/LICENSE +661 -0
  162. 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)