honeybee-energy 1.116.106__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. honeybee_energy/__init__.py +24 -0
  2. honeybee_energy/__main__.py +4 -0
  3. honeybee_energy/_extend_honeybee.py +145 -0
  4. honeybee_energy/altnumber.py +21 -0
  5. honeybee_energy/baseline/__init__.py +2 -0
  6. honeybee_energy/baseline/create.py +608 -0
  7. honeybee_energy/baseline/data/__init__.py +1 -0
  8. honeybee_energy/baseline/data/constructions.csv +64 -0
  9. honeybee_energy/baseline/data/fen_ratios.csv +15 -0
  10. honeybee_energy/baseline/data/lpd_building.csv +21 -0
  11. honeybee_energy/baseline/data/pci_2016.csv +22 -0
  12. honeybee_energy/baseline/data/pci_2019.csv +22 -0
  13. honeybee_energy/baseline/data/pci_2022.csv +22 -0
  14. honeybee_energy/baseline/data/shw.csv +21 -0
  15. honeybee_energy/baseline/pci.py +512 -0
  16. honeybee_energy/baseline/result.py +371 -0
  17. honeybee_energy/boundarycondition.py +128 -0
  18. honeybee_energy/cli/__init__.py +69 -0
  19. honeybee_energy/cli/baseline.py +475 -0
  20. honeybee_energy/cli/edit.py +327 -0
  21. honeybee_energy/cli/lib.py +1154 -0
  22. honeybee_energy/cli/result.py +810 -0
  23. honeybee_energy/cli/setconfig.py +124 -0
  24. honeybee_energy/cli/settings.py +569 -0
  25. honeybee_energy/cli/simulate.py +380 -0
  26. honeybee_energy/cli/translate.py +1714 -0
  27. honeybee_energy/cli/validate.py +224 -0
  28. honeybee_energy/config.json +11 -0
  29. honeybee_energy/config.py +842 -0
  30. honeybee_energy/construction/__init__.py +1 -0
  31. honeybee_energy/construction/_base.py +374 -0
  32. honeybee_energy/construction/air.py +325 -0
  33. honeybee_energy/construction/dictutil.py +89 -0
  34. honeybee_energy/construction/dynamic.py +607 -0
  35. honeybee_energy/construction/opaque.py +460 -0
  36. honeybee_energy/construction/shade.py +319 -0
  37. honeybee_energy/construction/window.py +1096 -0
  38. honeybee_energy/construction/windowshade.py +847 -0
  39. honeybee_energy/constructionset.py +1655 -0
  40. honeybee_energy/dictutil.py +56 -0
  41. honeybee_energy/generator/__init__.py +5 -0
  42. honeybee_energy/generator/loadcenter.py +204 -0
  43. honeybee_energy/generator/pv.py +535 -0
  44. honeybee_energy/hvac/__init__.py +21 -0
  45. honeybee_energy/hvac/_base.py +124 -0
  46. honeybee_energy/hvac/_template.py +270 -0
  47. honeybee_energy/hvac/allair/__init__.py +22 -0
  48. honeybee_energy/hvac/allair/_base.py +349 -0
  49. honeybee_energy/hvac/allair/furnace.py +168 -0
  50. honeybee_energy/hvac/allair/psz.py +131 -0
  51. honeybee_energy/hvac/allair/ptac.py +163 -0
  52. honeybee_energy/hvac/allair/pvav.py +109 -0
  53. honeybee_energy/hvac/allair/vav.py +128 -0
  54. honeybee_energy/hvac/detailed.py +337 -0
  55. honeybee_energy/hvac/doas/__init__.py +28 -0
  56. honeybee_energy/hvac/doas/_base.py +345 -0
  57. honeybee_energy/hvac/doas/fcu.py +127 -0
  58. honeybee_energy/hvac/doas/radiant.py +329 -0
  59. honeybee_energy/hvac/doas/vrf.py +81 -0
  60. honeybee_energy/hvac/doas/wshp.py +91 -0
  61. honeybee_energy/hvac/heatcool/__init__.py +23 -0
  62. honeybee_energy/hvac/heatcool/_base.py +177 -0
  63. honeybee_energy/hvac/heatcool/baseboard.py +61 -0
  64. honeybee_energy/hvac/heatcool/evapcool.py +72 -0
  65. honeybee_energy/hvac/heatcool/fcu.py +92 -0
  66. honeybee_energy/hvac/heatcool/gasunit.py +53 -0
  67. honeybee_energy/hvac/heatcool/radiant.py +269 -0
  68. honeybee_energy/hvac/heatcool/residential.py +77 -0
  69. honeybee_energy/hvac/heatcool/vrf.py +54 -0
  70. honeybee_energy/hvac/heatcool/windowac.py +70 -0
  71. honeybee_energy/hvac/heatcool/wshp.py +62 -0
  72. honeybee_energy/hvac/idealair.py +699 -0
  73. honeybee_energy/internalmass.py +310 -0
  74. honeybee_energy/lib/__init__.py +1 -0
  75. honeybee_energy/lib/_loadconstructions.py +194 -0
  76. honeybee_energy/lib/_loadconstructionsets.py +117 -0
  77. honeybee_energy/lib/_loadmaterials.py +83 -0
  78. honeybee_energy/lib/_loadprogramtypes.py +125 -0
  79. honeybee_energy/lib/_loadschedules.py +87 -0
  80. honeybee_energy/lib/_loadtypelimits.py +64 -0
  81. honeybee_energy/lib/constructions.py +207 -0
  82. honeybee_energy/lib/constructionsets.py +95 -0
  83. honeybee_energy/lib/materials.py +67 -0
  84. honeybee_energy/lib/programtypes.py +125 -0
  85. honeybee_energy/lib/schedules.py +61 -0
  86. honeybee_energy/lib/scheduletypelimits.py +31 -0
  87. honeybee_energy/load/__init__.py +1 -0
  88. honeybee_energy/load/_base.py +190 -0
  89. honeybee_energy/load/daylight.py +397 -0
  90. honeybee_energy/load/dictutil.py +47 -0
  91. honeybee_energy/load/equipment.py +771 -0
  92. honeybee_energy/load/hotwater.py +543 -0
  93. honeybee_energy/load/infiltration.py +460 -0
  94. honeybee_energy/load/lighting.py +480 -0
  95. honeybee_energy/load/people.py +497 -0
  96. honeybee_energy/load/process.py +472 -0
  97. honeybee_energy/load/setpoint.py +816 -0
  98. honeybee_energy/load/ventilation.py +550 -0
  99. honeybee_energy/material/__init__.py +1 -0
  100. honeybee_energy/material/_base.py +166 -0
  101. honeybee_energy/material/dictutil.py +59 -0
  102. honeybee_energy/material/frame.py +367 -0
  103. honeybee_energy/material/gas.py +1087 -0
  104. honeybee_energy/material/glazing.py +854 -0
  105. honeybee_energy/material/opaque.py +1351 -0
  106. honeybee_energy/material/shade.py +1360 -0
  107. honeybee_energy/measure.py +472 -0
  108. honeybee_energy/programtype.py +723 -0
  109. honeybee_energy/properties/__init__.py +1 -0
  110. honeybee_energy/properties/aperture.py +333 -0
  111. honeybee_energy/properties/door.py +342 -0
  112. honeybee_energy/properties/extension.py +244 -0
  113. honeybee_energy/properties/face.py +274 -0
  114. honeybee_energy/properties/model.py +2640 -0
  115. honeybee_energy/properties/room.py +1747 -0
  116. honeybee_energy/properties/shade.py +314 -0
  117. honeybee_energy/properties/shademesh.py +262 -0
  118. honeybee_energy/reader.py +48 -0
  119. honeybee_energy/result/__init__.py +1 -0
  120. honeybee_energy/result/colorobj.py +648 -0
  121. honeybee_energy/result/emissions.py +290 -0
  122. honeybee_energy/result/err.py +101 -0
  123. honeybee_energy/result/eui.py +100 -0
  124. honeybee_energy/result/generation.py +160 -0
  125. honeybee_energy/result/loadbalance.py +890 -0
  126. honeybee_energy/result/match.py +202 -0
  127. honeybee_energy/result/osw.py +90 -0
  128. honeybee_energy/result/rdd.py +59 -0
  129. honeybee_energy/result/zsz.py +190 -0
  130. honeybee_energy/run.py +1577 -0
  131. honeybee_energy/schedule/__init__.py +1 -0
  132. honeybee_energy/schedule/day.py +626 -0
  133. honeybee_energy/schedule/dictutil.py +59 -0
  134. honeybee_energy/schedule/fixedinterval.py +1012 -0
  135. honeybee_energy/schedule/rule.py +619 -0
  136. honeybee_energy/schedule/ruleset.py +1867 -0
  137. honeybee_energy/schedule/typelimit.py +310 -0
  138. honeybee_energy/shw.py +315 -0
  139. honeybee_energy/simulation/__init__.py +1 -0
  140. honeybee_energy/simulation/control.py +214 -0
  141. honeybee_energy/simulation/daylightsaving.py +185 -0
  142. honeybee_energy/simulation/dictutil.py +51 -0
  143. honeybee_energy/simulation/output.py +646 -0
  144. honeybee_energy/simulation/parameter.py +606 -0
  145. honeybee_energy/simulation/runperiod.py +443 -0
  146. honeybee_energy/simulation/shadowcalculation.py +295 -0
  147. honeybee_energy/simulation/sizing.py +546 -0
  148. honeybee_energy/ventcool/__init__.py +5 -0
  149. honeybee_energy/ventcool/_crack_data.py +91 -0
  150. honeybee_energy/ventcool/afn.py +289 -0
  151. honeybee_energy/ventcool/control.py +269 -0
  152. honeybee_energy/ventcool/crack.py +126 -0
  153. honeybee_energy/ventcool/fan.py +493 -0
  154. honeybee_energy/ventcool/opening.py +365 -0
  155. honeybee_energy/ventcool/simulation.py +314 -0
  156. honeybee_energy/writer.py +1078 -0
  157. honeybee_energy-1.116.106.dist-info/METADATA +113 -0
  158. honeybee_energy-1.116.106.dist-info/RECORD +162 -0
  159. honeybee_energy-1.116.106.dist-info/WHEEL +5 -0
  160. honeybee_energy-1.116.106.dist-info/entry_points.txt +2 -0
  161. honeybee_energy-1.116.106.dist-info/licenses/LICENSE +661 -0
  162. honeybee_energy-1.116.106.dist-info/top_level.txt +1 -0
@@ -0,0 +1,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))