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,890 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
"""Module for constructing thermal load balances from energy result data collections."""
|
|
3
|
+
from __future__ import division
|
|
4
|
+
|
|
5
|
+
from ladybug.sql import SQLiteResult
|
|
6
|
+
from ladybug.datacollection import HourlyContinuousCollection
|
|
7
|
+
from ladybug.header import Header
|
|
8
|
+
from ladybug.datatype.energyintensity import EnergyIntensity
|
|
9
|
+
from honeybee.model import Model as hb_model
|
|
10
|
+
from honeybee.aperture import Aperture
|
|
11
|
+
from honeybee.door import Door
|
|
12
|
+
from honeybee.facetype import Wall, RoofCeiling, Floor, AirBoundary
|
|
13
|
+
from honeybee.typing import float_positive
|
|
14
|
+
from honeybee.boundarycondition import Outdoors, Ground, Surface
|
|
15
|
+
|
|
16
|
+
from ..boundarycondition import Adiabatic, OtherSideTemperature
|
|
17
|
+
from .match import match_rooms_to_data, match_faces_to_data
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class LoadBalance(object):
|
|
21
|
+
"""Object for constructing thermal load balances from energy results.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
rooms: An array of honeybee Rooms, which will be matched to the input
|
|
25
|
+
data collections and used to determine which heat flow values are
|
|
26
|
+
through outdoor surfaces. The length of these Rooms does not have
|
|
27
|
+
to match the data collections and this object will only construct a
|
|
28
|
+
load balance for rooms that are found to be matching.
|
|
29
|
+
cooling_data: Array of data collections for 'Zone Ideal Loads Supply Air Total
|
|
30
|
+
Cooling Energy' that correspond to the input rooms.
|
|
31
|
+
heating_data: Array of data collections for 'Zone Ideal Loads Supply Air Total
|
|
32
|
+
Heating Energy' that correspond to the input rooms.
|
|
33
|
+
lighting_data: Array of data collections for 'Zone Lights Total Heating
|
|
34
|
+
Energy' that correspond to the input rooms.
|
|
35
|
+
electric_equip_data: Array of data collections for 'Zone Electric Equipment
|
|
36
|
+
Total Heating Energy' that correspond to the input rooms.
|
|
37
|
+
gas_equip_data: Array of data collections for 'Zone Gas Equipment Total
|
|
38
|
+
Heating Energy' that correspond to the input rooms.
|
|
39
|
+
process_data: Array of data collections for 'Zone Other Equipment Total
|
|
40
|
+
Heating Energy' that correspond to the input rooms.
|
|
41
|
+
service_hot_water_data: Array of data collections for 'Water Use Equipment
|
|
42
|
+
Zone Heat Gain Energy' that correspond to the input rooms.
|
|
43
|
+
people_data: Array of data collections for 'Zone People Total Heating
|
|
44
|
+
Energy' that correspond to the input rooms.
|
|
45
|
+
solar_data: Array of data collections for 'Zone Windows Total Transmitted
|
|
46
|
+
Solar Radiation Energy' that correspond to the input rooms.
|
|
47
|
+
infiltration_data: The infiltration heat loss (negative) or heat gain (positive),
|
|
48
|
+
which can be obtained by subtracting 'Zone Infiltration Total Heat
|
|
49
|
+
Loss Energy' data collections from 'Zone Infiltration Total Heat
|
|
50
|
+
Gain Energy' data collections.
|
|
51
|
+
mech_ventilation_data: The ventilation heat loss (negative) or heat gain
|
|
52
|
+
(positive) as a result of meeting minimum outdoor air requirements
|
|
53
|
+
with the mechanical system. This can be obtained by first subtracting
|
|
54
|
+
'Zone Ideal Loads Zone Total Energy' from 'Zone Ideal Loads Supply
|
|
55
|
+
Air Total Energy' for both heating and cooling loads. Then the
|
|
56
|
+
resulting heating load (ventilation loss) should be subtracted
|
|
57
|
+
from the cooling load (ventilation gain).
|
|
58
|
+
nat_ventilation_data: The natural ventilation heat loss (negative) or
|
|
59
|
+
heat gain (positive) which can be obtained by subtracting 'Zone
|
|
60
|
+
Ventilation Total Heat Loss Energy' data collections from 'Zone
|
|
61
|
+
Ventilation Total Heat Gain Energy' data collections.
|
|
62
|
+
surface_flow_data: The surface heat loss (negative) or heat gain (positive),
|
|
63
|
+
which can be obtained for opaque surfaces with a 'Surface Average Face
|
|
64
|
+
Conduction Heat Transfer Energy' data collection. For fenestration
|
|
65
|
+
surfaces, it can be obtained by by subtracting 'Surface Window Heat
|
|
66
|
+
Loss Energy' data collections from 'Surface Window Heat Gain Energy'
|
|
67
|
+
data collections.
|
|
68
|
+
units: Text for the units system in which the room geometry exists.
|
|
69
|
+
Choose from the following:
|
|
70
|
+
|
|
71
|
+
* Meters
|
|
72
|
+
* Millimeters
|
|
73
|
+
* Feet
|
|
74
|
+
* Inches
|
|
75
|
+
* Centimeters
|
|
76
|
+
|
|
77
|
+
use_all_solar: Boolean to note whether all of the solar_data should be used in
|
|
78
|
+
the resulting load balance, regardless of whether it has been matched to
|
|
79
|
+
the rooms. This is useful for the case that air boundaries exist in a model
|
|
80
|
+
and solar data is reported for grouped zones. (Default: False).
|
|
81
|
+
|
|
82
|
+
Properties:
|
|
83
|
+
* rooms
|
|
84
|
+
* floor_area
|
|
85
|
+
* cooling
|
|
86
|
+
* heating
|
|
87
|
+
* lighting
|
|
88
|
+
* electric_equip
|
|
89
|
+
* gas_equip
|
|
90
|
+
* process
|
|
91
|
+
* service_hot_water
|
|
92
|
+
* people
|
|
93
|
+
* solar
|
|
94
|
+
* infiltration
|
|
95
|
+
* mech_ventilation
|
|
96
|
+
* nat_ventilation
|
|
97
|
+
* conduction
|
|
98
|
+
* window_conduction
|
|
99
|
+
* opaque_conduction
|
|
100
|
+
* wall_conduction
|
|
101
|
+
* roof_conduction
|
|
102
|
+
* floor_conduction
|
|
103
|
+
* storage
|
|
104
|
+
* floor_storage
|
|
105
|
+
* wall_storage
|
|
106
|
+
* window_storage
|
|
107
|
+
* air_storage
|
|
108
|
+
* units
|
|
109
|
+
"""
|
|
110
|
+
__slots__ = \
|
|
111
|
+
('_rooms', '_floor_area', '_units', '_cooling', '_heating', '_lighting',
|
|
112
|
+
'_electric_equip', '_gas_equip', '_process', '_service_hot_water', '_people',
|
|
113
|
+
'_solar', '_infiltration', '_mech_ventilation', '_nat_ventilation',
|
|
114
|
+
'_conduction', '_window_conduction', '_opaque_conduction',
|
|
115
|
+
'_wall_conduction', '_roof_conduction', '_floor_conduction',
|
|
116
|
+
'_floor_storage', '_wall_storage', '_window_storage',
|
|
117
|
+
'_air_storage', '_storage')
|
|
118
|
+
|
|
119
|
+
# global constants used throughout the class
|
|
120
|
+
UNITS = hb_model.UNITS
|
|
121
|
+
EXTERIOR_BCS = (Outdoors, Ground, OtherSideTemperature)
|
|
122
|
+
INTERIOR_BCS = (Surface, Adiabatic)
|
|
123
|
+
|
|
124
|
+
# List of all EnergyPlus output strings relevant for thermal load balances
|
|
125
|
+
COOLING = (
|
|
126
|
+
'Zone Ideal Loads Supply Air Total Cooling Energy',
|
|
127
|
+
'Zone Ideal Loads Supply Air Sensible Cooling Energy',
|
|
128
|
+
'Zone Ideal Loads Supply Air Latent Cooling Energy')
|
|
129
|
+
HEATING = (
|
|
130
|
+
'Zone Ideal Loads Supply Air Total Heating Energy',
|
|
131
|
+
'Zone Ideal Loads Supply Air Sensible Heating Energy',
|
|
132
|
+
'Zone Ideal Loads Supply Air Latent Heating Energy')
|
|
133
|
+
LIGHTING = (
|
|
134
|
+
'Zone Lights Electricity Energy',
|
|
135
|
+
'Zone Lights Total Heating Energy')
|
|
136
|
+
ELECTRIC_EQUIP = (
|
|
137
|
+
'Zone Electric Equipment Electricity Energy',
|
|
138
|
+
'Zone Electric Equipment Total Heating Energy',
|
|
139
|
+
'Zone Electric Equipment Radiant Heating Energy',
|
|
140
|
+
'Zone Electric Equipment Convective Heating Energy',
|
|
141
|
+
'Zone Electric Equipment Latent Gain Energy')
|
|
142
|
+
GAS_EQUIP = (
|
|
143
|
+
'Zone Gas Equipment NaturalGas Energy',
|
|
144
|
+
'Zone Gas Equipment Total Heating Energy',
|
|
145
|
+
'Zone Gas Equipment Radiant Heating Energy',
|
|
146
|
+
'Zone Gas Equipment Convective Heating Energy',
|
|
147
|
+
'Zone Gas Equipment Latent Gain Energy')
|
|
148
|
+
PROCESS = (
|
|
149
|
+
'Zone Other Equipment Total Heating Energy',
|
|
150
|
+
'Zone Other Equipment Convective Heating Energy',
|
|
151
|
+
'Zone Other Equipment Radiant Heating Energy',
|
|
152
|
+
'Zone Other Equipment Latent Heating Energy')
|
|
153
|
+
HOT_WATER = (
|
|
154
|
+
'Water Use Equipment Zone Sensible Heat Gain Energy',
|
|
155
|
+
'Water Use Equipment Zone Latent Gain Energy')
|
|
156
|
+
PEOPLE_GAIN = (
|
|
157
|
+
'Zone People Total Heating Energy',
|
|
158
|
+
'Zone People Sensible Heating Energy',
|
|
159
|
+
'Zone People Latent Gain Energy')
|
|
160
|
+
SOLAR_GAIN = (
|
|
161
|
+
'Zone Windows Total Transmitted Solar Radiation Energy', # remove after E+ 24.2
|
|
162
|
+
'Enclosure Windows Total Transmitted Solar Radiation Energy')
|
|
163
|
+
INFIL_GAIN = (
|
|
164
|
+
'Zone Infiltration Total Heat Gain Energy',
|
|
165
|
+
'Zone Infiltration Sensible Heat Gain Energy',
|
|
166
|
+
'Zone Infiltration Latent Heat Gain Energy',
|
|
167
|
+
'AFN Zone Infiltration Sensible Heat Gain Energy',
|
|
168
|
+
'AFN Zone Infiltration Latent Heat Gain Energy')
|
|
169
|
+
INFIL_LOSS = (
|
|
170
|
+
'Zone Infiltration Total Heat Loss Energy',
|
|
171
|
+
'Zone Infiltration Sensible Heat Loss Energy',
|
|
172
|
+
'Zone Infiltration Latent Heat Loss Energy',
|
|
173
|
+
'AFN Zone Infiltration Sensible Heat Loss Energy',
|
|
174
|
+
'AFN Zone Infiltration Latent Heat Loss Energy')
|
|
175
|
+
VENT_LOSS = (
|
|
176
|
+
'Zone Ideal Loads Zone Total Heating Energy',
|
|
177
|
+
'Zone Ideal Loads Zone Sensible Heating Energy',
|
|
178
|
+
'Zone Ideal Loads Zone Latent Heating Energy')
|
|
179
|
+
VENT_GAIN = (
|
|
180
|
+
'Zone Ideal Loads Zone Total Cooling Energy',
|
|
181
|
+
'Zone Ideal Loads Zone Sensible Cooling Energy',
|
|
182
|
+
'Zone Ideal Loads Zone Latent Cooling Energy')
|
|
183
|
+
NAT_VENT_GAIN = (
|
|
184
|
+
'Zone Ventilation Sensible Heat Gain Energy',
|
|
185
|
+
'Zone Ventilation Latent Heat Gain Energy',
|
|
186
|
+
'AFN Zone Ventilation Sensible Heat Gain Energy',
|
|
187
|
+
'AFN Zone Ventilation Latent Heat Gain Energy')
|
|
188
|
+
NAT_VENT_LOSS = (
|
|
189
|
+
'Zone Ventilation Sensible Heat Loss Energy',
|
|
190
|
+
'Zone Ventilation Latent Heat Loss Energy',
|
|
191
|
+
'AFN Zone Ventilation Sensible Heat Loss Energy',
|
|
192
|
+
'AFN Zone Ventilation Latent Heat Loss Energy')
|
|
193
|
+
OPAQUE_ENERGY_FLOW = 'Surface Inside Face Conduction Heat Transfer Energy'
|
|
194
|
+
WINDOW_LOSS = 'Surface Window Heat Loss Energy'
|
|
195
|
+
WINDOW_GAIN = 'Surface Window Heat Gain Energy'
|
|
196
|
+
|
|
197
|
+
def __init__(self, rooms, cooling_data=None, heating_data=None, lighting_data=None,
|
|
198
|
+
electric_equip_data=None, gas_equip_data=None, process_data=None,
|
|
199
|
+
service_hot_water_data=None, people_data=None,
|
|
200
|
+
solar_data=None, infiltration_data=None, mech_ventilation_data=None,
|
|
201
|
+
nat_ventilation_data=None, surface_flow_data=None, units='Meters',
|
|
202
|
+
use_all_solar=False):
|
|
203
|
+
"""Initialize LoadBalance."""
|
|
204
|
+
# Set defaults for values that are computed upon request
|
|
205
|
+
self._conduction = None
|
|
206
|
+
self._window_conduction = None
|
|
207
|
+
self._opaque_conduction = None
|
|
208
|
+
self._storage = None
|
|
209
|
+
self._air_storage = None
|
|
210
|
+
self.units = units
|
|
211
|
+
self._floor_area = None
|
|
212
|
+
|
|
213
|
+
# match all of the room-level inputs
|
|
214
|
+
self._cooling = self._match_room_input(
|
|
215
|
+
cooling_data, rooms, 'Cooling', negate=True)
|
|
216
|
+
self._heating = self._match_room_input(
|
|
217
|
+
heating_data, rooms, 'Heating')
|
|
218
|
+
self._lighting = self._match_room_input(
|
|
219
|
+
lighting_data, rooms, 'Lighting', 'Lights')
|
|
220
|
+
self._electric_equip = self._match_room_input(
|
|
221
|
+
electric_equip_data, rooms, 'Electric Equipment', mult_per_room=True)
|
|
222
|
+
self._gas_equip = self._match_room_input(
|
|
223
|
+
gas_equip_data, rooms, 'Gas Equipment', mult_per_room=True)
|
|
224
|
+
self._process = self._match_room_input(
|
|
225
|
+
process_data, rooms, 'Process Equipment', 'Other Equipment',
|
|
226
|
+
mult_per_room=True)
|
|
227
|
+
self._service_hot_water = self._match_room_input(
|
|
228
|
+
service_hot_water_data, rooms, 'Service Hot Water',
|
|
229
|
+
'Water Use Equipment Zone', mult_per_room=True,
|
|
230
|
+
use_mult=False, space_based=True)
|
|
231
|
+
self._people = self._match_room_input(
|
|
232
|
+
people_data, rooms, 'People')
|
|
233
|
+
self._solar = self._match_room_input(
|
|
234
|
+
solar_data, rooms, 'Solar', use_all=use_all_solar, space_based=True)
|
|
235
|
+
self._mech_ventilation = self._match_room_input(
|
|
236
|
+
mech_ventilation_data, rooms, 'Mechanical Ventilation', 'Ventilation')
|
|
237
|
+
self._nat_ventilation = self._match_room_input(
|
|
238
|
+
nat_ventilation_data, rooms, 'Natural Ventilation', 'Ventilation')
|
|
239
|
+
self._infiltration = self._match_room_input(
|
|
240
|
+
infiltration_data, rooms, 'Infiltration')
|
|
241
|
+
|
|
242
|
+
# match the surface-level inputs
|
|
243
|
+
_win_f, self._wall_conduction, self._roof_conduction, self._floor_conduction, \
|
|
244
|
+
self._window_storage, self._wall_storage, self._floor_storage = \
|
|
245
|
+
self._match_face_input(surface_flow_data, rooms)
|
|
246
|
+
if _win_f is not None and self._solar is not None:
|
|
247
|
+
# compute just the conduction loss/gain from the windows
|
|
248
|
+
self._window_conduction = _win_f - self._solar
|
|
249
|
+
self._window_conduction.header.metadata['type'] = 'Window Conduction'
|
|
250
|
+
# when using all of the rooms, reset the property
|
|
251
|
+
if use_all_solar:
|
|
252
|
+
self._rooms = rooms
|
|
253
|
+
|
|
254
|
+
@classmethod
|
|
255
|
+
def from_sql_file(cls, model, sql_path):
|
|
256
|
+
"""Create a LoadBalance object from an EnergyPlus SQLite result file.
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
model: A honeybee Model, which will have its rooms matched to the input
|
|
260
|
+
data collections and used to determine which heat flow values are
|
|
261
|
+
through outdoor surfaces.
|
|
262
|
+
sql_path: Full path to an SQLite file that was generated by EnergyPlus.
|
|
263
|
+
this file should have the relevant load balance outputs in the
|
|
264
|
+
ReportData table.
|
|
265
|
+
"""
|
|
266
|
+
# load all of the relevant data from the SQL
|
|
267
|
+
cooling, heating, lighting, electric_equip, gas_equip, process, \
|
|
268
|
+
how_water, people_gain, solar_gain, infiltration, mech_vent, nat_vent, \
|
|
269
|
+
face_energy_flow = cls.load_data_from_sql(sql_path)
|
|
270
|
+
|
|
271
|
+
# create the LoadBalance object
|
|
272
|
+
bal_obj = cls(
|
|
273
|
+
model.rooms, cooling, heating, lighting, electric_equip, gas_equip, process,
|
|
274
|
+
how_water, people_gain, solar_gain, infiltration, mech_vent, nat_vent,
|
|
275
|
+
face_energy_flow, model.units, use_all_solar=True)
|
|
276
|
+
bal_obj.floor_area = bal_obj._area_as_meters_feet(model.floor_area)
|
|
277
|
+
return bal_obj
|
|
278
|
+
|
|
279
|
+
@classmethod
|
|
280
|
+
def from_sql_file_rooms(cls, rooms, sql_path, units='Meters'):
|
|
281
|
+
"""Create a LoadBalance object from a SQLite result file and Rooms.
|
|
282
|
+
|
|
283
|
+
This method will perform a check such that, if the rooms do not have
|
|
284
|
+
properties that can be matched to certain data in the SQL, no exception
|
|
285
|
+
will be raised. Note that, if the input rooms contain AirBoundaries, the
|
|
286
|
+
solar term of the resulting balance will not be correct.
|
|
287
|
+
|
|
288
|
+
Args:
|
|
289
|
+
rooms: An array of honeybee Rooms, which will be matched to the input
|
|
290
|
+
data collections and used to determine which heat flow values are
|
|
291
|
+
through outdoor surfaces. The length of these Rooms does not have
|
|
292
|
+
to match the data collections and this object will only construct a
|
|
293
|
+
load balance for rooms that are found to be matching.
|
|
294
|
+
sql_path: Full path to an SQLite file that was generated by EnergyPlus.
|
|
295
|
+
this file should have the relevant load balance outputs in the
|
|
296
|
+
ReportData table.
|
|
297
|
+
units: Text for the units system in which the room geometry exists.
|
|
298
|
+
Choose from the following:
|
|
299
|
+
|
|
300
|
+
* Meters
|
|
301
|
+
* Millimeters
|
|
302
|
+
* Feet
|
|
303
|
+
* Inches
|
|
304
|
+
* Centimeters
|
|
305
|
+
"""
|
|
306
|
+
# load all of the relevant data from the SQL
|
|
307
|
+
cooling, heating, lighting, electric_equip, gas_equip, process, \
|
|
308
|
+
how_water, people_gain, solar_gain, infiltration, mech_vent, nat_vent, \
|
|
309
|
+
face_energy_flow = cls.load_data_from_sql(sql_path)
|
|
310
|
+
|
|
311
|
+
# check that the data can be matched to the input Rooms
|
|
312
|
+
cooling = cls._check_data_matching(rooms, cooling)
|
|
313
|
+
heating = cls._check_data_matching(rooms, heating)
|
|
314
|
+
lighting = cls._check_data_matching(rooms, lighting)
|
|
315
|
+
electric_equip = cls._check_data_matching(rooms, electric_equip)
|
|
316
|
+
gas_equip = cls._check_data_matching(rooms, gas_equip)
|
|
317
|
+
process = cls._check_data_matching(rooms, process)
|
|
318
|
+
how_water = cls._check_data_matching(rooms, how_water)
|
|
319
|
+
people_gain = cls._check_data_matching(rooms, people_gain)
|
|
320
|
+
solar_gain = cls._check_data_matching(rooms, solar_gain)
|
|
321
|
+
infiltration = cls._check_data_matching(rooms, infiltration)
|
|
322
|
+
mech_vent = cls._check_data_matching(rooms, mech_vent)
|
|
323
|
+
nat_vent = cls._check_data_matching(rooms, nat_vent)
|
|
324
|
+
|
|
325
|
+
# create the LoadBalance object
|
|
326
|
+
return cls(
|
|
327
|
+
rooms, cooling, heating, lighting, electric_equip, gas_equip, process,
|
|
328
|
+
how_water, people_gain, solar_gain, infiltration, mech_vent, nat_vent,
|
|
329
|
+
face_energy_flow, units)
|
|
330
|
+
|
|
331
|
+
@property
|
|
332
|
+
def rooms(self):
|
|
333
|
+
"""Get the Rooms that have been successfully matched to the input data."""
|
|
334
|
+
return self._rooms
|
|
335
|
+
|
|
336
|
+
@property
|
|
337
|
+
def cooling(self):
|
|
338
|
+
"""Get a data collection for the cooling of the load balance."""
|
|
339
|
+
return self._cooling
|
|
340
|
+
|
|
341
|
+
@property
|
|
342
|
+
def heating(self):
|
|
343
|
+
"""Get a data collection for the heating of the load balance."""
|
|
344
|
+
return self._heating
|
|
345
|
+
|
|
346
|
+
@property
|
|
347
|
+
def lighting(self):
|
|
348
|
+
"""Get a data collection for the lighting gain of the load balance."""
|
|
349
|
+
return self._lighting
|
|
350
|
+
|
|
351
|
+
@property
|
|
352
|
+
def electric_equip(self):
|
|
353
|
+
"""Get a data collection for the electric equipment gain of the load balance."""
|
|
354
|
+
return self._electric_equip
|
|
355
|
+
|
|
356
|
+
@property
|
|
357
|
+
def gas_equip(self):
|
|
358
|
+
"""Get a data collection for the gas equipment gain of the load balance."""
|
|
359
|
+
return self._gas_equip
|
|
360
|
+
|
|
361
|
+
@property
|
|
362
|
+
def process(self):
|
|
363
|
+
"""Get a data collection for the process load gain of the load balance."""
|
|
364
|
+
return self._process
|
|
365
|
+
|
|
366
|
+
@property
|
|
367
|
+
def service_hot_water(self):
|
|
368
|
+
"""Get a data collection for the service hot water gain of the load balance."""
|
|
369
|
+
return self._service_hot_water
|
|
370
|
+
|
|
371
|
+
@property
|
|
372
|
+
def people(self):
|
|
373
|
+
"""Get a data collection for the people gain of the load balance."""
|
|
374
|
+
return self._people
|
|
375
|
+
|
|
376
|
+
@property
|
|
377
|
+
def solar(self):
|
|
378
|
+
"""Get a data collection for the solar gain of the load balance."""
|
|
379
|
+
return self._solar
|
|
380
|
+
|
|
381
|
+
@property
|
|
382
|
+
def infiltration(self):
|
|
383
|
+
"""Get a data collection for the infiltration gain/loss of the load balance."""
|
|
384
|
+
return self._infiltration
|
|
385
|
+
|
|
386
|
+
@property
|
|
387
|
+
def mech_ventilation(self):
|
|
388
|
+
"""Get a data collection for the mechanical ventilation of the load balance."""
|
|
389
|
+
return self._mech_ventilation
|
|
390
|
+
|
|
391
|
+
@property
|
|
392
|
+
def nat_ventilation(self):
|
|
393
|
+
"""Get a data collection for the natural ventilation of the load balance."""
|
|
394
|
+
return self._nat_ventilation
|
|
395
|
+
|
|
396
|
+
@property
|
|
397
|
+
def conduction(self):
|
|
398
|
+
"""Get a data collection for all conduction loss/gain of the load balance."""
|
|
399
|
+
if self._conduction is None:
|
|
400
|
+
if self.window_conduction is not None and self.opaque_conduction is not None:
|
|
401
|
+
self._conduction = self.window_conduction + self.opaque_conduction
|
|
402
|
+
self._conduction.header.metadata['type'] = 'Conduction'
|
|
403
|
+
return self._conduction
|
|
404
|
+
|
|
405
|
+
@property
|
|
406
|
+
def window_conduction(self):
|
|
407
|
+
"""Get a data collection for window conduction loss/gain of the load balance."""
|
|
408
|
+
return self._window_conduction
|
|
409
|
+
|
|
410
|
+
@property
|
|
411
|
+
def opaque_conduction(self):
|
|
412
|
+
"""Get a data collection for opaque conduction loss/gain of the load balance."""
|
|
413
|
+
if self._opaque_conduction is None:
|
|
414
|
+
if self.wall_conduction is not None and self.roof_conduction is not None \
|
|
415
|
+
and self.floor_conduction is not None:
|
|
416
|
+
self._opaque_conduction = self.wall_conduction + \
|
|
417
|
+
self.roof_conduction + self.floor_conduction
|
|
418
|
+
self._opaque_conduction = self._opaque_conduction.duplicate()
|
|
419
|
+
self._opaque_conduction.header.metadata['type'] = 'Opaque Conduction'
|
|
420
|
+
return self._opaque_conduction
|
|
421
|
+
|
|
422
|
+
@property
|
|
423
|
+
def wall_conduction(self):
|
|
424
|
+
"""Get a data collection for wall conduction loss/gain of the load balance."""
|
|
425
|
+
return self._wall_conduction
|
|
426
|
+
|
|
427
|
+
@property
|
|
428
|
+
def roof_conduction(self):
|
|
429
|
+
"""Get a data collection for roof conduction loss/gain of the load balance."""
|
|
430
|
+
return self._roof_conduction
|
|
431
|
+
|
|
432
|
+
@property
|
|
433
|
+
def floor_conduction(self):
|
|
434
|
+
"""Get a data collection for floor conduction loss/gain of the load balance."""
|
|
435
|
+
return self._floor_conduction
|
|
436
|
+
|
|
437
|
+
@property
|
|
438
|
+
def storage(self):
|
|
439
|
+
"""Get a data collection for the remainder of the load balance, indicating storage.
|
|
440
|
+
"""
|
|
441
|
+
if self._storage is None:
|
|
442
|
+
other_terms = self.load_balance_terms()
|
|
443
|
+
if len(other_terms) != 0:
|
|
444
|
+
_storage = other_terms[0]
|
|
445
|
+
for coll in other_terms[1:]:
|
|
446
|
+
_storage = _storage + coll
|
|
447
|
+
self._storage = -_storage.duplicate() # dup to avoid editing header
|
|
448
|
+
self._storage.header.metadata['type'] = 'Storage'
|
|
449
|
+
return self._storage
|
|
450
|
+
|
|
451
|
+
@property
|
|
452
|
+
def wall_storage(self):
|
|
453
|
+
"""Get a data collection for heat loss/gain from storage within interior walls.
|
|
454
|
+
|
|
455
|
+
When the model is for a single room or subset of a whole building, this
|
|
456
|
+
term will indicate heat loss to adjacent Rooms through interior Walls.
|
|
457
|
+
It also includes all heat exchange that happens across AirBoundaries.
|
|
458
|
+
"""
|
|
459
|
+
return self._wall_storage
|
|
460
|
+
|
|
461
|
+
@property
|
|
462
|
+
def floor_storage(self):
|
|
463
|
+
"""Get a data collection for heat loss/gain from storage within interior floors.
|
|
464
|
+
|
|
465
|
+
When the model is for a single room or subset of a whole building, this
|
|
466
|
+
term will indicate heat loss to adjacent Rooms through interior Floors.
|
|
467
|
+
It also includes all heat exchange that happens across Ceilings.
|
|
468
|
+
"""
|
|
469
|
+
return self._floor_storage
|
|
470
|
+
|
|
471
|
+
@property
|
|
472
|
+
def window_storage(self):
|
|
473
|
+
"""Get a data collection for heat loss/gain from storage within interior windows.
|
|
474
|
+
|
|
475
|
+
This will also include any heat transfer from doors. For full building
|
|
476
|
+
models, this term should typically be very close to zero given that
|
|
477
|
+
EnergyPlus Fenestration surfaces don't typically have thermal mass. However,
|
|
478
|
+
when the model is for a single room or subset of a whole building, this
|
|
479
|
+
term will indicate heat loss to adjacent Rooms through interior windows.
|
|
480
|
+
"""
|
|
481
|
+
return self._window_storage
|
|
482
|
+
|
|
483
|
+
@property
|
|
484
|
+
def air_storage(self):
|
|
485
|
+
"""Get a data collection for heat loss/gain from storage within Room air.
|
|
486
|
+
|
|
487
|
+
This term is computed as the remainder of the load balance after storage
|
|
488
|
+
within walls, floors and windows is removed.
|
|
489
|
+
"""
|
|
490
|
+
if self._air_storage is None:
|
|
491
|
+
air_storage = self.storage
|
|
492
|
+
if air_storage is not None:
|
|
493
|
+
others = [self.wall_storage, self.floor_storage, self.window_storage]
|
|
494
|
+
for coll in others:
|
|
495
|
+
air_storage = air_storage - coll
|
|
496
|
+
self._air_storage = air_storage.duplicate() # dup to avoid editing header
|
|
497
|
+
self._air_storage.header.metadata['type'] = 'Air Storage'
|
|
498
|
+
return self._air_storage
|
|
499
|
+
|
|
500
|
+
@property
|
|
501
|
+
def units(self):
|
|
502
|
+
"""Get or set text for the units system in which the room geometry exists."""
|
|
503
|
+
return self._units
|
|
504
|
+
|
|
505
|
+
@units.setter
|
|
506
|
+
def units(self, value):
|
|
507
|
+
assert value in self.UNITS, '{} is not supported as a units system. ' \
|
|
508
|
+
'Choose from the following: {}'.format(value, self.units)
|
|
509
|
+
self._units = value
|
|
510
|
+
|
|
511
|
+
@property
|
|
512
|
+
def floor_area(self):
|
|
513
|
+
"""Get or set a number for the total floor area in square meters or square feet.
|
|
514
|
+
|
|
515
|
+
By default, this is the floor area of only the successfully-matched rooms.
|
|
516
|
+
|
|
517
|
+
This floor area accounts for Room multipliers and will always be in either
|
|
518
|
+
square meters or square feet depending on whether this object's units are
|
|
519
|
+
either SI or IP.
|
|
520
|
+
"""
|
|
521
|
+
if self._floor_area is not None:
|
|
522
|
+
return self._floor_area
|
|
523
|
+
else:
|
|
524
|
+
base_area = sum([room.floor_area * room.multiplier for room in self._rooms
|
|
525
|
+
if not room.exclude_floor_area])
|
|
526
|
+
return self._area_as_meters_feet(base_area)
|
|
527
|
+
|
|
528
|
+
@floor_area.setter
|
|
529
|
+
def floor_area(self, value):
|
|
530
|
+
self._floor_area = float_positive(value)
|
|
531
|
+
|
|
532
|
+
def load_balance_terms(
|
|
533
|
+
self, floor_normalized=False, include_storage=False, detailed_faces=False):
|
|
534
|
+
"""Get a list of data collections with one for each term in the load balance.
|
|
535
|
+
|
|
536
|
+
Terms of the load balance that are None will be excluded from this list.
|
|
537
|
+
Conduction terms will only appear as opaque and window conduction terms.
|
|
538
|
+
|
|
539
|
+
Args:
|
|
540
|
+
floor_normalized: Boolean to note whether all of the output data
|
|
541
|
+
collections should have values that are normalized by the Room
|
|
542
|
+
floor area.
|
|
543
|
+
include_storage: Boolean to note whether the storage term should
|
|
544
|
+
be included in the list.
|
|
545
|
+
detailed_faces: Boolean to note whether the opaque conduction losses
|
|
546
|
+
should be broken down into walls, roofs, and floors. Setting
|
|
547
|
+
this to True will also cause storage to be broken down into
|
|
548
|
+
storage in walls, floors, windows, and air. (Default: False).
|
|
549
|
+
"""
|
|
550
|
+
if detailed_faces:
|
|
551
|
+
all_terms = [
|
|
552
|
+
self.heating, self.solar, self.service_hot_water, self.gas_equip,
|
|
553
|
+
self.process, self.electric_equip, self.lighting, self.people,
|
|
554
|
+
self.infiltration, self.mech_ventilation, self.nat_ventilation,
|
|
555
|
+
self.wall_conduction, self.roof_conduction, self.floor_conduction,
|
|
556
|
+
self.window_conduction, self.cooling
|
|
557
|
+
]
|
|
558
|
+
else:
|
|
559
|
+
all_terms = [
|
|
560
|
+
self.heating, self.solar, self.service_hot_water, self.gas_equip,
|
|
561
|
+
self.process, self.electric_equip, self.lighting, self.people,
|
|
562
|
+
self.infiltration, self.mech_ventilation, self.nat_ventilation,
|
|
563
|
+
self.opaque_conduction, self.window_conduction, self.cooling
|
|
564
|
+
]
|
|
565
|
+
bal_terms = [term for term in all_terms if term is not None and term != []]
|
|
566
|
+
|
|
567
|
+
if include_storage:
|
|
568
|
+
if detailed_faces:
|
|
569
|
+
storages = [
|
|
570
|
+
self.floor_storage, self.wall_storage,
|
|
571
|
+
self.window_storage, self.air_storage
|
|
572
|
+
]
|
|
573
|
+
bal_terms.extend(storages)
|
|
574
|
+
else:
|
|
575
|
+
bal_terms.append(self.storage)
|
|
576
|
+
|
|
577
|
+
if floor_normalized:
|
|
578
|
+
flr_area = self.floor_area
|
|
579
|
+
if flr_area == 0: # rare case but we don't want a ZeroDivision error
|
|
580
|
+
return bal_terms
|
|
581
|
+
is_ip = True if self.units in ('Feet', 'Inches') else False
|
|
582
|
+
bal_terms = [self._normalize_collection(term, flr_area, is_ip)
|
|
583
|
+
for term in bal_terms]
|
|
584
|
+
return bal_terms
|
|
585
|
+
|
|
586
|
+
@staticmethod
|
|
587
|
+
def load_data_from_sql(sql_path):
|
|
588
|
+
"""Load all data collections relevant to load balances from a SQL file.
|
|
589
|
+
|
|
590
|
+
Args:
|
|
591
|
+
sql_path: Full path to an SQLite file that was generated by EnergyPlus.
|
|
592
|
+
this file should have the relevant load balance outputs in the
|
|
593
|
+
ReportData table.
|
|
594
|
+
|
|
595
|
+
Returns:
|
|
596
|
+
A tuple where each item is a list of data collections relevant to
|
|
597
|
+
load balances.
|
|
598
|
+
"""
|
|
599
|
+
# create the SQL result parsing object
|
|
600
|
+
sql_obj = SQLiteResult(sql_path)
|
|
601
|
+
|
|
602
|
+
# get all of the results relevant for gains and losses
|
|
603
|
+
cooling = sql_obj.data_collections_by_output_name(LoadBalance.COOLING)
|
|
604
|
+
heating = sql_obj.data_collections_by_output_name(LoadBalance.HEATING)
|
|
605
|
+
lighting = sql_obj.data_collections_by_output_name(LoadBalance.LIGHTING)
|
|
606
|
+
people_gain = sql_obj.data_collections_by_output_name(LoadBalance.PEOPLE_GAIN)
|
|
607
|
+
solar_gain = sql_obj.data_collections_by_output_name(LoadBalance.SOLAR_GAIN)
|
|
608
|
+
infil_gain = sql_obj.data_collections_by_output_name(LoadBalance.INFIL_GAIN)
|
|
609
|
+
infil_loss = sql_obj.data_collections_by_output_name(LoadBalance.INFIL_LOSS)
|
|
610
|
+
vent_loss = sql_obj.data_collections_by_output_name(LoadBalance.VENT_LOSS)
|
|
611
|
+
vent_gain = sql_obj.data_collections_by_output_name(LoadBalance.VENT_GAIN)
|
|
612
|
+
nat_vent_gain = \
|
|
613
|
+
sql_obj.data_collections_by_output_name(LoadBalance.NAT_VENT_GAIN)
|
|
614
|
+
nat_vent_loss = \
|
|
615
|
+
sql_obj.data_collections_by_output_name(LoadBalance.NAT_VENT_LOSS)
|
|
616
|
+
|
|
617
|
+
# handle the case that both total elect/gas energy and zone gain are requested
|
|
618
|
+
electric_equip = \
|
|
619
|
+
sql_obj.data_collections_by_output_name(LoadBalance.ELECTRIC_EQUIP[1])
|
|
620
|
+
if len(electric_equip) == 0:
|
|
621
|
+
electric_equip = \
|
|
622
|
+
sql_obj.data_collections_by_output_name(LoadBalance.ELECTRIC_EQUIP)
|
|
623
|
+
gas_equip = sql_obj.data_collections_by_output_name(LoadBalance.GAS_EQUIP[1])
|
|
624
|
+
if len(gas_equip) == 0:
|
|
625
|
+
gas_equip = sql_obj.data_collections_by_output_name(LoadBalance.GAS_EQUIP)
|
|
626
|
+
process = sql_obj.data_collections_by_output_name(LoadBalance.PROCESS)
|
|
627
|
+
how_water = sql_obj.data_collections_by_output_name(LoadBalance.HOT_WATER[1])
|
|
628
|
+
if len(how_water) == 0:
|
|
629
|
+
how_water = sql_obj.data_collections_by_output_name(LoadBalance.HOT_WATER)
|
|
630
|
+
|
|
631
|
+
# subtract losses from gains
|
|
632
|
+
infiltration = None
|
|
633
|
+
mech_vent = None
|
|
634
|
+
nat_vent = None
|
|
635
|
+
if len(infil_gain) == len(infil_loss):
|
|
636
|
+
infiltration = LoadBalance.subtract_loss_from_gain(infil_gain, infil_loss)
|
|
637
|
+
if len(vent_gain) == len(vent_loss) == len(cooling) == len(heating):
|
|
638
|
+
mech_vent = \
|
|
639
|
+
LoadBalance.mech_vent_loss_gain(vent_gain, vent_loss, cooling, heating)
|
|
640
|
+
if len(nat_vent_gain) == len(nat_vent_loss):
|
|
641
|
+
nat_vent = LoadBalance.subtract_loss_from_gain(nat_vent_gain, nat_vent_loss)
|
|
642
|
+
|
|
643
|
+
# get the surface energy flow
|
|
644
|
+
opaque_flow = \
|
|
645
|
+
sql_obj.data_collections_by_output_name(LoadBalance.OPAQUE_ENERGY_FLOW)
|
|
646
|
+
window_loss = sql_obj.data_collections_by_output_name(LoadBalance.WINDOW_LOSS)
|
|
647
|
+
window_gain = sql_obj.data_collections_by_output_name(LoadBalance.WINDOW_GAIN)
|
|
648
|
+
window_flow = []
|
|
649
|
+
if len(window_gain) == len(window_loss):
|
|
650
|
+
window_flow = LoadBalance.subtract_loss_from_gain(window_gain, window_loss)
|
|
651
|
+
face_energy_flow = opaque_flow + window_flow
|
|
652
|
+
|
|
653
|
+
return cooling, heating, lighting, electric_equip, gas_equip, process, \
|
|
654
|
+
how_water, people_gain, solar_gain, infiltration, mech_vent, nat_vent, \
|
|
655
|
+
face_energy_flow
|
|
656
|
+
|
|
657
|
+
@staticmethod
|
|
658
|
+
def subtract_loss_from_gain(load_gain, load_loss):
|
|
659
|
+
"""Subtract an array of load loss data collections from load gain collections.
|
|
660
|
+
|
|
661
|
+
This is what is needed for certain LoadBalance inputs like infiltration
|
|
662
|
+
and natural ventilation.
|
|
663
|
+
|
|
664
|
+
Args:
|
|
665
|
+
load_gain: A list of data collections with load gains.
|
|
666
|
+
load_loss: A list of data collections with load losses.
|
|
667
|
+
"""
|
|
668
|
+
total_loads = []
|
|
669
|
+
for gain, loss in zip(load_gain, load_loss):
|
|
670
|
+
total_load = gain - loss
|
|
671
|
+
total_load.header.metadata['type'] = \
|
|
672
|
+
total_load.header.metadata['type'].replace('Gain ', '')
|
|
673
|
+
total_loads.append(total_load)
|
|
674
|
+
return total_loads
|
|
675
|
+
|
|
676
|
+
@staticmethod
|
|
677
|
+
def mech_vent_loss_gain(zone_cooling, zone_heating, cooling, heating):
|
|
678
|
+
"""Compute mechanical ventilation loss/gain from lists of data collections.
|
|
679
|
+
|
|
680
|
+
Args:
|
|
681
|
+
zone_cooling: A list of data collections for zone-level cooling.
|
|
682
|
+
zone_heating: A list of data collections for zone-level heating.
|
|
683
|
+
cooling: A list of data collections for supply air cooling.
|
|
684
|
+
heating: A list of data collections for supply air heating.
|
|
685
|
+
"""
|
|
686
|
+
mech_vent_loss = LoadBalance.subtract_loss_from_gain(heating, zone_heating)
|
|
687
|
+
mech_vent_gain = LoadBalance.subtract_loss_from_gain(cooling, zone_cooling)
|
|
688
|
+
total_load = LoadBalance.subtract_loss_from_gain(mech_vent_gain, mech_vent_loss)
|
|
689
|
+
mech_vent_load = [data.duplicate() for data in total_load]
|
|
690
|
+
for load in mech_vent_load:
|
|
691
|
+
load.header.metadata['type'] = \
|
|
692
|
+
'Zone Ideal Loads Ventilation Heat Energy'
|
|
693
|
+
return mech_vent_load
|
|
694
|
+
|
|
695
|
+
def _match_room_input(self, data_collections, rooms, data_type,
|
|
696
|
+
type_check_text=None, negate=False, use_all=False,
|
|
697
|
+
mult_per_room=False, space_based=False, use_mult=True):
|
|
698
|
+
"""Match a an array of input data collections to input rooms.
|
|
699
|
+
|
|
700
|
+
Args:
|
|
701
|
+
data_collections: An array of input data collections.
|
|
702
|
+
rooms: An array of input honeybee Rooms.
|
|
703
|
+
data_type: Text for the name of the data type for the totalled collection.
|
|
704
|
+
type_check_text: Optional text, which will be used to check if the input
|
|
705
|
+
data_collections are of the right type.
|
|
706
|
+
negate: Boolean to note whether the values should be negated.
|
|
707
|
+
use_all: Boolean to note whether all data_collections should be used instead
|
|
708
|
+
of those matched to the rooms.
|
|
709
|
+
mult_per_room: Boolean to note whether there are multiple data collections
|
|
710
|
+
for each room, which should be summed together.
|
|
711
|
+
space_based: Boolean to note whether the result is reported on the EnergyPlus
|
|
712
|
+
Space level instead of the Zone level. In this case, the matching to
|
|
713
|
+
the Room will account for the fact that the Space name is the Room
|
|
714
|
+
name with _Space added to it. (Default: False).
|
|
715
|
+
use_mult: Boolean to note whether the results should be multiplied by the
|
|
716
|
+
room multiplier (True) or whether the data type values already
|
|
717
|
+
account for the multiplier (False). (Default: True).
|
|
718
|
+
"""
|
|
719
|
+
# don't match anything if there are no collections
|
|
720
|
+
if data_collections is None or len(data_collections) == 0:
|
|
721
|
+
return None
|
|
722
|
+
|
|
723
|
+
# match the data collections to the rooms
|
|
724
|
+
if use_all: # firs try to see if all objects can be matched
|
|
725
|
+
matched_objs = match_rooms_to_data(
|
|
726
|
+
data_collections, rooms, use_mult, space_based)
|
|
727
|
+
if len(matched_objs) != len(rooms): # take them all
|
|
728
|
+
matched_objs = [(None, data, rm.multiplier)
|
|
729
|
+
for data, rm in zip(data_collections, rooms)]
|
|
730
|
+
elif mult_per_room: # group the collections by their type
|
|
731
|
+
coll_dict = {}
|
|
732
|
+
for coll in data_collections:
|
|
733
|
+
try:
|
|
734
|
+
coll_dict[coll.header.metadata['type']].append(coll)
|
|
735
|
+
except KeyError:
|
|
736
|
+
coll_dict[coll.header.metadata['type']] = [coll]
|
|
737
|
+
all_match = [match_rooms_to_data(val, rooms, use_mult, space_based)
|
|
738
|
+
for val in coll_dict.values()]
|
|
739
|
+
matched_objs = [list(tup) for tup in all_match[0]]
|
|
740
|
+
for other_tups in all_match[1:]:
|
|
741
|
+
for i, tup in enumerate(other_tups):
|
|
742
|
+
matched_objs[i][1] += tup[1]
|
|
743
|
+
else:
|
|
744
|
+
matched_objs = match_rooms_to_data(
|
|
745
|
+
data_collections, rooms, use_mult, space_based)
|
|
746
|
+
assert len(matched_objs) != 0, 'None of the {} data collections could be ' \
|
|
747
|
+
'matched to the input rooms.'.format(data_type)
|
|
748
|
+
self._rooms = tuple(obj[0] for obj in matched_objs) if not use_all else rooms
|
|
749
|
+
base_data = matched_objs[0][1]
|
|
750
|
+
|
|
751
|
+
# check that the data if of the correct type.
|
|
752
|
+
if 'type' in base_data.header.metadata:
|
|
753
|
+
check_text = type_check_text if type_check_text is not None else data_type
|
|
754
|
+
assert check_text in base_data.header.metadata['type'], \
|
|
755
|
+
'Input data collections for {} do not seem to be of the correct type:' \
|
|
756
|
+
'\n{}'.format(data_type, base_data.header.metadata['type'])
|
|
757
|
+
|
|
758
|
+
# compute the total values of the load
|
|
759
|
+
values = [0 for val in range(len(base_data))]
|
|
760
|
+
for obj in matched_objs:
|
|
761
|
+
for i, val in enumerate(obj[1].values):
|
|
762
|
+
values[i] += val * obj[2]
|
|
763
|
+
if negate:
|
|
764
|
+
values = [-val for val in values]
|
|
765
|
+
|
|
766
|
+
# create the new totalled data collection
|
|
767
|
+
new_header = base_data.header.duplicate()
|
|
768
|
+
if 'Zone' in new_header.metadata:
|
|
769
|
+
del new_header.metadata['Zone']
|
|
770
|
+
elif 'System' in new_header.metadata:
|
|
771
|
+
del new_header.metadata['System']
|
|
772
|
+
new_header.metadata['type'] = data_type
|
|
773
|
+
if isinstance(base_data, HourlyContinuousCollection):
|
|
774
|
+
return HourlyContinuousCollection(new_header, values)
|
|
775
|
+
else: # it's one of the data collections that needs datetimes
|
|
776
|
+
return base_data.__class__(new_header, values, base_data.datetimes)
|
|
777
|
+
|
|
778
|
+
def _match_face_input(self, surface_flow_data, rooms):
|
|
779
|
+
"""Match a an array of input data collections to input rooms.
|
|
780
|
+
|
|
781
|
+
Args:
|
|
782
|
+
surface_flow_data: An array of input data collections for surface
|
|
783
|
+
energy flow.
|
|
784
|
+
rooms: An array of input honeybee Rooms.
|
|
785
|
+
"""
|
|
786
|
+
# match the data collections to the rooms
|
|
787
|
+
if surface_flow_data is None or len(surface_flow_data) == 0:
|
|
788
|
+
return [None] * 7
|
|
789
|
+
base_data = surface_flow_data[0]
|
|
790
|
+
values = [0] * len(base_data)
|
|
791
|
+
|
|
792
|
+
# compute the total values of the load
|
|
793
|
+
window_vals, wall_vals, roof_vals, floor_vals = (values[:] for i in range(4))
|
|
794
|
+
int_win_vals, int_wall_vals, int_floor_vals = (values[:] for i in range(3))
|
|
795
|
+
for room in rooms:
|
|
796
|
+
mult = room.multiplier
|
|
797
|
+
match_objs = match_faces_to_data(surface_flow_data, room.faces)
|
|
798
|
+
for obj in match_objs:
|
|
799
|
+
if isinstance(obj[0].boundary_condition, self.EXTERIOR_BCS):
|
|
800
|
+
if isinstance(obj[0], (Aperture, Door)):
|
|
801
|
+
for i, val in enumerate(obj[1].values):
|
|
802
|
+
window_vals[i] += val * mult
|
|
803
|
+
elif isinstance(obj[0].type, Wall):
|
|
804
|
+
for i, val in enumerate(obj[1].values):
|
|
805
|
+
wall_vals[i] += val * mult
|
|
806
|
+
elif isinstance(obj[0].type, RoofCeiling):
|
|
807
|
+
for i, val in enumerate(obj[1].values):
|
|
808
|
+
roof_vals[i] += val * mult
|
|
809
|
+
elif isinstance(obj[0].type, Floor):
|
|
810
|
+
for i, val in enumerate(obj[1].values):
|
|
811
|
+
floor_vals[i] += val * mult
|
|
812
|
+
elif isinstance(obj[0].boundary_condition, self.INTERIOR_BCS):
|
|
813
|
+
if isinstance(obj[0], (Aperture, Door)):
|
|
814
|
+
for i, val in enumerate(obj[1].values):
|
|
815
|
+
int_win_vals[i] += val * mult
|
|
816
|
+
elif isinstance(obj[0].type, (Wall, AirBoundary)):
|
|
817
|
+
for i, val in enumerate(obj[1].values):
|
|
818
|
+
int_wall_vals[i] += val * mult
|
|
819
|
+
elif isinstance(obj[0].type, (Floor, RoofCeiling)):
|
|
820
|
+
for i, val in enumerate(obj[1].values):
|
|
821
|
+
int_floor_vals[i] += val * mult
|
|
822
|
+
|
|
823
|
+
# create the new totalled data collection
|
|
824
|
+
new_header = base_data.header.duplicate()
|
|
825
|
+
if 'Surface' in new_header.metadata:
|
|
826
|
+
del new_header.metadata['Surface']
|
|
827
|
+
window_head, wall_head, roof_head, floor_head, \
|
|
828
|
+
i_win_head, i_wall_head, i_floor_head = \
|
|
829
|
+
(new_header.duplicate() for _ in range(7))
|
|
830
|
+
window_head.metadata['type'] = 'Window Flow'
|
|
831
|
+
wall_head.metadata['type'] = 'Wall Conduction'
|
|
832
|
+
roof_head.metadata['type'] = 'Roof Conduction'
|
|
833
|
+
floor_head.metadata['type'] = 'Floor Conduction'
|
|
834
|
+
i_win_head.metadata['type'] = 'Interior Window Storage'
|
|
835
|
+
i_wall_head.metadata['type'] = 'Interior Wall Storage'
|
|
836
|
+
i_floor_head.metadata['type'] = 'Interior Floor Storage'
|
|
837
|
+
all_headers = [window_head, wall_head, roof_head, floor_head] + \
|
|
838
|
+
[i_win_head, i_wall_head, i_floor_head]
|
|
839
|
+
all_values = [window_vals, wall_vals, roof_vals, floor_vals] + \
|
|
840
|
+
[int_win_vals, int_wall_vals, int_floor_vals]
|
|
841
|
+
all_data = []
|
|
842
|
+
for head, vals in zip(all_headers, all_values):
|
|
843
|
+
if isinstance(base_data, HourlyContinuousCollection):
|
|
844
|
+
all_data.append(HourlyContinuousCollection(head, vals))
|
|
845
|
+
else: # it's one of the data collections that needs datetimes
|
|
846
|
+
all_data.append(base_data.__class__(head, vals, base_data.datetimes))
|
|
847
|
+
return all_data
|
|
848
|
+
|
|
849
|
+
def _area_as_meters_feet(self, base_area):
|
|
850
|
+
"""Convert a base area to meters or feet depending on the the assigned units."""
|
|
851
|
+
if self.units in ('Meters', 'Feet'): # no need to do unit conversions
|
|
852
|
+
return base_area
|
|
853
|
+
elif self.units == 'Millimeters': # convert to meters
|
|
854
|
+
return base_area / 1000000.0
|
|
855
|
+
elif self.units == 'Inches': # convert to feet
|
|
856
|
+
return base_area / 144.0
|
|
857
|
+
else: # assume it's cm; convert to meters
|
|
858
|
+
return base_area / 10000.0
|
|
859
|
+
|
|
860
|
+
@staticmethod
|
|
861
|
+
def _normalize_collection(collection, area, is_ip):
|
|
862
|
+
"""Normalize a given data collection by floor area.
|
|
863
|
+
|
|
864
|
+
Args:
|
|
865
|
+
collection: A data collection to be normalized.
|
|
866
|
+
area: The floor area the the collection is normalize by.
|
|
867
|
+
is_ip: Boolean to note whether the area is in square meters or square feet.
|
|
868
|
+
"""
|
|
869
|
+
new_vals = [val / area for val in collection.values]
|
|
870
|
+
head = collection.header
|
|
871
|
+
new_unit = '{}/m2'.format(head.unit) if not is_ip else '{}/ft2'.format(head.unit)
|
|
872
|
+
new_header = Header(
|
|
873
|
+
EnergyIntensity(), new_unit, head.analysis_period, head.metadata)
|
|
874
|
+
if isinstance(collection, HourlyContinuousCollection):
|
|
875
|
+
return HourlyContinuousCollection(new_header, new_vals)
|
|
876
|
+
else: # it's one of the data collections that needs datetimes
|
|
877
|
+
return collection.__class__(new_header, new_vals, collection.datetimes)
|
|
878
|
+
|
|
879
|
+
@staticmethod
|
|
880
|
+
def _check_data_matching(rooms, data):
|
|
881
|
+
return None if data is None or len(match_rooms_to_data(data, rooms)) == 0 \
|
|
882
|
+
else data
|
|
883
|
+
|
|
884
|
+
def ToString(self):
|
|
885
|
+
"""Overwrite .NET ToString."""
|
|
886
|
+
return self.__repr__()
|
|
887
|
+
|
|
888
|
+
def __repr__(self):
|
|
889
|
+
"""Load Balance representation."""
|
|
890
|
+
return 'Load Balance: [{} Rooms]'.format(len(self.rooms))
|