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,550 @@
1
+ # coding=utf-8
2
+ """Complete definition of ventilation in a simulation, including schedule and load."""
3
+ from __future__ import division
4
+
5
+ from honeybee._lockable import lockable
6
+ from honeybee.typing import float_positive, valid_string
7
+
8
+ from ._base import _LoadBase
9
+ from ..schedule.ruleset import ScheduleRuleset
10
+ from ..schedule.fixedinterval import ScheduleFixedInterval
11
+ from ..reader import parse_idf_string
12
+ from ..writer import generate_idf_string
13
+ from ..properties.extension import VentilationProperties
14
+
15
+ import honeybee_energy.lib.scheduletypelimits as _type_lib
16
+
17
+
18
+ @lockable
19
+ class Ventilation(_LoadBase):
20
+ """A complete definition of ventilation, including schedules and load.
21
+
22
+ Note the the 4 ventilation types (flow_per_person, flow_per_area, flow_per_zone,
23
+ and air_changes_per_hour) are ultimately added together to yield the ventilation
24
+ design flow rate used in the simulation.
25
+
26
+ Args:
27
+ identifier: Text string for a unique Ventilation ID. Must be < 100 characters
28
+ and not contain any EnergyPlus special characters. This will be used to
29
+ identify the object across a model and in the exported IDF.
30
+ flow_per_person: A numerical value for the intensity of ventilation
31
+ in m3/s per person. Note that setting this value here does not mean
32
+ that ventilation is varied based on real-time occupancy but rather
33
+ that the design level of ventilation is determined using this value
34
+ and the People object of the zone. To vary ventilation in real time,
35
+ the ventilation schedule should be used. Most ventilation standards
36
+ support that a value of 0.01 m3/s (10 L/s or ~20 cfm) per person is
37
+ sufficient to remove odors. Accordingly, setting this value to 0.01
38
+ and using 0 for the following ventilation terms will often be suitable
39
+ for many applications. Default: 0.
40
+ flow_per_area: A numerical value for the intensity of ventilation in m3/s
41
+ per square meter of floor area. Default: 0.
42
+ flow_per_zone: A numerical value for the design level of ventilation
43
+ in m3/s for the entire zone. Default: 0.
44
+ air_changes_per_hour: A numerical value for the design level of ventilation
45
+ in air changes per hour (ACH) for the entire zone. This is particularly
46
+ helpful for hospitals, where ventilation standards are often given
47
+ in ACH. (Default: 0).
48
+ schedule: An optional ScheduleRuleset or ScheduleFixedInterval for the
49
+ ventilation over the course of the year. The type of this schedule
50
+ should be Fractional and the fractional values will get multiplied by
51
+ the total design flow rate (determined from the sum or max of the other
52
+ 4 fields) to yield a complete ventilation profile. Setting
53
+ this schedule to be the occupancy schedule of the zone will mimic demand
54
+ controlled ventilation. If None, the design level of ventilation will
55
+ be used throughout all timesteps of the simulation. (Default: None).
56
+ method: Text to set how the different ventilation criteria are reconciled
57
+ against one another. Choose from the options below. (Default: Sum).
58
+
59
+ * Sum
60
+ * Max
61
+
62
+ Properties:
63
+ * identifier
64
+ * display_name
65
+ * flow_per_person
66
+ * flow_per_area
67
+ * flow_per_zone
68
+ * air_changes_per_hour
69
+ * schedule
70
+ * method
71
+ * user_data
72
+ """
73
+ __slots__ = ('_flow_per_person', '_flow_per_area', '_flow_per_zone',
74
+ '_air_changes_per_hour', '_schedule', '_method')
75
+ METHODS = ('Sum', 'Max')
76
+
77
+ def __init__(self, identifier, flow_per_person=0, flow_per_area=0, flow_per_zone=0,
78
+ air_changes_per_hour=0, schedule=None, method='Sum'):
79
+ """Initialize Ventilation."""
80
+ _LoadBase.__init__(self, identifier)
81
+ self.flow_per_person = flow_per_person
82
+ self.flow_per_area = flow_per_area
83
+ self.flow_per_zone = flow_per_zone
84
+ self.air_changes_per_hour = air_changes_per_hour
85
+ self.schedule = schedule
86
+ self.method = method
87
+ self._properties = VentilationProperties(self)
88
+
89
+ @property
90
+ def flow_per_person(self):
91
+ """Get or set the intensity of ventilation in m3/s per person.
92
+
93
+ Note that setting this value here does not mean that ventilation is varied
94
+ based on real-time occupancy but rather that the design level of ventilation
95
+ is determined using this value and the People object of the zone. To vary
96
+ ventilation in real time, the ventilation schedule should be used or demand
97
+ controlled ventilation options should be set on the HVAC system.
98
+
99
+ Most ventilation standards support that a value of 0.01 m3/s (10 L/s or ~20 cfm)
100
+ per person is sufficient to remove odors. Accordingly, setting this value to
101
+ 0.01 and using 0 for the following ventilation terms will often be suitable
102
+ for many applications.
103
+ """
104
+ return self._flow_per_person
105
+
106
+ @flow_per_person.setter
107
+ def flow_per_person(self, value):
108
+ self._flow_per_person = float_positive(value, 'ventilation flow per person') if \
109
+ value is not None else 0
110
+
111
+ @property
112
+ def flow_per_area(self):
113
+ """Get or set the ventilation in m3/s per square meter of zone floor area."""
114
+ return self._flow_per_area
115
+
116
+ @flow_per_area.setter
117
+ def flow_per_area(self, value):
118
+ self._flow_per_area = float_positive(value, 'ventilation flow per area') if \
119
+ value is not None else 0
120
+
121
+ @property
122
+ def flow_per_zone(self):
123
+ """Get or set the ventilation in m3/s per zone."""
124
+ return self._flow_per_zone
125
+
126
+ @flow_per_zone.setter
127
+ def flow_per_zone(self, value):
128
+ self._flow_per_zone = float_positive(value, 'ventilation flow per zone')if \
129
+ value is not None else 0
130
+
131
+ @property
132
+ def air_changes_per_hour(self):
133
+ """Get or set the ventilation in air changes per hour (ACH)."""
134
+ return self._air_changes_per_hour
135
+
136
+ @air_changes_per_hour.setter
137
+ def air_changes_per_hour(self, value):
138
+ self._air_changes_per_hour = \
139
+ float_positive(value, 'ventilation air changes per hour') if \
140
+ value is not None else 0
141
+
142
+ @property
143
+ def schedule(self):
144
+ """Get or set a ScheduleRuleset or ScheduleFixedInterval for ventilation."""
145
+ return self._schedule
146
+
147
+ @schedule.setter
148
+ def schedule(self, value):
149
+ if value is not None:
150
+ assert isinstance(value, (ScheduleRuleset, ScheduleFixedInterval)), \
151
+ 'Expected ScheduleRuleset or ScheduleFixedInterval for Ventilation ' \
152
+ 'schedule. Got {}.'.format(type(value))
153
+ self._check_fractional_schedule_type(value, 'Ventilation')
154
+ value.lock() # lock editing in case schedule has multiple references
155
+ self._schedule = value
156
+
157
+ @property
158
+ def method(self):
159
+ """Text to set how the ventilation criteria are reconciled against one another.
160
+
161
+ Choose from the options below.
162
+
163
+ * Sum
164
+ * Max
165
+ """
166
+ return self._method
167
+
168
+ @method.setter
169
+ def method(self, value):
170
+ clean_input = valid_string(value).lower()
171
+ for key in self.METHODS:
172
+ if key.lower() == clean_input:
173
+ value = key
174
+ break
175
+ else:
176
+ raise ValueError(
177
+ 'Method {} is not recognized.\nChoose from the '
178
+ 'following:\n{}'.format(value, self.METHODS))
179
+ self._method = value
180
+
181
+ @classmethod
182
+ def from_idf(cls, idf_string, schedule_dict):
183
+ """Create an Ventilation object from an EnergyPlus IDF text string.
184
+
185
+ Args:
186
+ idf_string: A text string fully describing an EnergyPlus
187
+ DesignSpecification:OutdoorAir definition.
188
+ schedule_dict: A dictionary with schedule identifiers as keys and honeybee
189
+ schedule objects as values (either ScheduleRuleset or
190
+ ScheduleFixedInterval). These will be used to assign the schedules to
191
+ the Ventilation object.
192
+
193
+ Returns:
194
+ ventilation -- An Ventilation object loaded from the idf_string.
195
+ """
196
+ # check the inputs
197
+ ep_strs = parse_idf_string(idf_string, 'DesignSpecification:OutdoorAir,')
198
+
199
+ # extract the numerical properties from the string
200
+ person = 0.00944
201
+ area = 0
202
+ zone = 0
203
+ ach = 0
204
+ try:
205
+ person = ep_strs[2] if ep_strs[2] != '' else 0.00944
206
+ area = ep_strs[3] if ep_strs[3] != '' else 0
207
+ zone = ep_strs[4] if ep_strs[4] != '' else 0
208
+ ach = ep_strs[5] if ep_strs[5] != '' else 0
209
+ except IndexError:
210
+ pass # shorter ventilation definition lacking values
211
+
212
+ # change the values to 0 if 'Sum' method is not used
213
+ method = 'Sum'
214
+ try:
215
+ if ep_strs[1].lower() == 'sum':
216
+ pass
217
+ elif ep_strs[1].lower() == 'maximum':
218
+ method = 'Max'
219
+ elif ep_strs[1].lower() == 'flow/person':
220
+ area, zone, ach = 0, 0, 0
221
+ elif ep_strs[1].lower() == 'flow/area':
222
+ person, zone, ach = 0, 0, 0
223
+ elif ep_strs[1].lower() == 'flow/zone':
224
+ person, area, ach = 0, 0, 0
225
+ elif ep_strs[1].lower() == 'airchanges/hour':
226
+ person, area, zone = 0, 0, 0
227
+ else:
228
+ raise ValueError('DesignSpecification:OutdoorAir {} method '
229
+ 'is not supported by honeybee.'.format(ep_strs[1]))
230
+ except IndexError: # EnergyPlus defaults to flow/person
231
+ area, zone, ach = 0, 0, 0
232
+
233
+ # extract the schedules from the string
234
+ try:
235
+ try:
236
+ sched = schedule_dict[ep_strs[6]] if ep_strs[6] != '' else None
237
+ except KeyError as e:
238
+ raise ValueError('Failed to find {} in the schedule_dict.'.format(e))
239
+ except IndexError: # No schedule given
240
+ sched = None
241
+
242
+ # return the object and the zone id for the object
243
+ obj_id = ep_strs[0].split('..')[0]
244
+ ventilation = cls(obj_id, person, area, zone, ach, sched, method)
245
+ return ventilation
246
+
247
+ @classmethod
248
+ def from_dict(cls, data):
249
+ """Create a Ventilation object from a dictionary.
250
+
251
+ Note that the dictionary must be a non-abridged version for this classmethod
252
+ to work.
253
+
254
+ Args:
255
+ data: A Ventilation dictionary in following the format below.
256
+
257
+ .. code-block:: python
258
+
259
+ {
260
+ "type": 'Ventilation',
261
+ "identifier": 'Office_Ventilation_0010_000050_0_0',
262
+ "display_name": 'Office Ventilation',
263
+ "flow_per_person": 0.01, # flow per person
264
+ "flow_per_area": 0.0005, # flow per square meter of floor area
265
+ "flow_per_zone": 0, # flow per zone
266
+ "air_changes_per_hour": 0, # air changes per hour
267
+ "schedule": {}, # ScheduleRuleset/ScheduleFixedInterval dictionary
268
+ "method": "Sum"
269
+ }
270
+ """
271
+ assert data['type'] == 'Ventilation', \
272
+ 'Expected Ventilation dictionary. Got {}.'.format(data['type'])
273
+ person, area, zone, ach, method = cls._optional_dict_keys(data)
274
+ sched = cls._get_schedule_from_dict(data['schedule']) if 'schedule' in data and \
275
+ data['schedule'] is not None else None
276
+ new_obj = cls(data['identifier'], person, area, zone, ach, sched, method)
277
+ if 'display_name' in data and data['display_name'] is not None:
278
+ new_obj.display_name = data['display_name']
279
+ if 'user_data' in data and data['user_data'] is not None:
280
+ new_obj.user_data = data['user_data']
281
+ if 'properties' in data and data['properties'] is not None:
282
+ new_obj.properties._load_extension_attr_from_dict(data['properties'])
283
+ return new_obj
284
+
285
+ @classmethod
286
+ def from_dict_abridged(cls, data, schedule_dict):
287
+ """Create a Ventilation object from an abridged dictionary.
288
+
289
+ Args:
290
+ data: A VentilationAbridged dictionary in following the format below.
291
+ schedule_dict: A dictionary with schedule identifiers as keys and
292
+ honeybee schedule objects as values (either ScheduleRuleset or
293
+ ScheduleFixedInterval). These will be used to assign the schedules
294
+ to the Ventilation object.
295
+
296
+ .. code-block:: python
297
+
298
+ {
299
+ "type": 'VentilationAbridged',
300
+ "identifier": 'Office_Ventilation_0010_000050_0_0',
301
+ "display_name": 'Office Ventilation',
302
+ "flow_per_person": 0.01, # flow per person
303
+ "flow_per_area": 0.0005, # flow per square meter of floor area
304
+ "flow_per_zone": 0, # flow per zone
305
+ "air_changes_per_hour": 0, # air changes per hour
306
+ "schedule": "Office Ventilation Schedule" # Schedule identifier
307
+ }
308
+ """
309
+ assert data['type'] == 'VentilationAbridged', \
310
+ 'Expected VentilationAbridged dictionary. Got {}.'.format(data['type'])
311
+ person, area, zone, ach, method = cls._optional_dict_keys(data)
312
+ sched = None
313
+ if 'schedule' in data and data['schedule'] is not None:
314
+ try:
315
+ sched = schedule_dict[data['schedule']]
316
+ except KeyError as e:
317
+ raise ValueError('Failed to find {} in the schedule_dict.'.format(e))
318
+ new_obj = cls(data['identifier'], person, area, zone, ach, sched, method)
319
+ if 'display_name' in data and data['display_name'] is not None:
320
+ new_obj.display_name = data['display_name']
321
+ if 'user_data' in data and data['user_data'] is not None:
322
+ new_obj.user_data = data['user_data']
323
+ if 'properties' in data and data['properties'] is not None:
324
+ new_obj.properties._load_extension_attr_from_dict(data['properties'])
325
+ return new_obj
326
+
327
+ def to_idf(self, zone_identifier):
328
+ """IDF string representation of Ventilation object.
329
+
330
+ Note that this method only outputs a single string for the DesignSpecification:
331
+ OutdoorAir object and, to write everything needed to describe the object
332
+ into an IDF, this object's schedule must also be written.
333
+
334
+ Args:
335
+ zone_identifier: Text for the zone identifier that the Ventilation
336
+ object is assigned to.
337
+
338
+ .. code-block:: shell
339
+
340
+ DesignSpecification:OutdoorAir
341
+ ZoneOAData, !- Name
342
+ Sum, !- Outdoor Air Method
343
+ 0.00944, !- Outdoor Air Flow per Person {m3/s}
344
+ 0.00305, !- Outdoor Air Flow per Zone Floor Area {m3/s-m2}
345
+ , !- Outdoor Air Flow per Zone {m3/s}
346
+ , !- Outdoor Air Flow Air Changes per Hour
347
+ OARequirements Sched; !- Outdoor Air Schedule Name
348
+ """
349
+ sched = self.schedule.identifier if self.schedule is not None else ''
350
+ vent_obj_identifier = '{}..{}'.format(self.identifier, zone_identifier)
351
+ method = 'Maximum' if self.method == 'Max' else 'Sum'
352
+ values = (
353
+ vent_obj_identifier, method,
354
+ self.flow_per_person, self.flow_per_area,
355
+ self.flow_per_zone, self.air_changes_per_hour, sched
356
+ )
357
+ comments = ('name', 'flow rate method', 'flow per person {m3/s-person}',
358
+ 'flow per floor area {m3/s-m2}', 'flow per zone {m3/s}',
359
+ 'air changes per hour {1/hr}', 'outdoor air schedule name')
360
+ return generate_idf_string('DesignSpecification:OutdoorAir', values, comments)
361
+
362
+ def to_dict(self, abridged=False):
363
+ """Ventilation dictionary representation.
364
+
365
+ Args:
366
+ abridged: Boolean to note whether the full dictionary describing the
367
+ object should be returned (False) or just an abridged version (True),
368
+ which only specifies the identifiers of schedules. Default: False.
369
+ """
370
+ base = {'type': 'Ventilation'} if not abridged \
371
+ else {'type': 'VentilationAbridged'}
372
+ base['identifier'] = self.identifier
373
+ if self.flow_per_person != 0:
374
+ base['flow_per_person'] = self.flow_per_person
375
+ if self.flow_per_area != 0:
376
+ base['flow_per_area'] = self.flow_per_area
377
+ if self.flow_per_zone != 0:
378
+ base['flow_per_zone'] = self.flow_per_zone
379
+ if self.air_changes_per_hour != 0:
380
+ base['air_changes_per_hour'] = self.air_changes_per_hour
381
+ if self.schedule is not None:
382
+ base['schedule'] = self.schedule.to_dict() if not \
383
+ abridged else self.schedule.identifier
384
+ if self.method != 'Sum':
385
+ base['method'] = self.method
386
+ if self._display_name is not None:
387
+ base['display_name'] = self.display_name
388
+ if self._user_data is not None:
389
+ base['user_data'] = self.user_data
390
+ prop_dict = self.properties.to_dict()
391
+ if prop_dict is not None:
392
+ base['properties'] = prop_dict
393
+ return base
394
+
395
+ @staticmethod
396
+ def average(identifier, ventilations, weights=None, timestep_resolution=1):
397
+ """Get a Ventilation object that's an average between other Ventilations.
398
+
399
+ Args:
400
+ identifier: Text string for a unique ID for the new averaged Ventilation.
401
+ Must be < 100 characters and not contain any EnergyPlus special
402
+ characters. This will be used to identify the object across a model
403
+ and in the exported IDF.
404
+ ventilations: A list of Ventilation objects that will be averaged
405
+ together to make a new Ventilation.
406
+ weights: An optional list of fractional numbers with the same length
407
+ as the input ventilations. These will be used to weight each of the
408
+ Ventilation objects in the resulting average. Note that these weights
409
+ can sum to less than 1 in which case the average flow rates
410
+ will assume 0 for the unaccounted fraction of the weights.
411
+ timestep_resolution: An optional integer for the timestep resolution
412
+ at which the schedules will be averaged. Any schedule details
413
+ smaller than this timestep will be lost in the averaging process.
414
+ Default: 1.
415
+ """
416
+ weights, u_weights = \
417
+ Ventilation._check_avg_weights(ventilations, weights, 'Ventilation')
418
+
419
+ # calculate the average values
420
+ person = sum([vent.flow_per_person * w
421
+ for vent, w in zip(ventilations, weights)])
422
+ area = sum([vent.flow_per_area * w
423
+ for vent, w in zip(ventilations, weights)])
424
+ zone = sum([vent.flow_per_zone * w
425
+ for vent, w in zip(ventilations, weights)])
426
+ ach = sum([vent.air_changes_per_hour * w
427
+ for vent, w in zip(ventilations, weights)])
428
+ method = 'Max' if all(vent.method == 'Max' for vent in ventilations) else 'Sum'
429
+
430
+ # calculate the average schedules
431
+ scheds = [vent.schedule for vent in ventilations]
432
+ if all(val is None for val in scheds):
433
+ sched = None
434
+ else:
435
+ full_vent = ScheduleRuleset.from_constant_value(
436
+ 'Full Ventilation', 1, _type_lib.fractional)
437
+ for i, sch in enumerate(scheds):
438
+ if sch is None:
439
+ scheds[i] = full_vent
440
+ sched = Ventilation._average_schedule(
441
+ '{} Schedule'.format(identifier), scheds, u_weights, timestep_resolution)
442
+
443
+ # return the averaged object
444
+ return Ventilation(identifier, person, area, zone, ach, sched, method)
445
+
446
+ @staticmethod
447
+ def combine_room_ventilations(identifier, rooms, timestep_resolution=1):
448
+ """Get a Ventilation object that represents the sum across rooms.
449
+
450
+ In this process of combining ventilation requirements, the following
451
+ rules hold: 1. Total flow rates in m3/s are simply added together. 2. Flow per
452
+ floor area gets recomputed using the floor areas of each room. 3. ACH flow
453
+ gets recomputed using the volumes of each room in the inputs. 4. Flow per
454
+ person gets set based on whichever room has the highest ventilation
455
+ requirement per person.
456
+
457
+ In the case of ventilation schedules, the strictest schedule governs and
458
+ note that the absence of a ventilation schedule means the schedule is
459
+ Always On. So, if one room has a ventilation schedule and the other
460
+ does not, then the schedule essentially gets removed. If each room has
461
+ a different ventilation schedule, then a new schedule will be created
462
+ using the maximum value across the two schedules at each timestep.
463
+
464
+ Args:
465
+ identifier: Text string for a unique ID for the new Ventilation object.
466
+ Must be < 100 characters and not contain any EnergyPlus special
467
+ characters. This will be used to identify the object across a model
468
+ and in the exported IDF.
469
+ rooms: A list of Rooms that will have their Ventilation objects
470
+ combined to make a new Ventilation.
471
+ timestep_resolution: An optional integer for the timestep resolution at
472
+ which conflicting ventilation schedules will be resolved. (Default: 1).
473
+ """
474
+ # compute weights based on floor areas and volumes
475
+ ventilations, floor_areas, volumes = [], [], []
476
+ for room in rooms:
477
+ vent = Ventilation() if room.properties.energy.ventilation is None else \
478
+ room.properties.energy.ventilation
479
+ ventilations.append(vent)
480
+ floor_areas.append(room.floor_area)
481
+ volumes.append(room.volume)
482
+ total_floor = sum(floor_areas)
483
+ total_volume = sum(volumes)
484
+ floor_weights = [ar / total_floor for ar in floor_areas]
485
+ vol_weights = [vol / total_volume for vol in volumes]
486
+
487
+ # calculate the average values
488
+ person = max(vent.flow_per_person for vent in ventilations)
489
+ area = sum([vent.flow_per_area * w
490
+ for vent, w in zip(ventilations, floor_weights)])
491
+ zone = sum(vent.flow_per_zone for vent in ventilations)
492
+ ach = sum([vent.air_changes_per_hour * w
493
+ for vent, w in zip(ventilations, vol_weights)])
494
+ method = 'Max' if all(vent.method == 'Max' for vent in ventilations) else 'Sum'
495
+
496
+ # calculate the average schedules
497
+ scheds = [vent.schedule for vent in ventilations]
498
+ if any(val is None for val in scheds):
499
+ sched = None
500
+ else:
501
+ base_sch = scheds[0]
502
+ if all(sch is base_sch for sch in scheds) or len(set(scheds)) == 1:
503
+ sched = scheds[0]
504
+ else:
505
+ sched = Ventilation._max_schedule(
506
+ '{} Schedule'.format(identifier), scheds, timestep_resolution)
507
+
508
+ # return the averaged object
509
+ return Ventilation(identifier, person, area, zone, ach, sched, method)
510
+
511
+ @staticmethod
512
+ def _optional_dict_keys(data):
513
+ """Get the optional keys from an Ventilation dictionary."""
514
+ person = data['flow_per_person'] if 'flow_per_person' in data else 0
515
+ area = data['flow_per_area'] if 'flow_per_area' in data else 0
516
+ zone = data['flow_per_zone'] if 'flow_per_zone' in data else 0
517
+ ach = data['air_changes_per_hour'] if 'air_changes_per_hour' in data else 0
518
+ method = data['method'] if 'method' in data else 'Sum'
519
+ return person, area, zone, ach, method
520
+
521
+ def __key(self):
522
+ """A tuple based on the object properties, useful for hashing."""
523
+ return (
524
+ self.identifier, self.flow_per_person, self.flow_per_area,
525
+ self.flow_per_zone, self.air_changes_per_hour, hash(self.schedule),
526
+ self.method
527
+ )
528
+
529
+ def __hash__(self):
530
+ return hash(self.__key())
531
+
532
+ def __eq__(self, other):
533
+ return isinstance(other, Ventilation) and self.__key() == other.__key()
534
+
535
+ def __ne__(self, other):
536
+ return not self.__eq__(other)
537
+
538
+ def __copy__(self):
539
+ new_obj = Ventilation(
540
+ self.identifier, self.flow_per_person, self.flow_per_area,
541
+ self.flow_per_zone, self.air_changes_per_hour, self.schedule, self.method)
542
+ new_obj._display_name = self._display_name
543
+ new_obj._user_data = None if self._user_data is None else self._user_data.copy()
544
+ new_obj._properties._duplicate_extension_attr(self._properties)
545
+ return new_obj
546
+
547
+ def __repr__(self):
548
+ return 'Ventilation: {} [{} m3/s-person] [{} m3/s-m2] [{} ACH]'.format(
549
+ self.display_name, round(self.flow_per_person, 4),
550
+ round(self.flow_per_area, 6), round(self.air_changes_per_hour, 2))
@@ -0,0 +1 @@
1
+ """honeybee-energy materials."""