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,648 @@
1
+ # coding=utf-8
2
+ """Module for coloring Model geometry with energy simulation results."""
3
+ from __future__ import division
4
+
5
+ from .match import match_rooms_to_data, match_faces_to_data
6
+
7
+ from honeybee.face import Face
8
+ from honeybee.room import Room
9
+ from honeybee.facetype import Floor
10
+ from honeybee.typing import int_in_range
11
+
12
+ from ladybug.dt import Date, DateTime
13
+ from ladybug.analysisperiod import AnalysisPeriod
14
+ from ladybug.datacollection import MonthlyCollection, DailyCollection, \
15
+ MonthlyPerHourCollection, HourlyContinuousCollection, HourlyDiscontinuousCollection
16
+ from ladybug.graphic import GraphicContainer
17
+ from ladybug.legend import LegendParameters
18
+
19
+ from ladybug_geometry.geometry3d.pointvector import Point3D
20
+
21
+
22
+ class _ColorObject(object):
23
+ """Base class for coloring geometry with simulation results.
24
+
25
+ Properties:
26
+ * data_collections
27
+ * legend_parameters
28
+ * simulation_step
29
+ * geo_unit
30
+ * title_text
31
+ * data_type_text
32
+ * data_type
33
+ * unit
34
+ * analysis_period
35
+ * min_point
36
+ * max_point
37
+ """
38
+ __slots__ = ('_data_collections', '_legend_parameters', '_simulation_step',
39
+ '_normalize', '_geo_unit', '_matched_objects', '_base_collection',
40
+ '_base_type', '_base_unit', '_min_point', '_max_point')
41
+
42
+ UNITS = ('m', 'mm', 'ft', 'in', 'cm')
43
+
44
+ def __init__(self, data_collections, legend_parameters=None,
45
+ simulation_step=None, geo_unit='m'):
46
+ """Initialize ColorObject."""
47
+ # check the input collections
48
+ acceptable_colls = (MonthlyCollection, DailyCollection, MonthlyPerHourCollection,
49
+ HourlyContinuousCollection, HourlyDiscontinuousCollection)
50
+ try:
51
+ data_collections = list(data_collections)
52
+ except TypeError:
53
+ raise TypeError('Input data_collections must be an array. Got {}.'.format(
54
+ type(data_collections)))
55
+ assert len(data_collections) > 0, \
56
+ 'ColorObject must have at least one data_collection.'
57
+ for i, coll in enumerate(data_collections):
58
+ assert isinstance(coll, acceptable_colls), 'Expected data collection for ' \
59
+ 'ColorObject data_collections. Got {}.'.format(type(coll))
60
+ if not coll.validated_a_period:
61
+ data_collections[i] = coll.validate_analysis_period()
62
+ self._base_collection = data_collections[0]
63
+ self._base_type = self._base_collection.header.data_type
64
+ self._base_unit = self._base_collection.header.unit
65
+ for coll in data_collections[1:]:
66
+ assert coll.header.unit == self._base_unit, \
67
+ 'ColorObject data_collections must all have matching units. ' \
68
+ '{} != {}.'.format(coll.header.unit, self._base_unit)
69
+ assert len(coll.values) == len(self._base_collection.values), \
70
+ 'ColorObject data_collections must all be aligned with one another.' \
71
+ '{} != {}'.format(len(coll.values), len(self._base_collection.values))
72
+ self._data_collections = data_collections
73
+
74
+ # assign the other properties of this object
75
+ self.legend_parameters = legend_parameters
76
+ self.simulation_step = simulation_step
77
+ self.geo_unit = geo_unit
78
+ self._normalize = False
79
+
80
+ @property
81
+ def data_collections(self):
82
+ """Get a tuple of data collections assigned to this object."""
83
+ return tuple(self._data_collections)
84
+
85
+ @property
86
+ def legend_parameters(self):
87
+ """Get or set the legend parameters."""
88
+ return self._legend_parameters
89
+
90
+ @legend_parameters.setter
91
+ def legend_parameters(self, value):
92
+ if value is not None:
93
+ assert isinstance(value, LegendParameters), \
94
+ 'Expected LegendParameters. Got {}.'.format(type(value))
95
+ self._legend_parameters = value.duplicate()
96
+ else:
97
+ self._legend_parameters = LegendParameters()
98
+
99
+ @property
100
+ def simulation_step(self):
101
+ """Get or set an integer to select a specific step of the data collections."""
102
+ return self._simulation_step
103
+
104
+ @simulation_step.setter
105
+ def simulation_step(self, value):
106
+ if value is not None:
107
+ value = int_in_range(
108
+ value, 0, len(self._base_collection) - 1, 'simulation_step')
109
+ self._simulation_step = value
110
+
111
+ @property
112
+ def geo_unit(self):
113
+ """Text to note the units that the object geometry is in.
114
+
115
+ This will be used to ensure the legend units display correctly when
116
+ data is floor-normalized. Examples include 'm', 'mm', 'ft'.
117
+ """
118
+ return self._geo_unit
119
+
120
+ @geo_unit.setter
121
+ def geo_unit(self, value):
122
+ self._geo_unit = str(value)
123
+ assert self._geo_unit in self.UNITS, \
124
+ 'Unit "{}" is not supported in color object.'.format(self._geo_unit)
125
+
126
+ @property
127
+ def title_text(self):
128
+ """Text string for the title of the color zones."""
129
+ d_type_text = self.data_type_text
130
+ if self._simulation_step is not None: # specific index from all collections
131
+ time_text = self.time_interval_text(self.simulation_step)
132
+ if self._base_type.normalized_type is not None and self._normalize:
133
+ d_type_text = '{} {}'.format(d_type_text, 'Intensity')
134
+ else: # average or total the data
135
+ time_text = str(self.analysis_period).split('@')[0]
136
+ if self._base_type.normalized_type is None or not self._normalize:
137
+ if not self._base_type.cumulative:
138
+ d_type_text = '{} {}'.format('Average', d_type_text)
139
+ else:
140
+ d_type_text = '{} {}'.format('Total', d_type_text)
141
+ else:
142
+ if not self._base_type.cumulative:
143
+ d_type_text = '{} {} {}'.format('Average', d_type_text, 'Intensity')
144
+ else:
145
+ d_type_text = '{} {} {}'.format('Total', d_type_text, 'Intensity')
146
+ return '{}\n{}'.format('{} ({})'.format(d_type_text, self.unit), time_text)
147
+
148
+ @property
149
+ def data_type_text(self):
150
+ """Text for the data type.
151
+
152
+ This will be the full name of the EnergyPlus output if the DataCollection
153
+ header metadata contains a 'type' key. Otherwise, this will be the name
154
+ of the data_type object.
155
+ """
156
+ m_data = self._base_collection.header.metadata
157
+ return m_data['type'] if 'type' in m_data else str(self.data_type)
158
+
159
+ @property
160
+ def data_type(self):
161
+ """The data type of this object's data collections."""
162
+ if self._base_type.normalized_type is None or not self._normalize:
163
+ return self._base_type
164
+ else:
165
+ return self._base_type.normalized_type()
166
+
167
+ @property
168
+ def unit(self):
169
+ """The unit of this object's data collections."""
170
+ if self._base_type.normalized_type is not None and self._normalize:
171
+ _geo_unit = 'ft' if self._geo_unit in ('ft', 'in') else 'm'
172
+ return '{}/{}2'.format(self._base_unit, _geo_unit) if '/' not in \
173
+ self._base_unit else '{}-{}2'.format(self._base_unit, _geo_unit)
174
+ else:
175
+ return self._base_unit
176
+
177
+ @property
178
+ def analysis_period(self):
179
+ """The analysis_period of this object's data collections."""
180
+ return self._base_collection.header.analysis_period
181
+
182
+ @property
183
+ def min_point(self):
184
+ """Get a Point3D for the minimum of the box around the rooms."""
185
+ return self._min_point
186
+
187
+ @property
188
+ def max_point(self):
189
+ """Get a Point3D for the maximum of the box around the rooms."""
190
+ return self._max_point
191
+
192
+ def time_interval_text(self, simulation_step):
193
+ """Get text for a specific time simulation_step of the data collections.
194
+
195
+ Args:
196
+ simulation_step: An integer for the step of simulation for which
197
+ text should be generated.
198
+ """
199
+ hourly_colls = (HourlyContinuousCollection, HourlyDiscontinuousCollection)
200
+ if isinstance(self._base_collection, hourly_colls):
201
+ return str(self._base_collection.datetimes[simulation_step])
202
+ elif isinstance(self._base_collection, MonthlyCollection):
203
+ month_names = AnalysisPeriod.MONTHNAMES
204
+ return month_names[self._base_collection.datetimes[simulation_step]]
205
+ elif isinstance(self._base_collection, DailyCollection):
206
+ return str(Date.from_doy(self._base_collection.datetimes[simulation_step]))
207
+ elif isinstance(self._base_collection, MonthlyPerHourCollection):
208
+ dt_tuple = self._base_collection.datetimes[simulation_step]
209
+ date_time = DateTime(month=dt_tuple[0], hour=dt_tuple[1])
210
+ return date_time.strftime('%b %H:%M')
211
+
212
+ def _calculate_min_max(self, hb_objs):
213
+ """Calculate maximum and minimum Point3D for a set of rooms."""
214
+ st_rm_min, st_rm_max = hb_objs[0].geometry.min, hb_objs[0].geometry.max
215
+ min_pt = [st_rm_min.x, st_rm_min.y, st_rm_min.z]
216
+ max_pt = [st_rm_max.x, st_rm_max.y, st_rm_max.z]
217
+
218
+ for room in hb_objs[1:]:
219
+ rm_min, rm_max = room.geometry.min, room.geometry.max
220
+ if rm_min.x < min_pt[0]:
221
+ min_pt[0] = rm_min.x
222
+ if rm_min.y < min_pt[1]:
223
+ min_pt[1] = rm_min.y
224
+ if rm_min.z < min_pt[2]:
225
+ min_pt[2] = rm_min.z
226
+ if rm_max.x > max_pt[0]:
227
+ max_pt[0] = rm_max.x
228
+ if rm_max.y > max_pt[1]:
229
+ max_pt[1] = rm_max.y
230
+ if rm_max.z > max_pt[2]:
231
+ max_pt[2] = rm_max.z
232
+
233
+ self._min_point = Point3D(min_pt[0], min_pt[1], min_pt[2])
234
+ self._max_point = Point3D(max_pt[0], max_pt[1], max_pt[2])
235
+
236
+ def ToString(self):
237
+ """Overwrite .NET ToString."""
238
+ return self.__repr__()
239
+
240
+ def __repr__(self):
241
+ return 'Color Object:'
242
+
243
+
244
+ class ColorRoom(_ColorObject):
245
+ """Object for visualization zone-level simulation results on Honeybee Room geometry.
246
+
247
+ Args:
248
+ data_collections: An array of data collections of the same data type,
249
+ which will be used to color Rooms with simulation results. Data collections
250
+ can be of any class (eg. MonthlyCollection, DailyCollection) but they
251
+ should all have headers with metadata dictionaries with 'Zone' or
252
+ 'System' keys. These keys will be used to match the data in the collections
253
+ to the input rooms.
254
+ rooms: An array of honeybee Rooms, which will be matched to the data_collections.
255
+ The length of these Rooms does not have to match the data_collections
256
+ and this object will only create visualizations for rooms that are
257
+ found to be matching.
258
+ legend_parameters: An optional LegendParameter object to change the display
259
+ of the ColorRooms (Default: None).
260
+ simulation_step: An optional integer (greater than or equal to 0) to select
261
+ a specific step of the data collections for which result values will be
262
+ generated. If None, the geometry will be colored with the total of
263
+ results in the data_collections if the data type is cumulative or with
264
+ the average of results if the data type is not cumulative. Default: None.
265
+ normalize_by_floor: Boolean to note whether results should be normalized
266
+ by the floor area of the Room if the data type of the data_collections
267
+ supports it. If False, values will be generated using sum total of
268
+ the data collection values. Note that this input has no effect if
269
+ the data type of the data_collections is not normalizable since data
270
+ collection values will always be averaged for this case. Default: True.
271
+ geo_unit: Optional text to note the units that the Room geometry is in.
272
+ This will be used to ensure the legend units display correctly when
273
+ data is floor-normalized. Examples include 'm', 'mm', 'ft'.
274
+ (Default: 'm' for meters).
275
+ space_based: Boolean to note whether the result is reported on the EnergyPlus
276
+ Space level instead of the Zone level. In this case, the matching to
277
+ the Room will account for the fact that the Space name is the Room
278
+ name with _Space added to it. (Default: False).
279
+
280
+ Properties:
281
+ * data_collections
282
+ * rooms
283
+ * legend_parameters
284
+ * simulation_step
285
+ * normalize_by_floor
286
+ * geo_unit
287
+ * space_based
288
+ * matched_rooms
289
+ * matched_data
290
+ * matched_values
291
+ * matched_floor_faces
292
+ * matched_floor_areas
293
+ * graphic_container
294
+ * title_text
295
+ * data_type_text
296
+ * data_type
297
+ * unit
298
+ * analysis_period
299
+ * min_point
300
+ * max_point
301
+ """
302
+ __slots__ = ('_rooms', '_space_based')
303
+
304
+ def __init__(self, data_collections, rooms, legend_parameters=None,
305
+ simulation_step=None, normalize_by_floor=True, geo_unit='m',
306
+ space_based=False):
307
+ """Initialize ColorRoom."""
308
+ # initialize the base object
309
+ _ColorObject.__init__(self, data_collections, legend_parameters,
310
+ simulation_step, geo_unit)
311
+ for coll in self._data_collections:
312
+ assert 'Zone' in coll.header.metadata or 'System' in coll.header.metadata, \
313
+ 'ColorRoom data collection does not have metadata associated with Zones.'
314
+
315
+ try: # check the input rooms
316
+ rooms = tuple(rooms)
317
+ except TypeError:
318
+ raise TypeError('Input rooms must be an array. Got {}.'.format(type(rooms)))
319
+ assert len(rooms) > 0, 'ColorRooms must have at least one room.'
320
+ for room in rooms:
321
+ assert isinstance(room, Room), 'Expected honeybee Room for ' \
322
+ 'ColorRoom rooms. Got {}.'.format(type(room))
323
+ self._rooms = rooms
324
+ self._calculate_min_max(self._rooms)
325
+
326
+ # match the rooms with the data collections
327
+ self._space_based = bool(space_based)
328
+ self._matched_objects = match_rooms_to_data(
329
+ data_collections, rooms, space_based=self._space_based,
330
+ zone_correct_mult=False)
331
+ if len(self._matched_objects) == 0:
332
+ raise ValueError('None of the ColorRoom data collections could be '
333
+ 'matched to the input rooms')
334
+
335
+ # assign the normalize property
336
+ self.normalize_by_floor = normalize_by_floor
337
+
338
+ @property
339
+ def rooms(self):
340
+ """Get a tuple of honeybee Rooms assigned to this object."""
341
+ return self._rooms
342
+
343
+ @property
344
+ def normalize_by_floor(self):
345
+ """Get or set a boolean for whether results should be normalized by floor area.
346
+ """
347
+ return self._normalize
348
+
349
+ @normalize_by_floor.setter
350
+ def normalize_by_floor(self, value):
351
+ self._normalize = bool(value)
352
+
353
+ @property
354
+ def space_based(self):
355
+ """Get a boolean for whether results are set to be space-based."""
356
+ return self._space_based
357
+
358
+ @property
359
+ def matched_rooms(self):
360
+ """Get a tuple of honeybee Rooms that have been matched to the data."""
361
+ return tuple(obj[0] for obj in self._matched_objects)
362
+
363
+ @property
364
+ def matched_data(self):
365
+ """Get a tuple of data collections that have been matched to the rooms."""
366
+ return tuple(obj[1] for obj in self._matched_objects)
367
+
368
+ @property
369
+ def matched_values(self):
370
+ """Get an array of numbers that correspond to the matched_rooms.
371
+
372
+ These values are derived from the data_collections but they will be
373
+ averaged/totaled and normalized by Room floor area depending on the
374
+ other inputs to this object.
375
+ """
376
+ if self._simulation_step is not None: # specific index from all collections
377
+ if self._base_type.normalized_type is None or not self._normalize:
378
+ return tuple(obj[1][self._simulation_step] for obj in
379
+ self._matched_objects)
380
+ else: # normalize the data by the floor area
381
+ vals = []
382
+ for obj, f_area in zip(self._matched_objects, self.matched_floor_areas):
383
+ try:
384
+ vals.append(obj[1][self._simulation_step] / (f_area * obj[2]))
385
+ except ZeroDivisionError: # no floor faces in the Room
386
+ vals.append(0)
387
+ return vals
388
+ else: # average or total the data based on data type
389
+ if self._base_type.normalized_type is None or not self._normalize:
390
+ if self._base_type.cumulative:
391
+ return tuple(obj[1].total for obj in self._matched_objects)
392
+ else:
393
+ return tuple(obj[1].average for obj in self._matched_objects)
394
+ else: # normalize the data by floor area
395
+ vals = []
396
+ zip_obj = zip(self._matched_objects, self.matched_floor_areas)
397
+ if self._base_type.cumulative: # divide total values by floor area
398
+ for obj, f_area in zip_obj:
399
+ try:
400
+ vals.append(obj[1].total / (f_area * obj[2]))
401
+ except ZeroDivisionError: # no floor faces in the Room
402
+ vals.append(0)
403
+ else: # divide average values by floor area
404
+ for obj, f_area in zip_obj:
405
+ try:
406
+ vals.append(obj[1].average / f_area)
407
+ except ZeroDivisionError: # no floor faces in the Room
408
+ vals.append(0)
409
+ return vals
410
+
411
+ @property
412
+ def matched_floor_faces(self):
413
+ """Get a nested array with each sub-array having all floor Face3Ds of each room.
414
+ """
415
+ flr_faces = []
416
+ for room in self.matched_rooms:
417
+ flr_faces.append(
418
+ [face.geometry for face in room.faces if isinstance(face.type, Floor)])
419
+ return flr_faces
420
+
421
+ @property
422
+ def matched_floor_areas(self):
423
+ """Get a list for all of the room floor areas that were matches with data.
424
+
425
+ These floor areas will always be in either square meters or square feet
426
+ depending on whether the geo_unit is either SI or IP.
427
+ """
428
+ # correct for more than one room in a zone
429
+ if not self.space_based:
430
+ zones = {}
431
+ for room in self.matched_rooms:
432
+ try:
433
+ zones[room.zone] += room.floor_area
434
+ except KeyError: # first room to be found in the zone
435
+ zones[room.zone] = room.floor_area
436
+ floor_areas = [zones[room.zone] for room in self.matched_rooms]
437
+ else:
438
+ floor_areas = [room.floor_area for room in self.matched_rooms]
439
+ # convert units if they are not conventional
440
+ if self._geo_unit in ('m', 'ft'): # no need to do unit conversions
441
+ return floor_areas
442
+ elif self._geo_unit == 'mm': # convert to meters
443
+ return [ar / 1000000.0 for ar in floor_areas]
444
+ elif self._geo_unit == 'in': # convert to feet
445
+ return [ar / 144.0 for ar in floor_areas]
446
+ else: # assume it's cm; convert to meters
447
+ return [ar / 10000.0 for ar in floor_areas]
448
+
449
+ @property
450
+ def graphic_container(self):
451
+ """Get a ladybug GraphicContainer that relates to this object.
452
+
453
+ The GraphicContainer possesses almost all things needed to visualize the
454
+ ColorRoom object including the legend, value_colors, lower_title_location,
455
+ upper_title_location, etc.
456
+ """
457
+ return GraphicContainer(
458
+ self.matched_values, self.min_point, self.max_point,
459
+ self.legend_parameters, self.data_type, str(self.unit))
460
+
461
+ def __repr__(self):
462
+ """Color Room representation."""
463
+ return 'Color Room: [{} Rooms] [{}]'.format(
464
+ len(self._matched_objects), self._base_collection.header)
465
+
466
+
467
+ class ColorFace(_ColorObject):
468
+ """Object for visualization face and sub-face-level simulation results on geometry.
469
+
470
+ Args:
471
+ data_collections: An array of data collections of the same data type,
472
+ which will be used to color Faces with simulation results. Data collections
473
+ can be of any class (eg. MonthlyCollection, DailyCollection) but they
474
+ should all have headers with metadata dictionaries with 'Surface'
475
+ keys. These keys will be used to match the data in the collections
476
+ to the input faces.
477
+ faces: An array of honeybee Faces, Apertures, and/or Doors which will be
478
+ matched to the data_collections.
479
+ legend_parameters: An optional LegendParameter object to change the display
480
+ of the ColorFace (Default: None).
481
+ simulation_step: An optional integer (greater than or equal to 0) to select
482
+ a specific step of the data collections for which result values will be
483
+ generated. If None, the geometry will be colored with the total of
484
+ results in the data_collections if the data type is cumulative or with
485
+ the average of results if the data type is not cumulative. Default: None.
486
+ normalize: Boolean to note whether results should be normalized by the
487
+ face/sub-face area if the data type of the data_collections supports it.
488
+ If False, values will be generated using sum total of the data collection
489
+ values. Note that this input has no effect if the data type of the
490
+ data_collections is not normalizable since data collection values will
491
+ always be averaged for this case. Default: True.
492
+ geo_unit: Optional text to note the units that the Face geometry is in.
493
+ This will be used to ensure the legend units display correctly when
494
+ data is floor-normalized. Examples include 'm', 'mm', 'ft'.
495
+ (Default: 'm' for meters).
496
+
497
+ Properties:
498
+ * data_collections
499
+ * faces
500
+ * legend_parameters
501
+ * simulation_step
502
+ * normalize
503
+ * geo_unit
504
+ * matched_flat_faces
505
+ * matched_data
506
+ * matched_values
507
+ * matched_flat_geometry
508
+ * matched_flat_areas
509
+ * graphic_container
510
+ * title_text
511
+ * data_type_text
512
+ * data_type
513
+ * unit
514
+ * analysis_period
515
+ * min_point
516
+ * max_point
517
+ """
518
+ __slots__ = ('_faces',)
519
+
520
+ def __init__(self, data_collections, faces, legend_parameters=None,
521
+ simulation_step=None, normalize=True, geo_unit='m'):
522
+ """Initialize ColorFace."""
523
+ # initialize the base object
524
+ _ColorObject.__init__(self, data_collections, legend_parameters,
525
+ simulation_step, geo_unit)
526
+ for coll in self._data_collections:
527
+ assert 'Surface' in coll.header.metadata, 'ColorFace data collection ' \
528
+ 'does not have metadata associated with Surfaces.'
529
+
530
+ try: # check the input faces
531
+ faces = tuple(faces)
532
+ except TypeError:
533
+ raise TypeError('Input faces must be an array. Got {}.'.format(type(faces)))
534
+ assert len(faces) > 0, 'ColorFaces must have at least one face.'
535
+ self._faces = faces
536
+ self._calculate_min_max(faces)
537
+
538
+ # match the faces with the data collections
539
+ self._matched_objects = match_faces_to_data(data_collections, faces)
540
+ if len(self._matched_objects) == 0:
541
+ raise ValueError('None of the ColorFace data collections could be '
542
+ 'matched to the input faces')
543
+
544
+ # assign the normalize property
545
+ self.normalize = normalize
546
+
547
+ @property
548
+ def faces(self):
549
+ """Get the honeybee Faces, Apertures, Doors and Shades assigned to this object.
550
+ """
551
+ return self._faces
552
+
553
+ @property
554
+ def normalize(self):
555
+ """Get or set a boolean for whether results are normalized by face/sub-face area.
556
+ """
557
+ return self._normalize
558
+
559
+ @normalize.setter
560
+ def normalize(self, value):
561
+ self._normalize = bool(value)
562
+
563
+ @property
564
+ def matched_flat_faces(self):
565
+ """Get a tuple of honeybee objects that have been matched to the data."""
566
+ return tuple(obj[0] for obj in self._matched_objects)
567
+
568
+ @property
569
+ def matched_data(self):
570
+ """Get a tuple of data collections that have been matched to the flat_faces."""
571
+ return tuple(obj[1] for obj in self._matched_objects)
572
+
573
+ @property
574
+ def matched_values(self):
575
+ """Get an array of numbers that correspond to the matched_flat_faces.
576
+
577
+ These values are derived from the data_collections but they will be
578
+ averaged/totaled and normalized by the face/sub-face area depending on the
579
+ other inputs to this object.
580
+ """
581
+ if self._simulation_step is not None: # specific index from all collections
582
+ if self._base_type.normalized_type is None or not self._normalize:
583
+ return tuple(obj[1][self._simulation_step] for obj in
584
+ self._matched_objects)
585
+ else: # normalize the data by the face area
586
+ vals = []
587
+ for obj, f_area in zip(self._matched_objects, self.matched_flat_areas):
588
+ vals.append(obj[1][self._simulation_step] / f_area)
589
+ return vals
590
+ else: # average or total the data based on data type
591
+ if self._base_type.normalized_type is None or not self._normalize:
592
+ if self._base_type.cumulative:
593
+ return tuple(obj[1].total for obj in self._matched_objects)
594
+ else:
595
+ return tuple(obj[1].average for obj in self._matched_objects)
596
+ else: # normalize the data by face area
597
+ vals = []
598
+ zip_obj = zip(self._matched_objects, self.matched_flat_areas)
599
+ if self._base_type.cumulative: # divide total values by face area
600
+ for obj, f_area in zip_obj:
601
+ vals.append(obj[1].total / f_area)
602
+ else: # divide average values by face area
603
+ for obj, f_area in zip_obj:
604
+ vals.append(obj[1].average / f_area)
605
+ return vals
606
+
607
+ @property
608
+ def matched_flat_geometry(self):
609
+ """Get non-nested array of faces/sub-faces on this object.
610
+
611
+ The geometries here align with the attributes and graphic_container colors.
612
+ """
613
+ return tuple(face.geometry if not isinstance(face, Face)
614
+ else face.punched_geometry for face in self.matched_flat_faces)
615
+
616
+ @property
617
+ def matched_flat_areas(self):
618
+ """Get a list numbers for the area of each of the matched_flat_faces.
619
+
620
+ These areas will always be in either square meters or square feet
621
+ depending on whether the geo_unit is either SI or IP. They also use
622
+ punched geometry in the case of a Face with child Apertures.
623
+ """
624
+ if self._geo_unit in ('m', 'ft'): # no need to do unit conversions
625
+ return [face.area for face in self.matched_flat_geometry]
626
+ elif self._geo_unit == 'mm': # convert to meters
627
+ return [face.area / 1000000.0 for face in self.matched_flat_geometry]
628
+ elif self._geo_unit == 'in': # convert to feet
629
+ return [face.area / 144.0 for face in self.matched_flat_geometry]
630
+ else: # assume it's cm; convert to meters
631
+ return [face.area / 10000.0 for face in self.matched_flat_geometry]
632
+
633
+ @property
634
+ def graphic_container(self):
635
+ """Get a ladybug GraphicContainer that relates to this object.
636
+
637
+ The GraphicContainer possesses almost all things needed to visualize the
638
+ ColorFace object including the legend, value_colors, lower_title_location,
639
+ upper_title_location, etc.
640
+ """
641
+ return GraphicContainer(
642
+ self.matched_values, self.min_point, self.max_point,
643
+ self.legend_parameters, self.data_type, str(self.unit))
644
+
645
+ def __repr__(self):
646
+ """Color Face representation."""
647
+ return 'Color Face: [{} Objects] [{}]'.format(
648
+ len(self._matched_objects), self._base_collection.header)