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,1747 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
"""Room Energy Properties."""
|
|
3
|
+
# import honeybee-core and ladybug-geometry modules
|
|
4
|
+
from ladybug_geometry.geometry2d import Vector2D
|
|
5
|
+
from ladybug_geometry.geometry3d import Vector3D, Point3D, Polyline3D, Face3D, Polyface3D
|
|
6
|
+
from honeybee.checkdup import is_equivalent
|
|
7
|
+
from honeybee.boundarycondition import Outdoors, Ground, Surface, boundary_conditions
|
|
8
|
+
from honeybee.facetype import Wall, RoofCeiling, Floor, AirBoundary
|
|
9
|
+
from honeybee.units import conversion_factor_to_meters
|
|
10
|
+
from honeybee.aperture import Aperture
|
|
11
|
+
|
|
12
|
+
# import the main types of assignable objects
|
|
13
|
+
from ..programtype import ProgramType
|
|
14
|
+
from ..constructionset import ConstructionSet
|
|
15
|
+
from ..load.people import People
|
|
16
|
+
from ..load.lighting import Lighting
|
|
17
|
+
from ..load.equipment import ElectricEquipment, GasEquipment
|
|
18
|
+
from ..load.hotwater import ServiceHotWater
|
|
19
|
+
from ..load.infiltration import Infiltration
|
|
20
|
+
from ..load.ventilation import Ventilation
|
|
21
|
+
from ..load.setpoint import Setpoint
|
|
22
|
+
from ..load.daylight import DaylightingControl
|
|
23
|
+
from ..load.process import Process
|
|
24
|
+
from ..internalmass import InternalMass
|
|
25
|
+
from ..ventcool.fan import VentilationFan
|
|
26
|
+
from ..ventcool.control import VentilationControl
|
|
27
|
+
from ..ventcool.crack import AFNCrack
|
|
28
|
+
from ..ventcool.opening import VentilationOpening
|
|
29
|
+
from ..construction.opaque import OpaqueConstruction
|
|
30
|
+
from ..material.opaque import EnergyMaterialVegetation
|
|
31
|
+
|
|
32
|
+
# import all hvac and shw modules
|
|
33
|
+
from ..hvac import HVAC_TYPES_DICT
|
|
34
|
+
from ..hvac._base import _HVACSystem
|
|
35
|
+
from ..hvac.idealair import IdealAirSystem
|
|
36
|
+
from ..hvac.heatcool._base import _HeatCoolBase
|
|
37
|
+
from ..hvac.doas._base import _DOASBase
|
|
38
|
+
from ..shw import SHWSystem
|
|
39
|
+
|
|
40
|
+
# import the libraries of constructionsets and programs
|
|
41
|
+
from ..lib.constructionsets import generic_construction_set
|
|
42
|
+
from ..lib.schedules import always_on
|
|
43
|
+
from ..lib.programtypes import plenum_program
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class RoomEnergyProperties(object):
|
|
47
|
+
"""Energy Properties for Honeybee Room.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
host: A honeybee_core Room object that hosts these properties.
|
|
51
|
+
program_type: A honeybee ProgramType object to specify all default
|
|
52
|
+
schedules and loads for the Room. If None, the Room will have a Plenum
|
|
53
|
+
program (with no loads or setpoints). Default: None.
|
|
54
|
+
construction_set: A honeybee ConstructionSet object to specify all
|
|
55
|
+
default constructions for the Faces of the Room. If None, the Room
|
|
56
|
+
will use the honeybee default construction set, which is not
|
|
57
|
+
representative of a particular building code or climate zone.
|
|
58
|
+
Default: None.
|
|
59
|
+
hvac: A honeybee HVAC object (such as an IdealAirSystem) that specifies
|
|
60
|
+
how the Room is conditioned. If None, it will be assumed that the
|
|
61
|
+
Room is not conditioned. Default: None.
|
|
62
|
+
|
|
63
|
+
Properties:
|
|
64
|
+
* host
|
|
65
|
+
* program_type
|
|
66
|
+
* construction_set
|
|
67
|
+
* hvac
|
|
68
|
+
* shw
|
|
69
|
+
* people
|
|
70
|
+
* lighting
|
|
71
|
+
* electric_equipment
|
|
72
|
+
* gas_equipment
|
|
73
|
+
* service_hot_water
|
|
74
|
+
* infiltration
|
|
75
|
+
* ventilation
|
|
76
|
+
* setpoint
|
|
77
|
+
* daylighting_control
|
|
78
|
+
* window_vent_control
|
|
79
|
+
* fans
|
|
80
|
+
* process_loads
|
|
81
|
+
* total_process_load
|
|
82
|
+
* internal_masses
|
|
83
|
+
* is_conditioned
|
|
84
|
+
* has_overridden_loads
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
__slots__ = (
|
|
88
|
+
'_host', '_program_type', '_construction_set', '_hvac', '_shw',
|
|
89
|
+
'_people', '_lighting', '_electric_equipment', '_gas_equipment',
|
|
90
|
+
'_service_hot_water', '_infiltration', '_ventilation', '_setpoint',
|
|
91
|
+
'_daylighting_control', '_window_vent_control', '_fans',
|
|
92
|
+
'_internal_masses', '_process_loads'
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
def __init__(
|
|
96
|
+
self, host, program_type=None, construction_set=None, hvac=None, shw=None):
|
|
97
|
+
"""Initialize Room energy properties."""
|
|
98
|
+
# set the main properties of the Room
|
|
99
|
+
self._host = host
|
|
100
|
+
self.program_type = program_type
|
|
101
|
+
self.construction_set = construction_set
|
|
102
|
+
self.hvac = hvac
|
|
103
|
+
self.shw = shw
|
|
104
|
+
|
|
105
|
+
# set the Room's specific properties that override the program_type to None
|
|
106
|
+
self._people = None
|
|
107
|
+
self._lighting = None
|
|
108
|
+
self._electric_equipment = None
|
|
109
|
+
self._gas_equipment = None
|
|
110
|
+
self._service_hot_water = None
|
|
111
|
+
self._infiltration = None
|
|
112
|
+
self._ventilation = None
|
|
113
|
+
self._setpoint = None
|
|
114
|
+
self._daylighting_control = None
|
|
115
|
+
self._window_vent_control = None
|
|
116
|
+
self._fans = []
|
|
117
|
+
self._internal_masses = []
|
|
118
|
+
self._process_loads = []
|
|
119
|
+
|
|
120
|
+
@property
|
|
121
|
+
def host(self):
|
|
122
|
+
"""Get the Room object hosting these properties."""
|
|
123
|
+
return self._host
|
|
124
|
+
|
|
125
|
+
@property
|
|
126
|
+
def program_type(self):
|
|
127
|
+
"""Get or set the ProgramType object for the Room.
|
|
128
|
+
|
|
129
|
+
If not set, it will default to a plenum ProgramType (with no loads assigned).
|
|
130
|
+
"""
|
|
131
|
+
if self._program_type is not None: # set by the user
|
|
132
|
+
return self._program_type
|
|
133
|
+
else:
|
|
134
|
+
return plenum_program
|
|
135
|
+
|
|
136
|
+
@program_type.setter
|
|
137
|
+
def program_type(self, value):
|
|
138
|
+
if value is not None:
|
|
139
|
+
assert isinstance(value, ProgramType), \
|
|
140
|
+
'Expected ProgramType for Room program_type. Got {}'.format(type(value))
|
|
141
|
+
value.lock() # lock in case program type has multiple references
|
|
142
|
+
self._program_type = value
|
|
143
|
+
|
|
144
|
+
@property
|
|
145
|
+
def construction_set(self):
|
|
146
|
+
"""Get or set the Room ConstructionSet object.
|
|
147
|
+
|
|
148
|
+
If not set, it will be the Honeybee default generic ConstructionSet.
|
|
149
|
+
"""
|
|
150
|
+
if self._construction_set is not None: # set by the user
|
|
151
|
+
return self._construction_set
|
|
152
|
+
else:
|
|
153
|
+
return generic_construction_set
|
|
154
|
+
|
|
155
|
+
@construction_set.setter
|
|
156
|
+
def construction_set(self, value):
|
|
157
|
+
if value is not None:
|
|
158
|
+
assert isinstance(value, ConstructionSet), \
|
|
159
|
+
'Expected ConstructionSet. Got {}'.format(type(value))
|
|
160
|
+
value.lock() # lock in case construction set has multiple references
|
|
161
|
+
self._construction_set = value
|
|
162
|
+
|
|
163
|
+
@property
|
|
164
|
+
def hvac(self):
|
|
165
|
+
"""Get or set the HVAC object for the Room.
|
|
166
|
+
|
|
167
|
+
If None, it will be assumed that the Room is not conditioned.
|
|
168
|
+
"""
|
|
169
|
+
return self._hvac
|
|
170
|
+
|
|
171
|
+
@hvac.setter
|
|
172
|
+
def hvac(self, value):
|
|
173
|
+
if value is not None:
|
|
174
|
+
assert isinstance(value, _HVACSystem), \
|
|
175
|
+
'Expected HVACSystem for Room hvac. Got {}'.format(type(value))
|
|
176
|
+
value.lock() # lock in case hvac has multiple references
|
|
177
|
+
self._hvac = value
|
|
178
|
+
|
|
179
|
+
@property
|
|
180
|
+
def shw(self):
|
|
181
|
+
"""Get or set the SHWSystem object for the Room.
|
|
182
|
+
|
|
183
|
+
If None, all hot water loads will be met with a system that doesn't compute
|
|
184
|
+
fuel or electricity usage.
|
|
185
|
+
"""
|
|
186
|
+
return self._shw
|
|
187
|
+
|
|
188
|
+
@shw.setter
|
|
189
|
+
def shw(self, value):
|
|
190
|
+
if value is not None:
|
|
191
|
+
assert isinstance(value, SHWSystem), \
|
|
192
|
+
'Expected SHWSystem for Room shw. Got {}'.format(type(value))
|
|
193
|
+
value.lock() # lock in case shw has multiple references
|
|
194
|
+
self._shw = value
|
|
195
|
+
|
|
196
|
+
@property
|
|
197
|
+
def people(self):
|
|
198
|
+
"""Get or set a People object to describe the occupancy of the Room."""
|
|
199
|
+
if self._people is not None: # set by the user
|
|
200
|
+
return self._people
|
|
201
|
+
else:
|
|
202
|
+
return self.program_type.people
|
|
203
|
+
|
|
204
|
+
@people.setter
|
|
205
|
+
def people(self, value):
|
|
206
|
+
if value is not None:
|
|
207
|
+
assert isinstance(value, People), \
|
|
208
|
+
'Expected People for Room people. Got {}'.format(type(value))
|
|
209
|
+
value.lock() # lock because we don't duplicate the object
|
|
210
|
+
self._people = value
|
|
211
|
+
|
|
212
|
+
@property
|
|
213
|
+
def lighting(self):
|
|
214
|
+
"""Get or set a Lighting object to describe the lighting usage of the Room."""
|
|
215
|
+
if self._lighting is not None: # set by the user
|
|
216
|
+
return self._lighting
|
|
217
|
+
else:
|
|
218
|
+
return self.program_type.lighting
|
|
219
|
+
|
|
220
|
+
@lighting.setter
|
|
221
|
+
def lighting(self, value):
|
|
222
|
+
if value is not None:
|
|
223
|
+
assert isinstance(value, Lighting), \
|
|
224
|
+
'Expected Lighting for Room lighting. Got {}'.format(type(value))
|
|
225
|
+
value.lock() # lock because we don't duplicate the object
|
|
226
|
+
self._lighting = value
|
|
227
|
+
|
|
228
|
+
@property
|
|
229
|
+
def electric_equipment(self):
|
|
230
|
+
"""Get or set an ElectricEquipment object to describe the equipment usage."""
|
|
231
|
+
if self._electric_equipment is not None: # set by the user
|
|
232
|
+
return self._electric_equipment
|
|
233
|
+
else:
|
|
234
|
+
return self.program_type.electric_equipment
|
|
235
|
+
|
|
236
|
+
@electric_equipment.setter
|
|
237
|
+
def electric_equipment(self, value):
|
|
238
|
+
if value is not None:
|
|
239
|
+
assert isinstance(value, ElectricEquipment), 'Expected ElectricEquipment ' \
|
|
240
|
+
'for Room electric_equipment. Got {}'.format(type(value))
|
|
241
|
+
value.lock() # lock because we don't duplicate the object
|
|
242
|
+
self._electric_equipment = value
|
|
243
|
+
|
|
244
|
+
@property
|
|
245
|
+
def gas_equipment(self):
|
|
246
|
+
"""Get or set a GasEquipment object to describe the equipment usage."""
|
|
247
|
+
if self._gas_equipment is not None: # set by the user
|
|
248
|
+
return self._gas_equipment
|
|
249
|
+
else:
|
|
250
|
+
return self.program_type.gas_equipment
|
|
251
|
+
|
|
252
|
+
@gas_equipment.setter
|
|
253
|
+
def gas_equipment(self, value):
|
|
254
|
+
if value is not None:
|
|
255
|
+
assert isinstance(value, GasEquipment), 'Expected GasEquipment ' \
|
|
256
|
+
'for Room gas_equipment. Got {}'.format(type(value))
|
|
257
|
+
value.lock() # lock because we don't duplicate the object
|
|
258
|
+
self._gas_equipment = value
|
|
259
|
+
|
|
260
|
+
@property
|
|
261
|
+
def service_hot_water(self):
|
|
262
|
+
"""Get or set a ServiceHotWater object to describe the hot water usage."""
|
|
263
|
+
if self._service_hot_water is not None: # set by the user
|
|
264
|
+
return self._service_hot_water
|
|
265
|
+
else:
|
|
266
|
+
return self.program_type.service_hot_water
|
|
267
|
+
|
|
268
|
+
@service_hot_water.setter
|
|
269
|
+
def service_hot_water(self, value):
|
|
270
|
+
if value is not None:
|
|
271
|
+
assert isinstance(value, ServiceHotWater), 'Expected ServiceHotWater ' \
|
|
272
|
+
'for Room service_hot_water. Got {}'.format(type(value))
|
|
273
|
+
value.lock() # lock because we don't duplicate the object
|
|
274
|
+
self._service_hot_water = value
|
|
275
|
+
|
|
276
|
+
@property
|
|
277
|
+
def infiltration(self):
|
|
278
|
+
"""Get or set a Infiltration object to to describe the outdoor air leakage."""
|
|
279
|
+
if self._infiltration is not None: # set by the user
|
|
280
|
+
return self._infiltration
|
|
281
|
+
else:
|
|
282
|
+
return self.program_type.infiltration
|
|
283
|
+
|
|
284
|
+
@infiltration.setter
|
|
285
|
+
def infiltration(self, value):
|
|
286
|
+
if value is not None:
|
|
287
|
+
assert isinstance(value, Infiltration), 'Expected Infiltration ' \
|
|
288
|
+
'for Room infiltration. Got {}'.format(type(value))
|
|
289
|
+
value.lock() # lock because we don't duplicate the object
|
|
290
|
+
self._infiltration = value
|
|
291
|
+
|
|
292
|
+
@property
|
|
293
|
+
def ventilation(self):
|
|
294
|
+
"""Get or set a Ventilation object for the minimum outdoor air requirement.
|
|
295
|
+
|
|
296
|
+
Note that setting the ventilation here will only affect the conditioned
|
|
297
|
+
outdoor air that is brought through a HVAC system. For mechanical ventilation
|
|
298
|
+
of outdoor air that is not connected to any heating or cooling system the
|
|
299
|
+
Room.fans property should be used.
|
|
300
|
+
"""
|
|
301
|
+
if self._ventilation is not None: # set by the user
|
|
302
|
+
return self._ventilation
|
|
303
|
+
else:
|
|
304
|
+
return self.program_type.ventilation
|
|
305
|
+
|
|
306
|
+
@ventilation.setter
|
|
307
|
+
def ventilation(self, value):
|
|
308
|
+
if value is not None:
|
|
309
|
+
assert isinstance(value, Ventilation), 'Expected Ventilation ' \
|
|
310
|
+
'for Room ventilation. Got {}'.format(type(value))
|
|
311
|
+
value.lock() # lock because we don't duplicate the object
|
|
312
|
+
self._ventilation = value
|
|
313
|
+
|
|
314
|
+
@property
|
|
315
|
+
def setpoint(self):
|
|
316
|
+
"""Get or set a Setpoint object for the temperature setpoints of the Room."""
|
|
317
|
+
if self._setpoint is not None: # set by the user
|
|
318
|
+
return self._setpoint
|
|
319
|
+
else:
|
|
320
|
+
return self.program_type.setpoint
|
|
321
|
+
|
|
322
|
+
@setpoint.setter
|
|
323
|
+
def setpoint(self, value):
|
|
324
|
+
if value is not None:
|
|
325
|
+
assert isinstance(value, Setpoint), 'Expected Setpoint ' \
|
|
326
|
+
'for Room setpoint. Got {}'.format(type(value))
|
|
327
|
+
value.lock() # lock because we don't duplicate the object
|
|
328
|
+
self._setpoint = value
|
|
329
|
+
|
|
330
|
+
@property
|
|
331
|
+
def daylighting_control(self):
|
|
332
|
+
"""Get or set a DaylightingControl object to dictate the dimming of lights.
|
|
333
|
+
|
|
334
|
+
If None, the lighting will respond only to the schedule and not the
|
|
335
|
+
daylight conditions within the room.
|
|
336
|
+
"""
|
|
337
|
+
return self._daylighting_control
|
|
338
|
+
|
|
339
|
+
@daylighting_control.setter
|
|
340
|
+
def daylighting_control(self, value):
|
|
341
|
+
if value is not None:
|
|
342
|
+
assert isinstance(value, DaylightingControl), 'Expected DaylightingControl' \
|
|
343
|
+
' object for Room daylighting_control. Got {}'.format(type(value))
|
|
344
|
+
value._parent = self.host
|
|
345
|
+
self._daylighting_control = value
|
|
346
|
+
|
|
347
|
+
@property
|
|
348
|
+
def window_vent_control(self):
|
|
349
|
+
"""Get or set a VentilationControl object to dictate the opening of windows.
|
|
350
|
+
|
|
351
|
+
If None, the windows will never open.
|
|
352
|
+
"""
|
|
353
|
+
return self._window_vent_control
|
|
354
|
+
|
|
355
|
+
@window_vent_control.setter
|
|
356
|
+
def window_vent_control(self, value):
|
|
357
|
+
if value is not None:
|
|
358
|
+
assert isinstance(value, VentilationControl), 'Expected VentilationControl' \
|
|
359
|
+
' object for Room window_vent_control. Got {}'.format(type(value))
|
|
360
|
+
value.lock() # lock because we don't duplicate the object
|
|
361
|
+
self._window_vent_control = value
|
|
362
|
+
|
|
363
|
+
@property
|
|
364
|
+
def fans(self):
|
|
365
|
+
"""Get or set an array of VentilationFan objects for fans within the room.
|
|
366
|
+
|
|
367
|
+
Note that these fans are not connected to the heating or cooling system
|
|
368
|
+
and are meant to represent the intentional circulation of unconditioned
|
|
369
|
+
outdoor air for the purposes of keeping a space cooler, drier or free
|
|
370
|
+
of indoor pollutants (as in the case of kitchen or bathroom exhaust fans).
|
|
371
|
+
|
|
372
|
+
For the specification of mechanical ventilation of conditioned outdoor air,
|
|
373
|
+
the Room.ventilation property should be used and the Room should be
|
|
374
|
+
given a HVAC that can meet this specification.
|
|
375
|
+
"""
|
|
376
|
+
return tuple(self._fans)
|
|
377
|
+
|
|
378
|
+
@fans.setter
|
|
379
|
+
def fans(self, value):
|
|
380
|
+
for val in value:
|
|
381
|
+
assert isinstance(val, VentilationFan), 'Expected VentilationFan ' \
|
|
382
|
+
'object for Room fans. Got {}'.format(type(val))
|
|
383
|
+
val.lock() # lock because we don't duplicate the object
|
|
384
|
+
self._fans = list(value)
|
|
385
|
+
|
|
386
|
+
@property
|
|
387
|
+
def total_fan_flow(self):
|
|
388
|
+
"""Get a number for the total process load in m3/s within the room."""
|
|
389
|
+
return sum([fan.flow_rate for fan in self._fans])
|
|
390
|
+
|
|
391
|
+
@property
|
|
392
|
+
def process_loads(self):
|
|
393
|
+
"""Get or set an array of Process objects for process loads within the room."""
|
|
394
|
+
return tuple(self._process_loads)
|
|
395
|
+
|
|
396
|
+
@process_loads.setter
|
|
397
|
+
def process_loads(self, value):
|
|
398
|
+
for val in value:
|
|
399
|
+
assert isinstance(val, Process), 'Expected Process ' \
|
|
400
|
+
'object for Room process_loads. Got {}'.format(type(val))
|
|
401
|
+
val.lock() # lock because we don't duplicate the object
|
|
402
|
+
self._process_loads = list(value)
|
|
403
|
+
|
|
404
|
+
@property
|
|
405
|
+
def total_process_load(self):
|
|
406
|
+
"""Get a number for the total process load in W within the room."""
|
|
407
|
+
return sum([load.watts for load in self._process_loads])
|
|
408
|
+
|
|
409
|
+
@property
|
|
410
|
+
def internal_masses(self):
|
|
411
|
+
"""Get or set an array of InternalMass objects for mass exposed to Room air.
|
|
412
|
+
|
|
413
|
+
Note that internal masses assigned this way cannot "see" solar radiation that
|
|
414
|
+
may potentially hit them and, as such, caution should be taken when using this
|
|
415
|
+
component with internal mass objects that are not always in shade. Masses are
|
|
416
|
+
factored into the the thermal calculations of the Room by undergoing heat
|
|
417
|
+
transfer with the indoor air.
|
|
418
|
+
"""
|
|
419
|
+
return tuple(self._internal_masses)
|
|
420
|
+
|
|
421
|
+
@internal_masses.setter
|
|
422
|
+
def internal_masses(self, value):
|
|
423
|
+
for val in value:
|
|
424
|
+
assert isinstance(val, InternalMass), 'Expected InternalMass' \
|
|
425
|
+
' object for Room internal_masses. Got {}'.format(type(val))
|
|
426
|
+
val.lock() # lock because we don't duplicate the object
|
|
427
|
+
self._internal_masses = list(value)
|
|
428
|
+
|
|
429
|
+
@property
|
|
430
|
+
def is_conditioned(self):
|
|
431
|
+
"""Boolean to note whether the Room is conditioned."""
|
|
432
|
+
return self._hvac is not None and self.setpoint is not None
|
|
433
|
+
|
|
434
|
+
@property
|
|
435
|
+
def has_overridden_loads(self):
|
|
436
|
+
"""Boolean to note whether the Room has any loads that override the Program.
|
|
437
|
+
|
|
438
|
+
This will happen if any of the absolute methods are used or if any of the
|
|
439
|
+
individual Room load objects have been set.
|
|
440
|
+
"""
|
|
441
|
+
load_attr = (
|
|
442
|
+
self._people, self._lighting, self._electric_equipment,
|
|
443
|
+
self._gas_equipment, self._service_hot_water, self._infiltration,
|
|
444
|
+
self._ventilation, self._setpoint
|
|
445
|
+
)
|
|
446
|
+
return not all(load is None for load in load_attr)
|
|
447
|
+
|
|
448
|
+
@property
|
|
449
|
+
def has_overridden_space_loads(self):
|
|
450
|
+
"""Boolean for whether the Room has SpaceType loads that override the Program.
|
|
451
|
+
|
|
452
|
+
This property is the same as has_overridden_loads except that overridden
|
|
453
|
+
service_hot_water, ventilation, and setpoint are ignored given that they
|
|
454
|
+
replace the attributes assigned by OpenStudio SpaceTypes rather than being
|
|
455
|
+
added to them.
|
|
456
|
+
"""
|
|
457
|
+
load_attr = (
|
|
458
|
+
self._people, self._lighting, self._electric_equipment,
|
|
459
|
+
self._gas_equipment, self._infiltration
|
|
460
|
+
)
|
|
461
|
+
return not all(load is None for load in load_attr)
|
|
462
|
+
|
|
463
|
+
def absolute_people(self, person_count, conversion=1):
|
|
464
|
+
"""Set the absolute number of people in the Room.
|
|
465
|
+
|
|
466
|
+
This overwrites the RoomEnergyProperties's people per area but preserves
|
|
467
|
+
all schedules and other people properties. If the Room has no people definition,
|
|
468
|
+
a new one with an Always On schedule will be created. Note that, if the
|
|
469
|
+
host Room has no floors, the people load will be zero.
|
|
470
|
+
|
|
471
|
+
Args:
|
|
472
|
+
person_count: Number for the maximum quantity of people in the room.
|
|
473
|
+
conversion: Factor to account for the case where host Room geometry is
|
|
474
|
+
not in meters. This will be multiplied by the floor area so it should
|
|
475
|
+
be 0.001 for millimeters, 0.305 for feet, etc. (Default: 1).
|
|
476
|
+
"""
|
|
477
|
+
people = self._dup_load('people', People)
|
|
478
|
+
self._absolute_by_floor(people, 'people_per_area', person_count, conversion)
|
|
479
|
+
self.people = people
|
|
480
|
+
|
|
481
|
+
def absolute_lighting(self, watts, conversion=1):
|
|
482
|
+
"""Set the absolute wattage of lighting in the Room.
|
|
483
|
+
|
|
484
|
+
This overwrites the RoomEnergyProperties's lighting per area but preserves all
|
|
485
|
+
schedules and other lighting properties. If the Room has no lighting definition,
|
|
486
|
+
a new one with an Always On schedule will be created. Note that, if the
|
|
487
|
+
host Room has no floors, the lighting load will be zero.
|
|
488
|
+
|
|
489
|
+
Args:
|
|
490
|
+
watts: Number for the installed wattage of lighting in the room.
|
|
491
|
+
conversion: Factor to account for the case where host Room geometry is
|
|
492
|
+
not in meters. This will be multiplied by the floor area so it should
|
|
493
|
+
be 0.001 for millimeters, 0.305 for feet, etc. (Default: 1).
|
|
494
|
+
"""
|
|
495
|
+
lighting = self._dup_load('lighting', Lighting)
|
|
496
|
+
self._absolute_by_floor(lighting, 'watts_per_area', watts, conversion)
|
|
497
|
+
self.lighting = lighting
|
|
498
|
+
|
|
499
|
+
def absolute_electric_equipment(self, watts, conversion=1):
|
|
500
|
+
"""Set the absolute wattage of electric equipment in the Room.
|
|
501
|
+
|
|
502
|
+
This overwrites the RoomEnergyProperties's electric equipment per area but
|
|
503
|
+
preserves all schedules and other properties. If the Room has no electric
|
|
504
|
+
equipment definition, a new one with an Always On schedule will be created.
|
|
505
|
+
Note that, if the host Room has no floors, the electric equipment load
|
|
506
|
+
will be zero.
|
|
507
|
+
|
|
508
|
+
Args:
|
|
509
|
+
watts: Number for the installed wattage of electric equipment in the room.
|
|
510
|
+
conversion: Factor to account for the case where host Room geometry is
|
|
511
|
+
not in meters. This will be multiplied by the floor area so it should
|
|
512
|
+
be 0.001 for millimeters, 0.305 for feet, etc. (Default: 1).
|
|
513
|
+
"""
|
|
514
|
+
elect_equip = self._dup_load('electric_equipment', ElectricEquipment)
|
|
515
|
+
self._absolute_by_floor(elect_equip, 'watts_per_area', watts, conversion)
|
|
516
|
+
self.electric_equipment = elect_equip
|
|
517
|
+
|
|
518
|
+
def absolute_gas_equipment(self, watts, conversion=1):
|
|
519
|
+
"""Set the absolute wattage of gas equipment in the Room.
|
|
520
|
+
|
|
521
|
+
This overwrites the RoomEnergyProperties's gas equipment per area but
|
|
522
|
+
preserves all schedules and other properties. If the Room has no gas
|
|
523
|
+
equipment definition, a new one with an Always On schedule will be created.
|
|
524
|
+
Note that, if the host Room has no floors, the gas equipment load
|
|
525
|
+
will be zero.
|
|
526
|
+
|
|
527
|
+
Args:
|
|
528
|
+
watts: Number for the installed wattage of gas equipment in the room.
|
|
529
|
+
conversion: Factor to account for the case where host Room geometry is
|
|
530
|
+
not in meters. This will be multiplied by the floor area so it should
|
|
531
|
+
be 0.001 for millimeters, 0.305 for feet, etc. (Default: 1).
|
|
532
|
+
"""
|
|
533
|
+
gas_equipment = self._dup_load('gas_equipment', GasEquipment)
|
|
534
|
+
self._absolute_by_floor(gas_equipment, 'watts_per_area', watts, conversion)
|
|
535
|
+
self.gas_equipment = gas_equipment
|
|
536
|
+
|
|
537
|
+
def absolute_service_hot_water(self, flow, conversion=1):
|
|
538
|
+
"""Set the absolute flow rate of service hot water use in the Room.
|
|
539
|
+
|
|
540
|
+
This overwrites the RoomEnergyProperties's hot water flow per area but
|
|
541
|
+
preserves all schedules and other properties. If the Room has no service
|
|
542
|
+
hot water definition, a new one with an Always On schedule will be created.
|
|
543
|
+
Note that, if the host Room has no floors, the service hot water flow
|
|
544
|
+
will be zero.
|
|
545
|
+
|
|
546
|
+
Args:
|
|
547
|
+
flow: Number for the peak flow rate of service hot water in the room.
|
|
548
|
+
conversion: Factor to account for the case where host Room geometry is
|
|
549
|
+
not in meters. This will be multiplied by the floor area so it should
|
|
550
|
+
be 0.001 for millimeters, 0.305 for feet, etc. (Default: 1).
|
|
551
|
+
"""
|
|
552
|
+
shw = self._dup_load('service_hot_water', ServiceHotWater)
|
|
553
|
+
self._absolute_by_floor(shw, 'flow_per_area', flow, conversion)
|
|
554
|
+
self.service_hot_water = shw
|
|
555
|
+
|
|
556
|
+
def absolute_infiltration(self, flow_rate, conversion=1):
|
|
557
|
+
"""Set the absolute flow rate of infiltration for the Room in m3/s.
|
|
558
|
+
|
|
559
|
+
This overwrites the RoomEnergyProperties's infiltration flow per exterior area
|
|
560
|
+
but preserves all schedules and other properties. If the Room has no
|
|
561
|
+
infiltration definition, a new one with an Always On schedule will be created.
|
|
562
|
+
Note that, if the host Room has no exterior faces, the infiltration load
|
|
563
|
+
will be zero.
|
|
564
|
+
|
|
565
|
+
Args:
|
|
566
|
+
flow_rate: Number for the infiltration flow rate in m3/s.
|
|
567
|
+
conversion: Factor to account for the case where host Room geometry is
|
|
568
|
+
not in meters. This will be multiplied by the floor area so it should
|
|
569
|
+
be 0.001 for millimeters, 0.305 for feet, etc. (Default: 1).
|
|
570
|
+
"""
|
|
571
|
+
infiltration = self._dup_load('infiltration', Infiltration)
|
|
572
|
+
try:
|
|
573
|
+
ext_area = self.host.exposed_area * conversion ** 2
|
|
574
|
+
infiltration.flow_per_exterior_area = flow_rate / ext_area
|
|
575
|
+
except ZeroDivisionError:
|
|
576
|
+
pass # no exposed area; just leave the load level as is
|
|
577
|
+
self.infiltration = infiltration
|
|
578
|
+
|
|
579
|
+
def absolute_infiltration_ach(self, air_changes_per_hour, conversion=1):
|
|
580
|
+
"""Set the absolute flow rate of infiltration for the Room in ACH.
|
|
581
|
+
|
|
582
|
+
This overwrites the RoomEnergyProperties's infiltration flow per exterior area
|
|
583
|
+
but preserves all schedules and other properties. If the Room has no
|
|
584
|
+
infiltration definition, a new one with an Always On schedule will be created.
|
|
585
|
+
Note that, if the host Room has no exterior faces, the infiltration load
|
|
586
|
+
will be zero.
|
|
587
|
+
|
|
588
|
+
Args:
|
|
589
|
+
air_changes_per_hour: Number for the infiltration flow rate in ACH.
|
|
590
|
+
conversion: Factor to account for the case where host Room geometry is
|
|
591
|
+
not in meters. This will be multiplied by the floor area so it should
|
|
592
|
+
be 0.001 for millimeters, 0.305 for feet, etc. (Default: 1).
|
|
593
|
+
"""
|
|
594
|
+
room_vol = self.host.volume * conversion ** 3
|
|
595
|
+
self.absolute_infiltration((air_changes_per_hour * room_vol) / 3600., conversion)
|
|
596
|
+
|
|
597
|
+
def absolute_ventilation(self, flow_rate):
|
|
598
|
+
"""Set the absolute flow rate of outdoor air ventilation for the Room in m3/s.
|
|
599
|
+
|
|
600
|
+
This overwrites all values of the RoomEnergyProperties's ventilation flow
|
|
601
|
+
but preserves the schedule. If the Room has no ventilation definition, a
|
|
602
|
+
new one with an Always On schedule will be created.
|
|
603
|
+
|
|
604
|
+
Args:
|
|
605
|
+
flow_rate: A number for the absolute of flow of outdoor air ventilation
|
|
606
|
+
for the room in cubic meters per second (m3/s). Note that inputting
|
|
607
|
+
a value here will overwrite all specification of outdoor air
|
|
608
|
+
ventilation currently on the room (per_floor, per_person, ach).
|
|
609
|
+
"""
|
|
610
|
+
ventilation = self._dup_load('ventilation', Ventilation)
|
|
611
|
+
ventilation.flow_per_person = 0
|
|
612
|
+
ventilation.flow_per_area = 0
|
|
613
|
+
ventilation.air_changes_per_hour = 0
|
|
614
|
+
ventilation.flow_per_zone = flow_rate
|
|
615
|
+
self.ventilation = ventilation
|
|
616
|
+
|
|
617
|
+
def add_process_load(self, process_load):
|
|
618
|
+
"""Add a Process load to this Room.
|
|
619
|
+
|
|
620
|
+
Args:
|
|
621
|
+
process_load: A Process load to add to this Room.
|
|
622
|
+
"""
|
|
623
|
+
assert isinstance(process_load, Process), \
|
|
624
|
+
'Expected Process load object. Got {}.'.format(type(process_load))
|
|
625
|
+
process_load.lock() # lock because we don't duplicate the object
|
|
626
|
+
self._process_loads.append(process_load)
|
|
627
|
+
|
|
628
|
+
def remove_process_loads(self):
|
|
629
|
+
"""Remove all Process loads from the Room."""
|
|
630
|
+
self._process_loads = []
|
|
631
|
+
|
|
632
|
+
def add_fan(self, fan):
|
|
633
|
+
"""Add a VentilationFan to this Room.
|
|
634
|
+
|
|
635
|
+
Args:
|
|
636
|
+
fan: A VentilationFan to add to this Room.
|
|
637
|
+
"""
|
|
638
|
+
assert isinstance(fan, VentilationFan), \
|
|
639
|
+
'Expected VentilationFan object. Got {}.'.format(type(fan))
|
|
640
|
+
fan.lock() # lock because we don't duplicate the object
|
|
641
|
+
self._fans.append(fan)
|
|
642
|
+
|
|
643
|
+
def remove_fans(self):
|
|
644
|
+
"""Remove all VentilationFans from the Room."""
|
|
645
|
+
self._fans = []
|
|
646
|
+
|
|
647
|
+
def add_internal_mass(self, internal_mass):
|
|
648
|
+
"""Add an InternalMass to this Room.
|
|
649
|
+
|
|
650
|
+
Args:
|
|
651
|
+
internal_mass: An InternalMass to add to this Room.
|
|
652
|
+
"""
|
|
653
|
+
assert isinstance(internal_mass, InternalMass), \
|
|
654
|
+
'Expected InternalMass. Got {}.'.format(type(internal_mass))
|
|
655
|
+
internal_mass.lock() # lock because we don't duplicate the object
|
|
656
|
+
self._internal_masses.append(internal_mass)
|
|
657
|
+
|
|
658
|
+
def remove_internal_masses(self):
|
|
659
|
+
"""Remove all internal masses from the Room."""
|
|
660
|
+
self._internal_masses = []
|
|
661
|
+
|
|
662
|
+
def floor_area_with_constructions(
|
|
663
|
+
self, geometry_units, destination_units='Meters', tolerance=0.01):
|
|
664
|
+
"""Get the floor area of the Room accounting for wall constructions.
|
|
665
|
+
|
|
666
|
+
Args:
|
|
667
|
+
geometry_units: The units in which the Room geometry exists.
|
|
668
|
+
destination_units: The "squared" units that the output value will
|
|
669
|
+
be in. (Default: Meters).
|
|
670
|
+
tolerance: The maximum difference between values at which point vertices
|
|
671
|
+
are considered to be the same. (Default: 0.01, suitable for objects
|
|
672
|
+
in meters).
|
|
673
|
+
"""
|
|
674
|
+
# get the area of the floors without accounting for constructions
|
|
675
|
+
con_fac = conversion_factor_to_meters(geometry_units)
|
|
676
|
+
floors = [face for face in self.host._faces if isinstance(face.type, Floor)]
|
|
677
|
+
base_area = sum((f.area for f in floors)) * con_fac
|
|
678
|
+
# subtract the thickness of the constructions from the base area
|
|
679
|
+
seg_sub = 0
|
|
680
|
+
for flr in floors:
|
|
681
|
+
fg = flr.geometry
|
|
682
|
+
segs = fg.boundary_segments if not fg.has_holes else \
|
|
683
|
+
fg.boundary_segments + fg.hole_segments
|
|
684
|
+
for seg in segs:
|
|
685
|
+
w_f = self._segment_wall_face(seg, tolerance)
|
|
686
|
+
if w_f is not None:
|
|
687
|
+
th = w_f.properties.energy.construction.thickness
|
|
688
|
+
t_c = th if isinstance(flr.boundary_condition, (Outdoors, Ground)) \
|
|
689
|
+
else th / 2
|
|
690
|
+
seg_sub += seg.length * t_c
|
|
691
|
+
return (base_area - seg_sub) / conversion_factor_to_meters(destination_units)
|
|
692
|
+
|
|
693
|
+
def remove_child_constructions(self):
|
|
694
|
+
"""Remove constructions assigned to the Room's Faces, Apertures, Doors and Shades.
|
|
695
|
+
|
|
696
|
+
This means that all constructions of the Room will be assigned by the Room's
|
|
697
|
+
construction_set (or the Honeybee default ConstructionSet if the Room has
|
|
698
|
+
no construction set).
|
|
699
|
+
"""
|
|
700
|
+
for shade in self.host.shades:
|
|
701
|
+
shade.properties.energy.construction = None
|
|
702
|
+
for face in self.host.faces:
|
|
703
|
+
face.properties.energy.construction = None
|
|
704
|
+
for shade in face.shades:
|
|
705
|
+
shade.properties.energy.construction = None
|
|
706
|
+
for ap in face._apertures:
|
|
707
|
+
ap.properties.energy.construction = None
|
|
708
|
+
for shade in ap.shades:
|
|
709
|
+
shade.properties.energy.construction = None
|
|
710
|
+
for dr in face._doors:
|
|
711
|
+
dr.properties.energy.construction = None
|
|
712
|
+
for shade in dr.shades:
|
|
713
|
+
shade.properties.energy.construction = None
|
|
714
|
+
|
|
715
|
+
def window_construction_by_orientation(
|
|
716
|
+
self, construction, orientation=0, offset=45, north_vector=Vector2D(0, 1)):
|
|
717
|
+
"""Set the construction of exterior Apertures in Walls facing a given orientation.
|
|
718
|
+
|
|
719
|
+
This is useful for testing orientation-specific energy conservation
|
|
720
|
+
strategies or creating ASHRAE baseline buildings.
|
|
721
|
+
|
|
722
|
+
Args:
|
|
723
|
+
construction: A WindowConstruction that will be assigned to all of the
|
|
724
|
+
room's Apertures in Walls that are facing a certain orientation.
|
|
725
|
+
orientation: A number between 0 and 360 that represents the orientation
|
|
726
|
+
in degrees to which the construction will be assigned. 0 = North,
|
|
727
|
+
90 = East, 180 = South, 270 = West. (Default: 0 for North).
|
|
728
|
+
offset: A number between 0 and 180 that represents the offset from the
|
|
729
|
+
orientation in degrees for which the construction will be assigned.
|
|
730
|
+
For example, a value of 45 indicates that any Apertures falling
|
|
731
|
+
in the 90 degree range around the orientation will get the input
|
|
732
|
+
construction. (Default: 45).
|
|
733
|
+
north_vector: A ladybug_geometry Vector3D for the north direction.
|
|
734
|
+
Default is the Y-axis (0, 1).
|
|
735
|
+
"""
|
|
736
|
+
# determine the min and max values for orientation
|
|
737
|
+
ori_min = orientation - offset
|
|
738
|
+
ori_max = orientation + offset
|
|
739
|
+
ori_min = ori_min + 360 if ori_min < 0 else ori_min
|
|
740
|
+
ori_max = ori_max - 360 if ori_max > 360 else ori_max
|
|
741
|
+
rev_vars = True if ori_min > ori_max else False
|
|
742
|
+
|
|
743
|
+
# loop through the faces an determine if they meet the criteria
|
|
744
|
+
for face in self.host.faces:
|
|
745
|
+
if isinstance(face.boundary_condition, Outdoors) and \
|
|
746
|
+
isinstance(face.type, Wall) and len(face._apertures) > 0:
|
|
747
|
+
if rev_vars:
|
|
748
|
+
if face.horizontal_orientation(north_vector) > ori_min \
|
|
749
|
+
or face.horizontal_orientation(north_vector) < ori_max:
|
|
750
|
+
for ap in face._apertures:
|
|
751
|
+
ap.properties.energy.construction = construction
|
|
752
|
+
else:
|
|
753
|
+
if ori_min < face.horizontal_orientation(north_vector) < ori_max:
|
|
754
|
+
for ap in face._apertures:
|
|
755
|
+
ap.properties.energy.construction = construction
|
|
756
|
+
|
|
757
|
+
def add_default_ideal_air(self):
|
|
758
|
+
"""Add a default IdealAirSystem to this Room.
|
|
759
|
+
|
|
760
|
+
The identifier of this system will be derived from the room zone name
|
|
761
|
+
and will align with the naming convention that EnergyPlus uses for
|
|
762
|
+
templates Ideal Air systems.
|
|
763
|
+
"""
|
|
764
|
+
hvac_id = '{} Ideal Loads Air System'.format(self.host.zone)
|
|
765
|
+
self.hvac = IdealAirSystem(hvac_id)
|
|
766
|
+
|
|
767
|
+
def assign_ideal_air_equivalent(self):
|
|
768
|
+
"""Convert any HVAC assigned to this Room to be an equivalent IdealAirSystem.
|
|
769
|
+
|
|
770
|
+
Relevant properties will be transferred to the resulting ideal air such as
|
|
771
|
+
economizer_type, sensible_heat_recovery, latent_heat_recovery, and
|
|
772
|
+
demand_controlled_ventilation.
|
|
773
|
+
|
|
774
|
+
For HeatCool systems that cannot supply ventilation, this Room's ventilation
|
|
775
|
+
specification will be overridden to zero. For DOAS systems with a
|
|
776
|
+
doas_availability_schedule, this schedule will be applied with the Room's
|
|
777
|
+
ventilation schedule.
|
|
778
|
+
"""
|
|
779
|
+
if self.hvac is None or isinstance(self.hvac, IdealAirSystem):
|
|
780
|
+
return None # the room is already good as it is
|
|
781
|
+
if self.setpoint is None:
|
|
782
|
+
return None # the room is not able to be conditioned
|
|
783
|
+
i_sys = self.hvac.to_ideal_air_equivalent()
|
|
784
|
+
i_sys.identifier = '{} Ideal Loads Air System'.format(self.host.identifier)
|
|
785
|
+
if isinstance(self.hvac, _HeatCoolBase):
|
|
786
|
+
if self.ventilation is not None: # override the ventilation
|
|
787
|
+
self.ventilation = Ventilation('HeatCool System Zero Ventilation')
|
|
788
|
+
if self.setpoint is not None and \
|
|
789
|
+
self.setpoint.humidifying_schedule is not None: # remove humid
|
|
790
|
+
new_setpt = self.setpoint.duplicate()
|
|
791
|
+
new_setpt.remove_humidity_setpoints()
|
|
792
|
+
self.setpoint = new_setpt
|
|
793
|
+
elif isinstance(self.hvac, _DOASBase) and \
|
|
794
|
+
self.hvac.doas_availability_schedule is not None:
|
|
795
|
+
if self.ventilation is not None: # apply availability to ventilation
|
|
796
|
+
new_vent = self.ventilation.duplicate()
|
|
797
|
+
new_vent.schedule = self.hvac.doas_availability_schedule
|
|
798
|
+
self.ventilation = new_vent
|
|
799
|
+
self.hvac = i_sys
|
|
800
|
+
|
|
801
|
+
def add_daylight_control_to_center(
|
|
802
|
+
self, distance_from_floor, illuminance_setpoint=300, control_fraction=1,
|
|
803
|
+
min_power_input=0.3, min_light_output=0.2, off_at_minimum=False,
|
|
804
|
+
tolerance=0.01):
|
|
805
|
+
"""Try to assign a DaylightingControl object to the center of the Room.
|
|
806
|
+
|
|
807
|
+
If the Room is too concave and the center point does not lie within the
|
|
808
|
+
Room volume, the pole of inaccessibility across the floor geometry will
|
|
809
|
+
be used. If the concave Room has no floors or the pole of inaccessibility
|
|
810
|
+
does not exist inside the Room, this method wil return None and no
|
|
811
|
+
daylighting control will be assigned.
|
|
812
|
+
|
|
813
|
+
Args:
|
|
814
|
+
distance_from_floor: A number for the distance that the daylight sensor
|
|
815
|
+
is from the floor. Typical values are around 0.8 meters.
|
|
816
|
+
illuminance_setpoint: A number for the illuminance setpoint in lux
|
|
817
|
+
beyond which electric lights are dimmed if there is sufficient
|
|
818
|
+
daylight. (Default: 300 lux).
|
|
819
|
+
control_fraction: A number between 0 and 1 that represents the fraction of
|
|
820
|
+
the Room lights that are dimmed when the illuminance at the sensor
|
|
821
|
+
position is at the specified illuminance. 1 indicates that all lights are
|
|
822
|
+
dim-able while 0 indicates that no lights are dim-able. Deeper rooms
|
|
823
|
+
should have lower control fractions to account for the face that the
|
|
824
|
+
lights in the back of the space do not dim in response to suitable
|
|
825
|
+
daylight at the front of the room. (Default: 1).
|
|
826
|
+
min_power_input: A number between 0 and 1 for the the lowest power the
|
|
827
|
+
lighting system can dim down to, expressed as a fraction of maximum
|
|
828
|
+
input power. (Default: 0.3).
|
|
829
|
+
min_light_output: A number between 0 and 1 the lowest lighting output the
|
|
830
|
+
lighting system can dim down to, expressed as a fraction of maximum
|
|
831
|
+
light output. (Default: 0.2).
|
|
832
|
+
off_at_minimum: Boolean to note whether lights should switch off completely
|
|
833
|
+
when they get to the minimum power input. (Default: False).
|
|
834
|
+
tolerance: The maximum difference between x, y, and z values at which
|
|
835
|
+
vertices are considered equivalent. (Default: 0.01, suitable for
|
|
836
|
+
objects in meters).
|
|
837
|
+
|
|
838
|
+
Returns:
|
|
839
|
+
A DaylightingControl object if the sensor was successfully assigned
|
|
840
|
+
to the Room. Will be None if the Room was too short or so concave
|
|
841
|
+
that a sensor could not be assigned.
|
|
842
|
+
"""
|
|
843
|
+
# first compute the Room center point and check the distance_from_floor
|
|
844
|
+
r_geo = self.host.geometry
|
|
845
|
+
cen_pt, min_pt, max_pt = r_geo.center, r_geo.min, r_geo.max
|
|
846
|
+
if max_pt.z - min_pt.z < distance_from_floor:
|
|
847
|
+
return None
|
|
848
|
+
sensor_pt = Point3D(cen_pt.x, cen_pt.y, min_pt.z + distance_from_floor)
|
|
849
|
+
# if the point is not inside the room, try checking the pole of inaccessibility
|
|
850
|
+
if not r_geo.is_point_inside(sensor_pt):
|
|
851
|
+
floor_faces = [face.geometry for face in self.host.faces
|
|
852
|
+
if isinstance(face.type, Floor)]
|
|
853
|
+
if len(floor_faces) == 0:
|
|
854
|
+
return None
|
|
855
|
+
if len(floor_faces) == 1:
|
|
856
|
+
flr_geo = floor_faces[0]
|
|
857
|
+
else:
|
|
858
|
+
flr_pf = Polyface3D.from_faces(floor_faces, tolerance)
|
|
859
|
+
flr_outline = Polyline3D.join_segments(flr_pf.naked_edges, tolerance)[0]
|
|
860
|
+
flr_geo = Face3D(flr_outline.vertices[:-1])
|
|
861
|
+
min_dim = min((max_pt.x - min_pt.x, max_pt.y - min_pt.y))
|
|
862
|
+
p_tol = min_dim / 100
|
|
863
|
+
sensor_pt = flr_geo.pole_of_inaccessibility(p_tol)
|
|
864
|
+
sensor_pt = sensor_pt.move(Vector3D(0, 0, distance_from_floor))
|
|
865
|
+
if not r_geo.is_point_inside(sensor_pt):
|
|
866
|
+
return None
|
|
867
|
+
# create the daylight control sensor at the point
|
|
868
|
+
dl_control = DaylightingControl(
|
|
869
|
+
sensor_pt, illuminance_setpoint, control_fraction,
|
|
870
|
+
min_power_input, min_light_output, off_at_minimum)
|
|
871
|
+
self.daylighting_control = dl_control
|
|
872
|
+
return dl_control
|
|
873
|
+
|
|
874
|
+
def assign_ventilation_opening(self, vent_opening):
|
|
875
|
+
"""Assign a VentilationOpening object to all operable Apertures on this Room.
|
|
876
|
+
|
|
877
|
+
This method will handle the duplication of the VentilationOpening object to
|
|
878
|
+
ensure that each aperture gets a unique object that can export the correct
|
|
879
|
+
area and height properties of its parent.
|
|
880
|
+
|
|
881
|
+
Args:
|
|
882
|
+
vent_opening: A VentilationOpening object to be duplicated and assigned
|
|
883
|
+
to all of the operable apertures of the Room.
|
|
884
|
+
|
|
885
|
+
Returns:
|
|
886
|
+
A list of Apertures for which ventilation opening properties were set.
|
|
887
|
+
This can be used to perform additional operations on the apertures, such
|
|
888
|
+
as changing their construction.
|
|
889
|
+
"""
|
|
890
|
+
operable_aps = []
|
|
891
|
+
for face in self.host.faces:
|
|
892
|
+
for ap in face.apertures:
|
|
893
|
+
if ap.is_operable:
|
|
894
|
+
ap.properties.energy.vent_opening = vent_opening.duplicate()
|
|
895
|
+
operable_aps.append(ap)
|
|
896
|
+
return operable_aps
|
|
897
|
+
|
|
898
|
+
def remove_ventilation_opening(self):
|
|
899
|
+
"""Remove all VentilationOpening objects assigned to the Room's Apertures."""
|
|
900
|
+
for face in self.host.faces:
|
|
901
|
+
for ap in face.apertures:
|
|
902
|
+
ap.properties.energy.vent_opening = None
|
|
903
|
+
|
|
904
|
+
def exterior_afn_from_infiltration_load(self, exterior_face_groups,
|
|
905
|
+
air_density=1.2041, delta_pressure=4):
|
|
906
|
+
"""Assign AirflowNetwork parameters using the room's infiltration rate.
|
|
907
|
+
|
|
908
|
+
This will assign air leakage parameters to the Room's exterior Faces that
|
|
909
|
+
produce a total air flow rate equivalent to the room infiltration rate at
|
|
910
|
+
an envelope pressure difference of 4 Pa. However, the individual flow air
|
|
911
|
+
leakage parameters are not meant to be representative of real values, since the
|
|
912
|
+
infiltration flow rate is an average of the actual, variable surface flow
|
|
913
|
+
dynamics.
|
|
914
|
+
|
|
915
|
+
VentilationOpening objects will be added to Aperture and Door objects if not
|
|
916
|
+
already defined, with the fraction_area_operable set to 0. If VentilationOpening
|
|
917
|
+
objects are already defined, only the parameters defining leakage when the
|
|
918
|
+
openings are closed will be overwritten. AFNCrack objects will be added
|
|
919
|
+
to all external and internal Face objects, and any existing AFNCrack
|
|
920
|
+
objects will be overwritten.
|
|
921
|
+
|
|
922
|
+
Args:
|
|
923
|
+
exterior_face_groups: A tuple with five types of the exterior room envelope
|
|
924
|
+
|
|
925
|
+
- ext_walls - A list of exterior Wall type Face objects.
|
|
926
|
+
|
|
927
|
+
- ext_roofs - A list of exterior RoofCeiling type Face objects.
|
|
928
|
+
|
|
929
|
+
- ext_floors - A list of exterior Floor type Face objects, like you
|
|
930
|
+
would find in a cantilevered Room.
|
|
931
|
+
|
|
932
|
+
- ext_apertures - A list of exterior Aperture Face objects.
|
|
933
|
+
|
|
934
|
+
- ext_doors - A list of exterior Door Face objects.
|
|
935
|
+
|
|
936
|
+
air_density: Air density in kg/m3. (Default: 1.2041 represents
|
|
937
|
+
air density at a temperature of 20 C and 101325 Pa).
|
|
938
|
+
delta_pressure: Reference air pressure difference across the building
|
|
939
|
+
envelope orifice in Pascals used to calculate infiltration crack flow
|
|
940
|
+
coefficients. The resulting average simulated air pressure difference
|
|
941
|
+
will roughly equal this delta_pressure times the nth root of the ratio
|
|
942
|
+
between the simulated and target room infiltration rates::
|
|
943
|
+
|
|
944
|
+
dP_sim = (Q_sim / Q_target)^(1/n) * dP_ref
|
|
945
|
+
|
|
946
|
+
where:
|
|
947
|
+
dP: delta_pressure, the reference air pressure difference [Pa]
|
|
948
|
+
dP_sim: Simulated air pressure difference [Pa]
|
|
949
|
+
Q_sim: Simulated volumetric air flow rate per area [m3/s/m2]
|
|
950
|
+
Q_target: Target volumetric air flow rate per area [m3/s/m2]
|
|
951
|
+
n: Air mass flow exponent [-]
|
|
952
|
+
|
|
953
|
+
If attempting to replicate the room infiltration rate per exterior area,
|
|
954
|
+
delta_pressure should be set to an approximation of the simulated air
|
|
955
|
+
pressure difference described in the above formula. Default 4 represents
|
|
956
|
+
typical building pressures.
|
|
957
|
+
"""
|
|
958
|
+
# simplify parameters
|
|
959
|
+
ext_walls, ext_roofs, ext_floors, ext_apertures, ext_doors = exterior_face_groups
|
|
960
|
+
ext_faces = ext_walls + ext_roofs + ext_floors
|
|
961
|
+
ext_openings = ext_apertures + ext_doors
|
|
962
|
+
infil_flow = self.infiltration.flow_per_exterior_area
|
|
963
|
+
|
|
964
|
+
# derive normalized flow coefficient
|
|
965
|
+
flow_cof_per_area = self.solve_norm_area_flow_coefficient(
|
|
966
|
+
infil_flow, air_density=air_density, delta_pressure=delta_pressure)
|
|
967
|
+
|
|
968
|
+
# add exterior crack leakage components
|
|
969
|
+
for ext_face in ext_faces:
|
|
970
|
+
# Note: this calculation includes opening areas to be consistent with
|
|
971
|
+
# assumption behind the Infiltration Flow per Exterior Area measure.
|
|
972
|
+
flow_cof = flow_cof_per_area * ext_face.area
|
|
973
|
+
ext_face.properties.energy.vent_crack = AFNCrack(flow_cof)
|
|
974
|
+
|
|
975
|
+
# add exterior opening leakage components
|
|
976
|
+
for ext_opening in ext_openings:
|
|
977
|
+
if ext_opening.properties.energy.vent_opening is None:
|
|
978
|
+
if isinstance(ext_opening, Aperture):
|
|
979
|
+
ext_opening.is_operable = True
|
|
980
|
+
ext_opening.properties.energy.vent_opening = \
|
|
981
|
+
VentilationOpening(fraction_area_operable=0.0)
|
|
982
|
+
vent_opening = ext_opening.properties.energy.vent_opening
|
|
983
|
+
# Note: can be calculated with solve_norm_perimeter_flow_coefficient
|
|
984
|
+
# but it adds an additional degree of freedom when attempting to calculate
|
|
985
|
+
# reference delta pressure from simulated delta pressure and infiltration
|
|
986
|
+
# data. Setting to zero simplifies assumptions by constraining infiltration
|
|
987
|
+
# to just area-based method.
|
|
988
|
+
vent_opening.flow_coefficient_closed = 0.0
|
|
989
|
+
vent_opening.flow_exponent_closed = 0.5
|
|
990
|
+
|
|
991
|
+
def envelope_components_by_type(self):
|
|
992
|
+
"""Get groups for room envelope components by boundary condition and type.
|
|
993
|
+
|
|
994
|
+
The groups created by this function correspond to the structure of the
|
|
995
|
+
crack template data used to generate the AirflowNetwork but can be
|
|
996
|
+
useful for other purposes. However, any parts of the envelope with a
|
|
997
|
+
boundary condition other than Outdoors and Surface will be excluded
|
|
998
|
+
(eg. Ground or Adiabatic).
|
|
999
|
+
|
|
1000
|
+
Return:
|
|
1001
|
+
A tuple with five groups of exterior envelope types
|
|
1002
|
+
|
|
1003
|
+
- ext_walls - A list of exterior Wall type Face objects.
|
|
1004
|
+
|
|
1005
|
+
- ext_roofs - A list of exterior RoofCeiling type Face objects.
|
|
1006
|
+
|
|
1007
|
+
- ext_floors - A list of exterior Floor type Face objects, like you
|
|
1008
|
+
would find in a cantilevered Room.
|
|
1009
|
+
|
|
1010
|
+
- ext_apertures - A list of exterior Aperture Face objects.
|
|
1011
|
+
|
|
1012
|
+
- ext_doors - A list of exterior Door Face objects.
|
|
1013
|
+
|
|
1014
|
+
A tuple with four groups of interior faces types
|
|
1015
|
+
|
|
1016
|
+
- int_walls: List of interior Wall type Face objects.
|
|
1017
|
+
|
|
1018
|
+
- int_floorceilings: List of interior RoofCeiling and Floor type Face
|
|
1019
|
+
objects.
|
|
1020
|
+
|
|
1021
|
+
- int_apertures: List of interior Aperture Face objects.
|
|
1022
|
+
|
|
1023
|
+
- int_doors: List of interior Door Face objects.
|
|
1024
|
+
|
|
1025
|
+
- int_air: List of interior Faces with AirBoundary face type.
|
|
1026
|
+
"""
|
|
1027
|
+
ext_walls, ext_roofs, ext_floors, ext_apertures, ext_doors = \
|
|
1028
|
+
[], [], [], [], []
|
|
1029
|
+
int_walls, int_floorceilings, int_apertures, int_doors, int_air = \
|
|
1030
|
+
[], [], [], [], []
|
|
1031
|
+
|
|
1032
|
+
for face in self.host.faces:
|
|
1033
|
+
if isinstance(face.boundary_condition, Outdoors):
|
|
1034
|
+
if isinstance(face.type, Wall):
|
|
1035
|
+
ext_walls.append(face)
|
|
1036
|
+
ext_apertures.extend(face.apertures)
|
|
1037
|
+
ext_doors.extend(face.doors)
|
|
1038
|
+
elif isinstance(face.type, RoofCeiling):
|
|
1039
|
+
ext_roofs.append(face)
|
|
1040
|
+
ext_apertures.extend(face.apertures) # exterior skylights
|
|
1041
|
+
elif isinstance(face.type, Floor):
|
|
1042
|
+
ext_floors.append(face)
|
|
1043
|
+
elif isinstance(face.boundary_condition, Surface):
|
|
1044
|
+
if isinstance(face.type, Wall):
|
|
1045
|
+
int_walls.append(face)
|
|
1046
|
+
int_apertures.extend(face.apertures)
|
|
1047
|
+
int_doors.extend(face.doors)
|
|
1048
|
+
elif isinstance(face.type, RoofCeiling) or isinstance(face.type, Floor):
|
|
1049
|
+
int_floorceilings.append(face)
|
|
1050
|
+
int_apertures.extend(face.apertures) # interior skylights
|
|
1051
|
+
elif isinstance(face.type, AirBoundary):
|
|
1052
|
+
int_air.append(face)
|
|
1053
|
+
|
|
1054
|
+
ext_faces = (ext_walls, ext_roofs, ext_floors,
|
|
1055
|
+
ext_apertures, ext_doors)
|
|
1056
|
+
int_faces = (int_walls, int_floorceilings,
|
|
1057
|
+
int_apertures, int_doors, int_air)
|
|
1058
|
+
|
|
1059
|
+
return ext_faces, int_faces
|
|
1060
|
+
|
|
1061
|
+
def move(self, moving_vec):
|
|
1062
|
+
"""Move this object along a vector.
|
|
1063
|
+
|
|
1064
|
+
Args:
|
|
1065
|
+
moving_vec: A ladybug_geometry Vector3D with the direction and distance
|
|
1066
|
+
to move the object.
|
|
1067
|
+
"""
|
|
1068
|
+
if self.daylighting_control is not None:
|
|
1069
|
+
self.daylighting_control.move(moving_vec)
|
|
1070
|
+
|
|
1071
|
+
def rotate(self, angle, axis, origin):
|
|
1072
|
+
"""Rotate this object by a certain angle around an axis and origin.
|
|
1073
|
+
|
|
1074
|
+
Args:
|
|
1075
|
+
angle: An angle for rotation in degrees.
|
|
1076
|
+
axis: Rotation axis as a Vector3D.
|
|
1077
|
+
origin: A ladybug_geometry Point3D for the origin around which the
|
|
1078
|
+
object will be rotated.
|
|
1079
|
+
"""
|
|
1080
|
+
if self.daylighting_control is not None:
|
|
1081
|
+
self.daylighting_control.rotate(angle, axis, origin)
|
|
1082
|
+
|
|
1083
|
+
def rotate_xy(self, angle, origin):
|
|
1084
|
+
"""Rotate this object counterclockwise in the world XY plane by a certain angle.
|
|
1085
|
+
|
|
1086
|
+
Args:
|
|
1087
|
+
angle: An angle in degrees.
|
|
1088
|
+
origin: A ladybug_geometry Point3D for the origin around which the
|
|
1089
|
+
object will be rotated.
|
|
1090
|
+
"""
|
|
1091
|
+
if self.daylighting_control is not None:
|
|
1092
|
+
self.daylighting_control.rotate_xy(angle, origin)
|
|
1093
|
+
|
|
1094
|
+
def reflect(self, plane):
|
|
1095
|
+
"""Reflect this object across a plane.
|
|
1096
|
+
|
|
1097
|
+
Args:
|
|
1098
|
+
plane: A ladybug_geometry Plane across which the object will
|
|
1099
|
+
be reflected.
|
|
1100
|
+
"""
|
|
1101
|
+
if self.daylighting_control is not None:
|
|
1102
|
+
self.daylighting_control.reflect(plane)
|
|
1103
|
+
|
|
1104
|
+
def scale(self, factor, origin=None):
|
|
1105
|
+
"""Scale this object by a factor from an origin point.
|
|
1106
|
+
|
|
1107
|
+
Args:
|
|
1108
|
+
factor: A number representing how much the object should be scaled.
|
|
1109
|
+
origin: A ladybug_geometry Point3D representing the origin from which
|
|
1110
|
+
to scale. If None, it will be scaled from the World origin (0, 0, 0).
|
|
1111
|
+
"""
|
|
1112
|
+
if self.daylighting_control is not None:
|
|
1113
|
+
self.daylighting_control.scale(factor, origin)
|
|
1114
|
+
|
|
1115
|
+
def make_plenum(self, conditioned=False, remove_infiltration=False,
|
|
1116
|
+
include_floor_area=False):
|
|
1117
|
+
"""Turn the host Room into a plenum with no internal loads.
|
|
1118
|
+
|
|
1119
|
+
This includes removing all people, lighting, equipment, hot water, and
|
|
1120
|
+
mechanical ventilation. By default, the heating/cooling system and
|
|
1121
|
+
setpoints will also be removed but they can optionally be kept. Infiltration
|
|
1122
|
+
is kept by default but can optionally be removed as well.
|
|
1123
|
+
|
|
1124
|
+
This is useful to appropriately assign properties for closets,
|
|
1125
|
+
underfloor spaces, and drop ceilings.
|
|
1126
|
+
|
|
1127
|
+
Args:
|
|
1128
|
+
conditioned: Boolean to indicate whether the plenum is conditioned with a
|
|
1129
|
+
heating/cooling system. If True, the setpoints of the Room will also
|
|
1130
|
+
be kept in addition to the heating/cooling system (Default: False).
|
|
1131
|
+
remove_infiltration: Boolean to indicate whether infiltration should be
|
|
1132
|
+
removed from the Rooms. (Default: False).
|
|
1133
|
+
include_floor_area: Boolean to indicate whether the floor area of the
|
|
1134
|
+
plenum contributes to the Model that it is a part of. Note that this
|
|
1135
|
+
will not affect the floor_area property of this Room but it will
|
|
1136
|
+
ensure the Room's floor area is excluded from any calculations when
|
|
1137
|
+
the Room is part of a Model and when it is simulated in
|
|
1138
|
+
EnergyPlus. (Default: False).
|
|
1139
|
+
"""
|
|
1140
|
+
# remove or add the HVAC system as needed
|
|
1141
|
+
if conditioned and not self.is_conditioned:
|
|
1142
|
+
self.add_default_ideal_air()
|
|
1143
|
+
elif not conditioned:
|
|
1144
|
+
self.hvac = None
|
|
1145
|
+
self._shw = None
|
|
1146
|
+
|
|
1147
|
+
# discount the floor area unless otherwise specified
|
|
1148
|
+
if not include_floor_area:
|
|
1149
|
+
self.host.exclude_floor_area = True
|
|
1150
|
+
else:
|
|
1151
|
+
self.host.exclude_floor_area = False
|
|
1152
|
+
|
|
1153
|
+
# remove the loads and reapply infiltration/setpoints as needed
|
|
1154
|
+
infiltration = None if remove_infiltration else self.infiltration
|
|
1155
|
+
setpt = self.setpoint if conditioned else None
|
|
1156
|
+
self._program_type = None
|
|
1157
|
+
self._people = None
|
|
1158
|
+
self._lighting = None
|
|
1159
|
+
self._electric_equipment = None
|
|
1160
|
+
self._gas_equipment = None
|
|
1161
|
+
self._service_hot_water = None
|
|
1162
|
+
self._ventilation = None
|
|
1163
|
+
self._infiltration = infiltration
|
|
1164
|
+
self._setpoint = setpt
|
|
1165
|
+
self._process_loads = []
|
|
1166
|
+
|
|
1167
|
+
def make_ground(self, soil_construction, include_floor_area=False):
|
|
1168
|
+
"""Change the properties of the host Room to reflect those of a ground surface.
|
|
1169
|
+
|
|
1170
|
+
This is particularly useful for setting up outdoor thermal comfort maps
|
|
1171
|
+
to account for the surface temperature of the ground. Modeling the ground
|
|
1172
|
+
as a room this way will ensure that shadows other objects cast upon it
|
|
1173
|
+
are accounted for along with the storage of heat in the ground surface.
|
|
1174
|
+
|
|
1175
|
+
The turning of a Room into a ground entails:
|
|
1176
|
+
|
|
1177
|
+
* Setting all constructions to be indicative of a certain soil type.
|
|
1178
|
+
* Setting all Faces except the roof to have a Ground boundary condition.
|
|
1179
|
+
* Removing all loads and schedules assigned to the Room.
|
|
1180
|
+
|
|
1181
|
+
Args:
|
|
1182
|
+
soil_construction: An OpaqueConstruction that reflects the soil type of
|
|
1183
|
+
the ground. If a multi-layered construction is input, the multiple
|
|
1184
|
+
layers will only be used for the roof Face of the Room and all other
|
|
1185
|
+
Faces will get a construction with the inner-most layer assigned.
|
|
1186
|
+
If the outer-most material is an EnergyMaterialVegetation and there
|
|
1187
|
+
are no other layers in the construction, the vegetation's soil
|
|
1188
|
+
material will be used for all other Faces.
|
|
1189
|
+
include_floor_area: Boolean to indicate whether the floor area of the ground
|
|
1190
|
+
room contributes to the Model that it is a part of. Note that this
|
|
1191
|
+
will not affect the floor_area property of this Room but it will
|
|
1192
|
+
ensure the Room's floor area is excluded from any calculations when
|
|
1193
|
+
the Room is part of a Model and when it is simulated in
|
|
1194
|
+
EnergyPlus. (Default: False).
|
|
1195
|
+
"""
|
|
1196
|
+
# process the input soil_construction
|
|
1197
|
+
assert isinstance(soil_construction, OpaqueConstruction), 'Expected ' \
|
|
1198
|
+
'OpaqueConstruction for soil_construction. Got {}.'.format(
|
|
1199
|
+
type(soil_construction))
|
|
1200
|
+
int_soil = soil_construction if len(soil_construction.materials) == 1 else \
|
|
1201
|
+
OpaqueConstruction('{}_BelowGrade'.format(soil_construction.identifier),
|
|
1202
|
+
(soil_construction.materials[-1],))
|
|
1203
|
+
if isinstance(int_soil.materials[0], EnergyMaterialVegetation):
|
|
1204
|
+
below_id = '{}_BelowGrade'.format(soil_construction.identifier)
|
|
1205
|
+
int_soil = OpaqueConstruction(below_id, (int_soil.materials[0].soil_layer,))
|
|
1206
|
+
|
|
1207
|
+
# discount the floor area unless otherwise specified
|
|
1208
|
+
if not include_floor_area:
|
|
1209
|
+
self.host.exclude_floor_area = True
|
|
1210
|
+
else:
|
|
1211
|
+
self.host.exclude_floor_area = False
|
|
1212
|
+
|
|
1213
|
+
# reset all of the properties of the room to reflect the ground
|
|
1214
|
+
self.reset_to_default()
|
|
1215
|
+
for face in self.host.faces:
|
|
1216
|
+
face.remove_sub_faces()
|
|
1217
|
+
if isinstance(face.type, RoofCeiling):
|
|
1218
|
+
face.boundary_condition = boundary_conditions.outdoors
|
|
1219
|
+
face.properties.energy.construction = soil_construction
|
|
1220
|
+
else:
|
|
1221
|
+
face.boundary_condition = boundary_conditions.ground
|
|
1222
|
+
face.properties.energy.construction = int_soil
|
|
1223
|
+
|
|
1224
|
+
def reset_loads_to_program(self):
|
|
1225
|
+
"""Reset all loads on the Room to be assigned from the ProgramType.
|
|
1226
|
+
|
|
1227
|
+
This will erase any loads that have been overridden specifically for this Room.
|
|
1228
|
+
"""
|
|
1229
|
+
self._people = None
|
|
1230
|
+
self._lighting = None
|
|
1231
|
+
self._electric_equipment = None
|
|
1232
|
+
self._gas_equipment = None
|
|
1233
|
+
self._service_hot_water = None
|
|
1234
|
+
self._infiltration = None
|
|
1235
|
+
self._ventilation = None
|
|
1236
|
+
self._setpoint = None
|
|
1237
|
+
|
|
1238
|
+
def reset_constructions_to_set(self):
|
|
1239
|
+
"""Reset all constructions on the Room to be assigned from the ConstructionSet.
|
|
1240
|
+
|
|
1241
|
+
This will erase any constructions that have been assigned to individual Faces,
|
|
1242
|
+
Apertures, Doors, and Shades.
|
|
1243
|
+
"""
|
|
1244
|
+
for face in self.host.faces:
|
|
1245
|
+
face.properties.energy.reset_construction_to_set()
|
|
1246
|
+
for shade in self.host.shades:
|
|
1247
|
+
shade.properties.energy.reset_construction_to_set()
|
|
1248
|
+
|
|
1249
|
+
def reset_to_default(self):
|
|
1250
|
+
"""Reset all of the energy properties assigned to this Room to the default.
|
|
1251
|
+
|
|
1252
|
+
This includes resetting all loads, the program, all constructions and the
|
|
1253
|
+
constructions set, and all other energy properties.
|
|
1254
|
+
"""
|
|
1255
|
+
self.reset_loads_to_program()
|
|
1256
|
+
self.reset_constructions_to_set()
|
|
1257
|
+
self._program_type = None
|
|
1258
|
+
self._construction_set = None
|
|
1259
|
+
self._hvac = None
|
|
1260
|
+
self._shw = None
|
|
1261
|
+
self._daylighting_control = None
|
|
1262
|
+
self._window_vent_control = None
|
|
1263
|
+
self._process_loads = []
|
|
1264
|
+
self._fans = []
|
|
1265
|
+
self._internal_masses = []
|
|
1266
|
+
|
|
1267
|
+
@classmethod
|
|
1268
|
+
def from_dict(cls, data, host):
|
|
1269
|
+
"""Create RoomEnergyProperties from a dictionary.
|
|
1270
|
+
|
|
1271
|
+
Note that the dictionary must be a non-abridged version for this
|
|
1272
|
+
classmethod to work.
|
|
1273
|
+
|
|
1274
|
+
Args:
|
|
1275
|
+
data: A dictionary representation of RoomEnergyProperties with the
|
|
1276
|
+
format below.
|
|
1277
|
+
host: A Room object that hosts these properties.
|
|
1278
|
+
|
|
1279
|
+
.. code-block:: python
|
|
1280
|
+
|
|
1281
|
+
{
|
|
1282
|
+
"type": 'RoomEnergyProperties',
|
|
1283
|
+
"construction_set": {}, # A ConstructionSet dictionary
|
|
1284
|
+
"program_type": {}, # A ProgramType dictionary
|
|
1285
|
+
"hvac": {}, # A HVACSystem dictionary
|
|
1286
|
+
"shw": {}, # A SHWSystem dictionary
|
|
1287
|
+
"people":{}, # A People dictionary
|
|
1288
|
+
"lighting": {}, # A Lighting dictionary
|
|
1289
|
+
"electric_equipment": {}, # A ElectricEquipment dictionary
|
|
1290
|
+
"gas_equipment": {}, # A GasEquipment dictionary
|
|
1291
|
+
"service_hot_water": {}, # A ServiceHotWater dictionary
|
|
1292
|
+
"infiltration": {}, # A Infiltration dictionary
|
|
1293
|
+
"ventilation": {}, # A Ventilation dictionary
|
|
1294
|
+
"setpoint": {}, # A Setpoint dictionary
|
|
1295
|
+
"daylighting_control": {}, # A DaylightingControl dictionary
|
|
1296
|
+
"window_vent_control": {}, # A VentilationControl dictionary
|
|
1297
|
+
"fans": [], # An array of VentilationFan dictionaries
|
|
1298
|
+
"process_loads": [], # An array of Process dictionaries
|
|
1299
|
+
"internal_masses": [] # An array of InternalMass dictionaries
|
|
1300
|
+
}
|
|
1301
|
+
"""
|
|
1302
|
+
assert data['type'] == 'RoomEnergyProperties', \
|
|
1303
|
+
'Expected RoomEnergyProperties. Got {}.'.format(data['type'])
|
|
1304
|
+
|
|
1305
|
+
new_prop = cls(host)
|
|
1306
|
+
if 'construction_set' in data and data['construction_set'] is not None:
|
|
1307
|
+
new_prop.construction_set = \
|
|
1308
|
+
ConstructionSet.from_dict(data['construction_set'])
|
|
1309
|
+
if 'program_type' in data and data['program_type'] is not None:
|
|
1310
|
+
new_prop.program_type = ProgramType.from_dict(data['program_type'])
|
|
1311
|
+
if 'hvac' in data and data['hvac'] is not None:
|
|
1312
|
+
hvac_class = HVAC_TYPES_DICT[data['hvac']['type']]
|
|
1313
|
+
new_prop.hvac = hvac_class.from_dict(data['hvac'])
|
|
1314
|
+
if 'shw' in data and data['shw'] is not None:
|
|
1315
|
+
new_prop.shw = SHWSystem.from_dict(data['shw'])
|
|
1316
|
+
|
|
1317
|
+
if 'people' in data and data['people'] is not None:
|
|
1318
|
+
new_prop.people = People.from_dict(data['people'])
|
|
1319
|
+
if 'lighting' in data and data['lighting'] is not None:
|
|
1320
|
+
new_prop.lighting = Lighting.from_dict(data['lighting'])
|
|
1321
|
+
if 'electric_equipment' in data and data['electric_equipment'] is not None:
|
|
1322
|
+
new_prop.electric_equipment = \
|
|
1323
|
+
ElectricEquipment.from_dict(data['electric_equipment'])
|
|
1324
|
+
if 'gas_equipment' in data and data['gas_equipment'] is not None:
|
|
1325
|
+
new_prop.gas_equipment = GasEquipment.from_dict(data['gas_equipment'])
|
|
1326
|
+
if 'service_hot_water' in data and data['service_hot_water'] is not None:
|
|
1327
|
+
new_prop.service_hot_water = \
|
|
1328
|
+
ServiceHotWater.from_dict(data['service_hot_water'])
|
|
1329
|
+
if 'infiltration' in data and data['infiltration'] is not None:
|
|
1330
|
+
new_prop.infiltration = Infiltration.from_dict(data['infiltration'])
|
|
1331
|
+
if 'ventilation' in data and data['ventilation'] is not None:
|
|
1332
|
+
new_prop.ventilation = Ventilation.from_dict(data['ventilation'])
|
|
1333
|
+
if 'setpoint' in data and data['setpoint'] is not None:
|
|
1334
|
+
new_prop.setpoint = Setpoint.from_dict(data['setpoint'])
|
|
1335
|
+
if 'daylighting_control' in data and data['daylighting_control'] is not None:
|
|
1336
|
+
new_prop.daylighting_control = \
|
|
1337
|
+
DaylightingControl.from_dict(data['daylighting_control'])
|
|
1338
|
+
if 'window_vent_control' in data and data['window_vent_control'] is not None:
|
|
1339
|
+
new_prop.window_vent_control = \
|
|
1340
|
+
VentilationControl.from_dict(data['window_vent_control'])
|
|
1341
|
+
if 'fans' in data and data['fans'] is not None:
|
|
1342
|
+
new_prop.fans = [VentilationFan.from_dict(dat) for dat in data['fans']]
|
|
1343
|
+
if 'process_loads' in data and data['process_loads'] is not None:
|
|
1344
|
+
new_prop.process_loads = \
|
|
1345
|
+
[Process.from_dict(dat) for dat in data['process_loads']]
|
|
1346
|
+
if 'internal_masses' in data and data['internal_masses'] is not None:
|
|
1347
|
+
new_prop.internal_masses = \
|
|
1348
|
+
[InternalMass.from_dict(dat) for dat in data['internal_masses']]
|
|
1349
|
+
|
|
1350
|
+
return new_prop
|
|
1351
|
+
|
|
1352
|
+
def apply_properties_from_dict(
|
|
1353
|
+
self, abridged_data, construction_sets, program_types,
|
|
1354
|
+
hvacs, shws, schedules, constructions):
|
|
1355
|
+
"""Apply properties from a RoomEnergyPropertiesAbridged dictionary.
|
|
1356
|
+
|
|
1357
|
+
Args:
|
|
1358
|
+
abridged_data: A RoomEnergyPropertiesAbridged dictionary (typically
|
|
1359
|
+
coming from a Model).
|
|
1360
|
+
construction_sets: A dictionary of ConstructionSets with identifiers
|
|
1361
|
+
of the sets as keys, which will be used to re-assign construction_sets.
|
|
1362
|
+
program_types: A dictionary of ProgramTypes with identifiers of the types as
|
|
1363
|
+
keys, which will be used to re-assign program_types.
|
|
1364
|
+
hvacs: A dictionary of HVACSystems with the identifiers of the systems as
|
|
1365
|
+
keys, which will be used to re-assign hvac to the Room.
|
|
1366
|
+
shws: A dictionary of SHWSystems with the identifiers of the systems as
|
|
1367
|
+
keys, which will be used to re-assign shw to the Room.
|
|
1368
|
+
schedules: A dictionary of Schedules with identifiers of the schedules as
|
|
1369
|
+
keys, which will be used to re-assign schedules.
|
|
1370
|
+
constructions: A dictionary with construction identifiers as keys
|
|
1371
|
+
and honeybee construction objects as values.
|
|
1372
|
+
"""
|
|
1373
|
+
base_e = 'Room {1} "{0}" was not found in {1}s.'
|
|
1374
|
+
if 'construction_set' in abridged_data and \
|
|
1375
|
+
abridged_data['construction_set'] is not None:
|
|
1376
|
+
try:
|
|
1377
|
+
self.construction_set = \
|
|
1378
|
+
construction_sets[abridged_data['construction_set']]
|
|
1379
|
+
except KeyError:
|
|
1380
|
+
raise ValueError(
|
|
1381
|
+
base_e.format(abridged_data['construction_set'], 'construction_set'))
|
|
1382
|
+
if 'program_type' in abridged_data and abridged_data['program_type'] is not None:
|
|
1383
|
+
try:
|
|
1384
|
+
self.program_type = program_types[abridged_data['program_type']]
|
|
1385
|
+
except KeyError:
|
|
1386
|
+
raise ValueError(
|
|
1387
|
+
base_e.format(abridged_data['program_type'], 'program_type'))
|
|
1388
|
+
if 'hvac' in abridged_data and abridged_data['hvac'] is not None:
|
|
1389
|
+
try:
|
|
1390
|
+
self.hvac = hvacs[abridged_data['hvac']]
|
|
1391
|
+
except KeyError:
|
|
1392
|
+
raise ValueError(base_e.format(abridged_data['hvac'], 'hvac'))
|
|
1393
|
+
if 'shw' in abridged_data and abridged_data['shw'] is not None:
|
|
1394
|
+
try:
|
|
1395
|
+
self.shw = shws[abridged_data['shw']]
|
|
1396
|
+
except KeyError:
|
|
1397
|
+
raise ValueError(base_e.format(abridged_data['shw'], 'shw'))
|
|
1398
|
+
|
|
1399
|
+
if 'people' in abridged_data and abridged_data['people'] is not None:
|
|
1400
|
+
self.people = People.from_dict_abridged(
|
|
1401
|
+
abridged_data['people'], schedules)
|
|
1402
|
+
if 'lighting' in abridged_data and abridged_data['lighting'] is not None:
|
|
1403
|
+
self.lighting = Lighting.from_dict_abridged(
|
|
1404
|
+
abridged_data['lighting'], schedules)
|
|
1405
|
+
if 'electric_equipment' in abridged_data and \
|
|
1406
|
+
abridged_data['electric_equipment'] is not None:
|
|
1407
|
+
self.electric_equipment = ElectricEquipment.from_dict_abridged(
|
|
1408
|
+
abridged_data['electric_equipment'], schedules)
|
|
1409
|
+
if 'gas_equipment' in abridged_data and \
|
|
1410
|
+
abridged_data['gas_equipment'] is not None:
|
|
1411
|
+
self.gas_equipment = GasEquipment.from_dict_abridged(
|
|
1412
|
+
abridged_data['gas_equipment'], schedules)
|
|
1413
|
+
if 'service_hot_water' in abridged_data and \
|
|
1414
|
+
abridged_data['service_hot_water'] is not None:
|
|
1415
|
+
self.service_hot_water = ServiceHotWater.from_dict_abridged(
|
|
1416
|
+
abridged_data['service_hot_water'], schedules)
|
|
1417
|
+
if 'infiltration' in abridged_data and abridged_data['infiltration'] is not None:
|
|
1418
|
+
self.infiltration = Infiltration.from_dict_abridged(
|
|
1419
|
+
abridged_data['infiltration'], schedules)
|
|
1420
|
+
if 'ventilation' in abridged_data and abridged_data['ventilation'] is not None:
|
|
1421
|
+
self.ventilation = Ventilation.from_dict_abridged(
|
|
1422
|
+
abridged_data['ventilation'], schedules)
|
|
1423
|
+
if 'setpoint' in abridged_data and abridged_data['setpoint'] is not None:
|
|
1424
|
+
self.setpoint = Setpoint.from_dict_abridged(
|
|
1425
|
+
abridged_data['setpoint'], schedules)
|
|
1426
|
+
if 'daylighting_control' in abridged_data and \
|
|
1427
|
+
abridged_data['daylighting_control'] is not None:
|
|
1428
|
+
self.daylighting_control = DaylightingControl.from_dict(
|
|
1429
|
+
abridged_data['daylighting_control'])
|
|
1430
|
+
if 'window_vent_control' in abridged_data and \
|
|
1431
|
+
abridged_data['window_vent_control'] is not None:
|
|
1432
|
+
self.window_vent_control = VentilationControl.from_dict_abridged(
|
|
1433
|
+
abridged_data['window_vent_control'], schedules)
|
|
1434
|
+
if 'fans' in abridged_data and abridged_data['fans'] is not None:
|
|
1435
|
+
for dat in abridged_data['fans']:
|
|
1436
|
+
if dat['type'] == 'VentilationFan':
|
|
1437
|
+
self._fans.append(VentilationFan.from_dict(dat))
|
|
1438
|
+
else:
|
|
1439
|
+
self._fans.append(VentilationFan.from_dict_abridged(dat, schedules))
|
|
1440
|
+
if 'process_loads' in abridged_data and \
|
|
1441
|
+
abridged_data['process_loads'] is not None:
|
|
1442
|
+
for dat in abridged_data['process_loads']:
|
|
1443
|
+
if dat['type'] == 'Process':
|
|
1444
|
+
self._process_loads.append(Process.from_dict(dat))
|
|
1445
|
+
else:
|
|
1446
|
+
self._process_loads.append(
|
|
1447
|
+
Process.from_dict_abridged(dat, schedules))
|
|
1448
|
+
if 'internal_masses' in abridged_data and \
|
|
1449
|
+
abridged_data['internal_masses'] is not None:
|
|
1450
|
+
for dat in abridged_data['internal_masses']:
|
|
1451
|
+
if dat['type'] == 'InternalMass':
|
|
1452
|
+
self._internal_masses.append(InternalMass.from_dict(dat))
|
|
1453
|
+
else:
|
|
1454
|
+
self._internal_masses.append(
|
|
1455
|
+
InternalMass.from_dict_abridged(dat, constructions))
|
|
1456
|
+
|
|
1457
|
+
def to_dict(self, abridged=False):
|
|
1458
|
+
"""Return Room energy properties as a dictionary.
|
|
1459
|
+
|
|
1460
|
+
Args:
|
|
1461
|
+
abridged: Boolean for whether the full dictionary of the Room should
|
|
1462
|
+
be written (False) or just the identifier of the the individual
|
|
1463
|
+
properties (True). Default: False.
|
|
1464
|
+
"""
|
|
1465
|
+
base = {'energy': {}}
|
|
1466
|
+
base['energy']['type'] = 'RoomEnergyProperties' if not \
|
|
1467
|
+
abridged else 'RoomEnergyPropertiesAbridged'
|
|
1468
|
+
|
|
1469
|
+
# write the ProgramType into the dictionary
|
|
1470
|
+
if self._program_type is not None:
|
|
1471
|
+
base['energy']['program_type'] = \
|
|
1472
|
+
self._program_type.identifier if abridged else \
|
|
1473
|
+
self._program_type.to_dict()
|
|
1474
|
+
|
|
1475
|
+
# write the ConstructionSet into the dictionary
|
|
1476
|
+
if self._construction_set is not None:
|
|
1477
|
+
base['energy']['construction_set'] = \
|
|
1478
|
+
self._construction_set.identifier if abridged else \
|
|
1479
|
+
self._construction_set.to_dict()
|
|
1480
|
+
|
|
1481
|
+
# write the hvac into the dictionary
|
|
1482
|
+
if self._hvac is not None:
|
|
1483
|
+
base['energy']['hvac'] = \
|
|
1484
|
+
self._hvac.identifier if abridged else self._hvac.to_dict()
|
|
1485
|
+
|
|
1486
|
+
# write the shw into the dictionary
|
|
1487
|
+
if self._shw is not None:
|
|
1488
|
+
base['energy']['shw'] = \
|
|
1489
|
+
self._shw.identifier if abridged else self._shw.to_dict()
|
|
1490
|
+
|
|
1491
|
+
# write any room-specific overriding properties into the dictionary
|
|
1492
|
+
if self._people is not None:
|
|
1493
|
+
base['energy']['people'] = self._people.to_dict(abridged)
|
|
1494
|
+
if self._lighting is not None:
|
|
1495
|
+
base['energy']['lighting'] = self._lighting.to_dict(abridged)
|
|
1496
|
+
if self._electric_equipment is not None:
|
|
1497
|
+
base['energy']['electric_equipment'] = \
|
|
1498
|
+
self._electric_equipment.to_dict(abridged)
|
|
1499
|
+
if self._gas_equipment is not None:
|
|
1500
|
+
base['energy']['gas_equipment'] = self._gas_equipment.to_dict(abridged)
|
|
1501
|
+
if self._service_hot_water is not None:
|
|
1502
|
+
base['energy']['service_hot_water'] = \
|
|
1503
|
+
self._service_hot_water.to_dict(abridged)
|
|
1504
|
+
if self._infiltration is not None:
|
|
1505
|
+
base['energy']['infiltration'] = self._infiltration.to_dict(abridged)
|
|
1506
|
+
if self._ventilation is not None:
|
|
1507
|
+
base['energy']['ventilation'] = self._ventilation.to_dict(abridged)
|
|
1508
|
+
if self._setpoint is not None:
|
|
1509
|
+
base['energy']['setpoint'] = self._setpoint.to_dict(abridged)
|
|
1510
|
+
if self._daylighting_control is not None:
|
|
1511
|
+
base['energy']['daylighting_control'] = self._daylighting_control.to_dict()
|
|
1512
|
+
if self._window_vent_control is not None:
|
|
1513
|
+
base['energy']['window_vent_control'] = \
|
|
1514
|
+
self._window_vent_control.to_dict(abridged)
|
|
1515
|
+
if len(self._fans) != 0:
|
|
1516
|
+
base['energy']['fans'] = [f.to_dict(abridged) for f in self._fans]
|
|
1517
|
+
if len(self._process_loads) != 0:
|
|
1518
|
+
base['energy']['process_loads'] = \
|
|
1519
|
+
[p.to_dict(abridged) for p in self._process_loads]
|
|
1520
|
+
if len(self._internal_masses) != 0:
|
|
1521
|
+
base['energy']['internal_masses'] = \
|
|
1522
|
+
[m.to_dict(abridged) for m in self._internal_masses]
|
|
1523
|
+
|
|
1524
|
+
return base
|
|
1525
|
+
|
|
1526
|
+
def duplicate(self, new_host=None):
|
|
1527
|
+
"""Get a copy of this object.
|
|
1528
|
+
|
|
1529
|
+
Args:
|
|
1530
|
+
new_host: A new Room object that hosts these properties.
|
|
1531
|
+
If None, the properties will be duplicated with the same host.
|
|
1532
|
+
"""
|
|
1533
|
+
_host = new_host or self._host
|
|
1534
|
+
new_room = RoomEnergyProperties(
|
|
1535
|
+
_host, self._program_type, self._construction_set, self._hvac, self._shw)
|
|
1536
|
+
new_room._people = self._people
|
|
1537
|
+
new_room._lighting = self._lighting
|
|
1538
|
+
new_room._electric_equipment = self._electric_equipment
|
|
1539
|
+
new_room._gas_equipment = self._gas_equipment
|
|
1540
|
+
new_room._service_hot_water = self._service_hot_water
|
|
1541
|
+
new_room._infiltration = self._infiltration
|
|
1542
|
+
new_room._ventilation = self._ventilation
|
|
1543
|
+
new_room._setpoint = self._setpoint
|
|
1544
|
+
if self._daylighting_control is not None:
|
|
1545
|
+
new_room._daylighting_control = self._daylighting_control.duplicate()
|
|
1546
|
+
new_room._daylighting_control._parent = _host
|
|
1547
|
+
new_room._window_vent_control = self._window_vent_control
|
|
1548
|
+
new_room._fans = self._fans[:] # copy fans list
|
|
1549
|
+
new_room._process_loads = self._process_loads[:] # copy process load list
|
|
1550
|
+
new_room._internal_masses = self._internal_masses[:] # copy internal masses list
|
|
1551
|
+
return new_room
|
|
1552
|
+
|
|
1553
|
+
def is_equivalent(self, other):
|
|
1554
|
+
"""Check to see if these energy properties are equivalent to another object.
|
|
1555
|
+
|
|
1556
|
+
This will only be True if all properties match (except for the host) and
|
|
1557
|
+
will otherwise be False.
|
|
1558
|
+
"""
|
|
1559
|
+
if not is_equivalent(self._program_type, other._program_type):
|
|
1560
|
+
return False
|
|
1561
|
+
if not is_equivalent(self._construction_set, other._construction_set):
|
|
1562
|
+
return False
|
|
1563
|
+
if not is_equivalent(self._hvac, other._hvac):
|
|
1564
|
+
return False
|
|
1565
|
+
if not is_equivalent(self._shw, other._shw):
|
|
1566
|
+
return False
|
|
1567
|
+
if not is_equivalent(self._people, other._people):
|
|
1568
|
+
return False
|
|
1569
|
+
if not is_equivalent(self._lighting, other._lighting):
|
|
1570
|
+
return False
|
|
1571
|
+
if not is_equivalent(self._electric_equipment, other._electric_equipment):
|
|
1572
|
+
return False
|
|
1573
|
+
if not is_equivalent(self._gas_equipment, other._gas_equipment):
|
|
1574
|
+
return False
|
|
1575
|
+
if not is_equivalent(self._service_hot_water, other._service_hot_water):
|
|
1576
|
+
return False
|
|
1577
|
+
if not is_equivalent(self._infiltration, other._infiltration):
|
|
1578
|
+
return False
|
|
1579
|
+
if not is_equivalent(self._ventilation, other._ventilation):
|
|
1580
|
+
return False
|
|
1581
|
+
if not is_equivalent(self._setpoint, other._setpoint):
|
|
1582
|
+
return False
|
|
1583
|
+
if not is_equivalent(self._daylighting_control, other._daylighting_control):
|
|
1584
|
+
return False
|
|
1585
|
+
if not is_equivalent(self._window_vent_control, other._window_vent_control):
|
|
1586
|
+
return False
|
|
1587
|
+
return True
|
|
1588
|
+
|
|
1589
|
+
@staticmethod
|
|
1590
|
+
def solve_norm_area_flow_coefficient(flow_per_exterior_area, flow_exponent=0.65,
|
|
1591
|
+
air_density=1.2041, delta_pressure=4):
|
|
1592
|
+
"""Get normalized mass flow coefficient [kg/(m2 s P^n)] from infiltration per area.
|
|
1593
|
+
|
|
1594
|
+
The normalized area air mass flow coefficient is derived from a zone's
|
|
1595
|
+
infiltration flow rate using the power law relationship between pressure
|
|
1596
|
+
and air flow::
|
|
1597
|
+
|
|
1598
|
+
Qva * d = Cqa * dP^n
|
|
1599
|
+
|
|
1600
|
+
where:
|
|
1601
|
+
Cqa: Air mass flow coefficient per unit meter at 1 Pa [kg/m2/s/P^n]
|
|
1602
|
+
Qva: Volumetric air flow rate per area [m3/s/m2]
|
|
1603
|
+
d: Air density [kg/m3]
|
|
1604
|
+
dP: Change in pressure across building envelope orifice [Pa]
|
|
1605
|
+
n: Air mass flow exponent [-]
|
|
1606
|
+
|
|
1607
|
+
Rearranged to solve for ``Cqa`` ::
|
|
1608
|
+
|
|
1609
|
+
Cqa = (Qva * d) / dP^n
|
|
1610
|
+
|
|
1611
|
+
The resulting value has units of kg/(m2-s-P^n) @ <delta_pressure> Pa, while the
|
|
1612
|
+
EnergyPlus AirflowNetwork requires this value to be in kg/(s-Pa) @ 1 Pa. Thus
|
|
1613
|
+
this value needs to be multiplied by its corresponding exposed surface area.
|
|
1614
|
+
Since the actual ratio between mass infiltration and pressure difference (raised
|
|
1615
|
+
by n) is constant, we assume solving for the flow coefficient at the
|
|
1616
|
+
delta_pressure value is equivalent to solving it at the required 1 Pa.
|
|
1617
|
+
|
|
1618
|
+
Args:
|
|
1619
|
+
flow_per_exterior_area: A numerical value for the intensity of infiltration
|
|
1620
|
+
in m3/s per square meter of exterior surface area.
|
|
1621
|
+
air_density: Air density in kg/m3. (Default: 1.2041 represents
|
|
1622
|
+
air density at a temperature of 20 C and 101325 Pa).
|
|
1623
|
+
flow_exponent: A numerical value for the air mass flow exponent.
|
|
1624
|
+
(Default: 0.65).
|
|
1625
|
+
delta_pressure: Reference air pressure difference across building envelope
|
|
1626
|
+
orifice in Pascals. Default 4 represents typical building pressures.
|
|
1627
|
+
|
|
1628
|
+
Returns:
|
|
1629
|
+
Air mass flow coefficient per unit meter at 1 Pa [kg/m2/s/P^n]
|
|
1630
|
+
"""
|
|
1631
|
+
qva = flow_per_exterior_area
|
|
1632
|
+
n = flow_exponent
|
|
1633
|
+
d = air_density
|
|
1634
|
+
dp = delta_pressure
|
|
1635
|
+
# group similar magnitude terms to preserve precision
|
|
1636
|
+
return (qva * d) / (dp ** n)
|
|
1637
|
+
|
|
1638
|
+
@staticmethod
|
|
1639
|
+
def solve_norm_perimeter_flow_coefficient(norm_area_flow_coefficient, face_area,
|
|
1640
|
+
face_perimeter):
|
|
1641
|
+
"""Get mass flow coefficient [kg/(s m P^n)] from a normalized one and geometry.
|
|
1642
|
+
|
|
1643
|
+
This parameter is used to derive air flow for the four cracks around the
|
|
1644
|
+
perimeter of a closed window or door: one along the bottom, one along the top,
|
|
1645
|
+
and one on each side. Since this value is derived from the infiltration flow
|
|
1646
|
+
rate per exterior area, which represents an average over many types of air
|
|
1647
|
+
leakage rates, this value is not intended to be representative of actual opening
|
|
1648
|
+
edges flow coefficients. The normalized perimeter air mass flow coefficient is
|
|
1649
|
+
derived from its infiltration flow rate using the following formula::
|
|
1650
|
+
|
|
1651
|
+
Qva * d * A = Cql * L * dP^n
|
|
1652
|
+
|
|
1653
|
+
where:
|
|
1654
|
+
Cql: Air mass flow coefficient per unit length at 1 Pa [kg/m/s/P^n]
|
|
1655
|
+
Qva: Volumetric air flow rate per length [m3/s/m]
|
|
1656
|
+
d: Air density [kg/m3]
|
|
1657
|
+
A: Surface area of opening [m2]
|
|
1658
|
+
L: Surface perimeter of opening [m]
|
|
1659
|
+
dP: Change in pressure across building envelope [Pa]
|
|
1660
|
+
n: Air mass flow exponent [-]
|
|
1661
|
+
|
|
1662
|
+
Since ``(Qva * d) / dP^n`` equals ``Cqa`` the normalized area flow coefficient,
|
|
1663
|
+
this can be simplified and rearranged to solve for ``Cql`` with the following
|
|
1664
|
+
formula::
|
|
1665
|
+
|
|
1666
|
+
(Cqa * dP^n) * A = Cql * L * dP^n
|
|
1667
|
+
Cql = ((Cqa * dP^n) * A) / (L * dP^n)
|
|
1668
|
+
= Cqa * A / L
|
|
1669
|
+
|
|
1670
|
+
|
|
1671
|
+
The resulting value has units of kg/(m-s-P^n) @ <delta_pressure> Pa, while the
|
|
1672
|
+
EnergyPlus AirflowNetwork requires this value to be in kg/(s-Pa) @ 1 Pa. Thus
|
|
1673
|
+
unlike the surface area flow_coefficient, this coefficient is normalized per
|
|
1674
|
+
unit length. Since the actual ratio between mass infiltration and pressure
|
|
1675
|
+
difference (raised by n) is constant, we assume solving for the flow coefficient
|
|
1676
|
+
at the delta_pressure value is equivalent to solving it at the required 1 Pa.
|
|
1677
|
+
|
|
1678
|
+
Args:
|
|
1679
|
+
norm_area_flow_coefficient: Air mass flow coefficient per unit meter at
|
|
1680
|
+
1 Pa [kg/m2/s/P^n]
|
|
1681
|
+
face_area: A numerical value for the total exterior area in m2.
|
|
1682
|
+
face_perimeter: A numerical value for the total exterior perimeter in meters.
|
|
1683
|
+
|
|
1684
|
+
Returns:
|
|
1685
|
+
Air mass flow coefficient per unit length at 1 Pa [kg/m/s/P^n]
|
|
1686
|
+
"""
|
|
1687
|
+
cqa = norm_area_flow_coefficient
|
|
1688
|
+
a = face_area
|
|
1689
|
+
ln = face_perimeter
|
|
1690
|
+
# group similar magnitude terms to preserve precision
|
|
1691
|
+
return cqa * (a / ln)
|
|
1692
|
+
|
|
1693
|
+
def _dup_load(self, load_name, load_class):
|
|
1694
|
+
"""Duplicate a load object assigned to this Room or get a new one if none exists.
|
|
1695
|
+
|
|
1696
|
+
Args:
|
|
1697
|
+
load_name: Text for the name of the property as it appears on this object.
|
|
1698
|
+
This is used both to retrieve the load and to man an identifier
|
|
1699
|
+
for it. (eg. "people", "lighting").
|
|
1700
|
+
load_class: The class of the load object (eg. People).
|
|
1701
|
+
"""
|
|
1702
|
+
load_obj = getattr(self, load_name)
|
|
1703
|
+
load_id = '{}_{}'.format(self.host.identifier, load_name)
|
|
1704
|
+
try: # duplicate the Room's current load object and give it a unique ID
|
|
1705
|
+
dup_load = load_obj.duplicate()
|
|
1706
|
+
dup_load.identifier = load_id
|
|
1707
|
+
return dup_load
|
|
1708
|
+
except AttributeError: # currently no load object; create a new one
|
|
1709
|
+
if load_class != Ventilation:
|
|
1710
|
+
return load_class(load_id, 0, always_on)
|
|
1711
|
+
else: # it's a ventilation object
|
|
1712
|
+
return load_class(load_id)
|
|
1713
|
+
|
|
1714
|
+
def _absolute_by_floor(self, load_obj, property_name, value, conversion):
|
|
1715
|
+
"""Set a floor-normalized load object to have an absolute value for a property.
|
|
1716
|
+
"""
|
|
1717
|
+
try:
|
|
1718
|
+
floor_area = self.host.floor_area * conversion ** 2
|
|
1719
|
+
setattr(load_obj, property_name, value / floor_area)
|
|
1720
|
+
except ZeroDivisionError:
|
|
1721
|
+
pass # no floor area; just leave the load level as is
|
|
1722
|
+
|
|
1723
|
+
def _segment_wall_face(self, segment, tolerance):
|
|
1724
|
+
"""Get a Wall Face that corresponds with a certain wall segment.
|
|
1725
|
+
|
|
1726
|
+
Args:
|
|
1727
|
+
segment: A LineSegment3D along one of the walls of the room.
|
|
1728
|
+
tolerance: The maximum difference between values at which point vertices
|
|
1729
|
+
are considered to be the same.
|
|
1730
|
+
"""
|
|
1731
|
+
for face in self.host.faces:
|
|
1732
|
+
if isinstance(face.type, Wall):
|
|
1733
|
+
fg = face.geometry
|
|
1734
|
+
segs = fg.boundary_segments if not fg.has_holes else \
|
|
1735
|
+
fg.boundary_segments + fg.hole_segments
|
|
1736
|
+
for seg in segs:
|
|
1737
|
+
try:
|
|
1738
|
+
if seg.is_colinear(segment, tolerance, 0.0349):
|
|
1739
|
+
return face
|
|
1740
|
+
except ZeroDivisionError:
|
|
1741
|
+
pass # zero-length segment; just ignore it
|
|
1742
|
+
|
|
1743
|
+
def ToString(self):
|
|
1744
|
+
return self.__repr__()
|
|
1745
|
+
|
|
1746
|
+
def __repr__(self):
|
|
1747
|
+
return 'Room Energy Properties: [host: {}]'.format(self.host.display_name)
|