honeybee-energy 1.116.64__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.

Potentially problematic release.


This version of honeybee-energy might be problematic. Click here for more details.

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