honeybee-energy 1.116.106__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- honeybee_energy/__init__.py +24 -0
- honeybee_energy/__main__.py +4 -0
- honeybee_energy/_extend_honeybee.py +145 -0
- honeybee_energy/altnumber.py +21 -0
- honeybee_energy/baseline/__init__.py +2 -0
- honeybee_energy/baseline/create.py +608 -0
- honeybee_energy/baseline/data/__init__.py +1 -0
- honeybee_energy/baseline/data/constructions.csv +64 -0
- honeybee_energy/baseline/data/fen_ratios.csv +15 -0
- honeybee_energy/baseline/data/lpd_building.csv +21 -0
- honeybee_energy/baseline/data/pci_2016.csv +22 -0
- honeybee_energy/baseline/data/pci_2019.csv +22 -0
- honeybee_energy/baseline/data/pci_2022.csv +22 -0
- honeybee_energy/baseline/data/shw.csv +21 -0
- honeybee_energy/baseline/pci.py +512 -0
- honeybee_energy/baseline/result.py +371 -0
- honeybee_energy/boundarycondition.py +128 -0
- honeybee_energy/cli/__init__.py +69 -0
- honeybee_energy/cli/baseline.py +475 -0
- honeybee_energy/cli/edit.py +327 -0
- honeybee_energy/cli/lib.py +1154 -0
- honeybee_energy/cli/result.py +810 -0
- honeybee_energy/cli/setconfig.py +124 -0
- honeybee_energy/cli/settings.py +569 -0
- honeybee_energy/cli/simulate.py +380 -0
- honeybee_energy/cli/translate.py +1714 -0
- honeybee_energy/cli/validate.py +224 -0
- honeybee_energy/config.json +11 -0
- honeybee_energy/config.py +842 -0
- honeybee_energy/construction/__init__.py +1 -0
- honeybee_energy/construction/_base.py +374 -0
- honeybee_energy/construction/air.py +325 -0
- honeybee_energy/construction/dictutil.py +89 -0
- honeybee_energy/construction/dynamic.py +607 -0
- honeybee_energy/construction/opaque.py +460 -0
- honeybee_energy/construction/shade.py +319 -0
- honeybee_energy/construction/window.py +1096 -0
- honeybee_energy/construction/windowshade.py +847 -0
- honeybee_energy/constructionset.py +1655 -0
- honeybee_energy/dictutil.py +56 -0
- honeybee_energy/generator/__init__.py +5 -0
- honeybee_energy/generator/loadcenter.py +204 -0
- honeybee_energy/generator/pv.py +535 -0
- honeybee_energy/hvac/__init__.py +21 -0
- honeybee_energy/hvac/_base.py +124 -0
- honeybee_energy/hvac/_template.py +270 -0
- honeybee_energy/hvac/allair/__init__.py +22 -0
- honeybee_energy/hvac/allair/_base.py +349 -0
- honeybee_energy/hvac/allair/furnace.py +168 -0
- honeybee_energy/hvac/allair/psz.py +131 -0
- honeybee_energy/hvac/allair/ptac.py +163 -0
- honeybee_energy/hvac/allair/pvav.py +109 -0
- honeybee_energy/hvac/allair/vav.py +128 -0
- honeybee_energy/hvac/detailed.py +337 -0
- honeybee_energy/hvac/doas/__init__.py +28 -0
- honeybee_energy/hvac/doas/_base.py +345 -0
- honeybee_energy/hvac/doas/fcu.py +127 -0
- honeybee_energy/hvac/doas/radiant.py +329 -0
- honeybee_energy/hvac/doas/vrf.py +81 -0
- honeybee_energy/hvac/doas/wshp.py +91 -0
- honeybee_energy/hvac/heatcool/__init__.py +23 -0
- honeybee_energy/hvac/heatcool/_base.py +177 -0
- honeybee_energy/hvac/heatcool/baseboard.py +61 -0
- honeybee_energy/hvac/heatcool/evapcool.py +72 -0
- honeybee_energy/hvac/heatcool/fcu.py +92 -0
- honeybee_energy/hvac/heatcool/gasunit.py +53 -0
- honeybee_energy/hvac/heatcool/radiant.py +269 -0
- honeybee_energy/hvac/heatcool/residential.py +77 -0
- honeybee_energy/hvac/heatcool/vrf.py +54 -0
- honeybee_energy/hvac/heatcool/windowac.py +70 -0
- honeybee_energy/hvac/heatcool/wshp.py +62 -0
- honeybee_energy/hvac/idealair.py +699 -0
- honeybee_energy/internalmass.py +310 -0
- honeybee_energy/lib/__init__.py +1 -0
- honeybee_energy/lib/_loadconstructions.py +194 -0
- honeybee_energy/lib/_loadconstructionsets.py +117 -0
- honeybee_energy/lib/_loadmaterials.py +83 -0
- honeybee_energy/lib/_loadprogramtypes.py +125 -0
- honeybee_energy/lib/_loadschedules.py +87 -0
- honeybee_energy/lib/_loadtypelimits.py +64 -0
- honeybee_energy/lib/constructions.py +207 -0
- honeybee_energy/lib/constructionsets.py +95 -0
- honeybee_energy/lib/materials.py +67 -0
- honeybee_energy/lib/programtypes.py +125 -0
- honeybee_energy/lib/schedules.py +61 -0
- honeybee_energy/lib/scheduletypelimits.py +31 -0
- honeybee_energy/load/__init__.py +1 -0
- honeybee_energy/load/_base.py +190 -0
- honeybee_energy/load/daylight.py +397 -0
- honeybee_energy/load/dictutil.py +47 -0
- honeybee_energy/load/equipment.py +771 -0
- honeybee_energy/load/hotwater.py +543 -0
- honeybee_energy/load/infiltration.py +460 -0
- honeybee_energy/load/lighting.py +480 -0
- honeybee_energy/load/people.py +497 -0
- honeybee_energy/load/process.py +472 -0
- honeybee_energy/load/setpoint.py +816 -0
- honeybee_energy/load/ventilation.py +550 -0
- honeybee_energy/material/__init__.py +1 -0
- honeybee_energy/material/_base.py +166 -0
- honeybee_energy/material/dictutil.py +59 -0
- honeybee_energy/material/frame.py +367 -0
- honeybee_energy/material/gas.py +1087 -0
- honeybee_energy/material/glazing.py +854 -0
- honeybee_energy/material/opaque.py +1351 -0
- honeybee_energy/material/shade.py +1360 -0
- honeybee_energy/measure.py +472 -0
- honeybee_energy/programtype.py +723 -0
- honeybee_energy/properties/__init__.py +1 -0
- honeybee_energy/properties/aperture.py +333 -0
- honeybee_energy/properties/door.py +342 -0
- honeybee_energy/properties/extension.py +244 -0
- honeybee_energy/properties/face.py +274 -0
- honeybee_energy/properties/model.py +2640 -0
- honeybee_energy/properties/room.py +1747 -0
- honeybee_energy/properties/shade.py +314 -0
- honeybee_energy/properties/shademesh.py +262 -0
- honeybee_energy/reader.py +48 -0
- honeybee_energy/result/__init__.py +1 -0
- honeybee_energy/result/colorobj.py +648 -0
- honeybee_energy/result/emissions.py +290 -0
- honeybee_energy/result/err.py +101 -0
- honeybee_energy/result/eui.py +100 -0
- honeybee_energy/result/generation.py +160 -0
- honeybee_energy/result/loadbalance.py +890 -0
- honeybee_energy/result/match.py +202 -0
- honeybee_energy/result/osw.py +90 -0
- honeybee_energy/result/rdd.py +59 -0
- honeybee_energy/result/zsz.py +190 -0
- honeybee_energy/run.py +1577 -0
- honeybee_energy/schedule/__init__.py +1 -0
- honeybee_energy/schedule/day.py +626 -0
- honeybee_energy/schedule/dictutil.py +59 -0
- honeybee_energy/schedule/fixedinterval.py +1012 -0
- honeybee_energy/schedule/rule.py +619 -0
- honeybee_energy/schedule/ruleset.py +1867 -0
- honeybee_energy/schedule/typelimit.py +310 -0
- honeybee_energy/shw.py +315 -0
- honeybee_energy/simulation/__init__.py +1 -0
- honeybee_energy/simulation/control.py +214 -0
- honeybee_energy/simulation/daylightsaving.py +185 -0
- honeybee_energy/simulation/dictutil.py +51 -0
- honeybee_energy/simulation/output.py +646 -0
- honeybee_energy/simulation/parameter.py +606 -0
- honeybee_energy/simulation/runperiod.py +443 -0
- honeybee_energy/simulation/shadowcalculation.py +295 -0
- honeybee_energy/simulation/sizing.py +546 -0
- honeybee_energy/ventcool/__init__.py +5 -0
- honeybee_energy/ventcool/_crack_data.py +91 -0
- honeybee_energy/ventcool/afn.py +289 -0
- honeybee_energy/ventcool/control.py +269 -0
- honeybee_energy/ventcool/crack.py +126 -0
- honeybee_energy/ventcool/fan.py +493 -0
- honeybee_energy/ventcool/opening.py +365 -0
- honeybee_energy/ventcool/simulation.py +314 -0
- honeybee_energy/writer.py +1078 -0
- honeybee_energy-1.116.106.dist-info/METADATA +113 -0
- honeybee_energy-1.116.106.dist-info/RECORD +162 -0
- honeybee_energy-1.116.106.dist-info/WHEEL +5 -0
- honeybee_energy-1.116.106.dist-info/entry_points.txt +2 -0
- honeybee_energy-1.116.106.dist-info/licenses/LICENSE +661 -0
- honeybee_energy-1.116.106.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,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."""
|