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,1096 @@
1
+ # coding=utf-8
2
+ """Window Construction."""
3
+ from __future__ import division
4
+
5
+ import re
6
+
7
+ from honeybee._lockable import lockable
8
+ from honeybee.typing import clean_rad_string
9
+
10
+ from ._base import _ConstructionBase
11
+ from ..material.dictutil import dict_to_material
12
+ from ..material._base import _EnergyMaterialWindowBase
13
+ from ..material.glazing import _EnergyWindowMaterialGlazingBase, \
14
+ EnergyWindowMaterialGlazing, EnergyWindowMaterialSimpleGlazSys
15
+ from ..material.gas import _EnergyWindowMaterialGasBase, EnergyWindowMaterialGas, \
16
+ EnergyWindowMaterialGasMixture, EnergyWindowMaterialGasCustom
17
+ from ..material.shade import EnergyWindowMaterialShade, EnergyWindowMaterialBlind
18
+ from ..material.frame import EnergyWindowFrame
19
+ from ..reader import parse_idf_string, clean_idf_file_contents
20
+ from ..properties.extension import WindowConstructionProperties
21
+
22
+
23
+ @lockable
24
+ class WindowConstruction(_ConstructionBase):
25
+ """Window energy construction.
26
+
27
+ Args:
28
+ identifier: Text string for a unique Construction ID. Must be < 100 characters
29
+ and not contain any EnergyPlus special characters. This will be used to
30
+ identify the object across a model and in the exported IDF.
31
+ materials: List of materials in the construction (from outside to inside).
32
+ The first and last layer must be a glazing layer. Adjacent glass layers
33
+ be separated by one and only one gas layer. When using a Simple Glazing
34
+ System material, it must be the only material.
35
+ frame: An optional window frame material to denote the frame that surrounds
36
+ the window construction. (Default: None).
37
+
38
+ Properties:
39
+ * identifier
40
+ * display_name
41
+ * materials
42
+ * layers
43
+ * unique_materials
44
+ * frame
45
+ * r_value
46
+ * u_value
47
+ * u_factor
48
+ * r_factor
49
+ * is_symmetric
50
+ * has_frame
51
+ * has_shade
52
+ * is_dynamic
53
+ * inside_emissivity
54
+ * outside_emissivity
55
+ * solar_transmittance
56
+ * solar_reflectance
57
+ * solar_absorptance
58
+ * visible_transmittance
59
+ * visible_reflectance
60
+ * visible_absorptance
61
+ * shgc
62
+ * thickness
63
+ * glazing_count
64
+ * gap_count
65
+ * glazing_materials
66
+ * inside_material
67
+ * outside_material
68
+ * user_data
69
+ * properties
70
+ """
71
+ __slots__ = ('_frame',)
72
+
73
+ COG_AREA = 0.76
74
+ EDGE_AREA = 0.24
75
+
76
+ def __init__(self, identifier, materials, frame=None):
77
+ """Initialize window construction."""
78
+ _ConstructionBase.__init__(self, identifier, materials)
79
+ self.frame = frame
80
+ self._properties = WindowConstructionProperties(self)
81
+
82
+ @classmethod
83
+ def from_simple_parameters(cls, identifier, u_factor, shgc, vt=0.6):
84
+ """Create a WindowConstruction from a specification of simple parameters.
85
+
86
+ The result will have a single EnergyWindowMaterialSimpleGlazSys layer that
87
+ is derived from the input parameters.
88
+
89
+ Args:
90
+ identifier: Text string for a unique construction ID.
91
+ Must be <90 characters and not contain any EnergyPlus special
92
+ characters. This will be used to identify the object across a model
93
+ and in the exported IDF.
94
+ u_factor: A number for the U-factor of the glazing system [W/m2-K]
95
+ including standard air gap resistances on either side of the system.
96
+ shgc: A number between 0 and 1 for the solar heat gain coefficient
97
+ of the glazing system. This includes both directly transmitted solar
98
+ heat as well as solar heat that is absorbed by the glazing system and
99
+ conducts towards the interior.
100
+ vt: A number between 0 and 1 for the visible transmittance of the
101
+ glazing system. Default: 0.6.
102
+ """
103
+ mat_layer = EnergyWindowMaterialSimpleGlazSys(
104
+ '{} Material'.format(identifier), u_factor, shgc, vt
105
+ )
106
+ return cls(identifier, (mat_layer,))
107
+
108
+ @property
109
+ def materials(self):
110
+ """Get or set the list of materials in the construction (outside to inside).
111
+
112
+ The following rules apply:
113
+
114
+ * The first and last layer must be a glazing layer.
115
+ * When using a Simple Glazing System material, it must be the only material.
116
+ * Adjacent glass layers must be separated by one and only one gas layer
117
+ * Shades/blinds are not allowed; WindowConstructionShade must be used
118
+ """
119
+ return self._materials
120
+
121
+ @materials.setter
122
+ def materials(self, mats):
123
+ try:
124
+ if not isinstance(mats, tuple):
125
+ mats = tuple(mats)
126
+ except TypeError:
127
+ raise TypeError('Expected list or tuple for construction materials. '
128
+ 'Got {}'.format(type(mats)))
129
+ assert len(mats) > 0, 'Construction must possess at least one material.'
130
+ assert len(mats) <= 8, 'Window construction cannot have more than 8 materials.'
131
+ assert not isinstance(mats[0], _EnergyWindowMaterialGasBase), \
132
+ 'Window construction cannot have gas gap layers on the outside layer.'
133
+ assert not isinstance(mats[-1], _EnergyWindowMaterialGasBase), \
134
+ 'Window construction cannot have gas gap layers on the inside layer.'
135
+ glazing_layer = False
136
+ for i, mat in enumerate(mats):
137
+ assert isinstance(mat, _EnergyMaterialWindowBase), 'Expected window energy' \
138
+ ' material for construction. Got {}.'.format(type(mat))
139
+ if isinstance(mat, EnergyWindowMaterialSimpleGlazSys):
140
+ assert len(mats) == 1, 'Only one material layer is allowed when using' \
141
+ ' EnergyWindowMaterialSimpleGlazSys'
142
+ elif isinstance(mat, _EnergyWindowMaterialGasBase):
143
+ assert glazing_layer, 'Gas layer must be adjacent to a glazing layer.'
144
+ glazing_layer = False
145
+ elif isinstance(mat, _EnergyWindowMaterialGlazingBase):
146
+ assert not glazing_layer, 'Two adjacent glazing layers are not allowed.'
147
+ glazing_layer = True
148
+ else: # it's a shade material
149
+ raise ValueError(
150
+ 'Shades and blinds are not permitted within WindowConstruction.\n'
151
+ 'Use the WindowConstructionShade to add shades and blinds.')
152
+ self._materials = mats
153
+
154
+ @property
155
+ def frame(self):
156
+ """Get or set a window frame for the frame material surrounding the construction.
157
+ """
158
+ return self._frame
159
+
160
+ @frame.setter
161
+ def frame(self, value):
162
+ if value is not None:
163
+ assert isinstance(value, EnergyWindowFrame), 'Expected EnergyWindowFrame ' \
164
+ 'for WindowConstruction frame. Got {}.'.format(type(value))
165
+ self._frame = value
166
+
167
+ @property
168
+ def r_factor(self):
169
+ """Construction R-factor [m2-K/W] (including standard resistances for air films).
170
+
171
+ Formulas for film coefficients come from EN673 / ISO10292.
172
+ """
173
+ # figure out the center-of-glass R-value
174
+ gap_count = self.gap_count
175
+ if gap_count == 0: # single pane or simple glazing system
176
+ cog_r = self.materials[0].r_value + (1 / self.out_h_simple()) + \
177
+ (1 / self.in_h_simple())
178
+ else:
179
+ r_vals, emissivities = self._layered_r_value_initial(gap_count)
180
+ r_vals = self._solve_r_values(r_vals, emissivities)
181
+ cog_r = sum(r_vals)
182
+ if self.frame is None:
183
+ return cog_r
184
+ # if there is a frame, account for it in the final R-value
185
+ glass_u = (1 / cog_r)
186
+ if self.frame.edge_to_center_ratio != 1 and not \
187
+ isinstance(self.materials[0], EnergyWindowMaterialSimpleGlazSys):
188
+ edge_u = self.frame.edge_to_center_ratio * glass_u
189
+ glass_u = (glass_u * self.COG_AREA) + (edge_u * self.EDGE_AREA)
190
+ frame_r = self.frame.r_value + (1 / self.out_h_simple()) + \
191
+ (1 / self.in_h_simple())
192
+ frame_u = 1 / frame_r
193
+ frame_area = ((1 + self.frame.width) ** 2) - 1
194
+ total_u = (glass_u + (frame_u * frame_area)) / (1 + frame_area)
195
+ return 1 / total_u
196
+
197
+ @property
198
+ def r_value(self):
199
+ """R-value of the construction [m2-K/W] (excluding air films).
200
+
201
+ Note that shade materials are currently considered impermeable to air within
202
+ the U-value calculation.
203
+ """
204
+ # figure out the center-of-glass R-value
205
+ gap_count = self.gap_count
206
+ if gap_count == 0: # single pane or simple glazing system
207
+ cog_r = self.materials[0].r_value
208
+ else:
209
+ r_vals, emissivities = self._layered_r_value_initial(gap_count)
210
+ r_vals = self._solve_r_values(r_vals, emissivities)
211
+ cog_r = sum(r_vals[1:-1])
212
+ if self.frame is None:
213
+ return cog_r
214
+ # if there is a frame, account for it in the final R-value
215
+ glass_u = (1 / cog_r)
216
+ if self.frame.edge_to_center_ratio != 1 and not \
217
+ isinstance(self.materials[0], EnergyWindowMaterialSimpleGlazSys):
218
+ edge_u = self.frame.edge_to_center_ratio * glass_u
219
+ glass_u = (glass_u * self.COG_AREA) + (edge_u * self.EDGE_AREA)
220
+ frame_area = ((1 + self.frame.width) ** 2) - 1
221
+ total_u = (glass_u + (self.frame.u_value * frame_area)) / (1 + frame_area)
222
+ return 1 / total_u
223
+
224
+ @property
225
+ def inside_emissivity(self):
226
+ """"The emissivity of the inside face of the construction."""
227
+ if isinstance(self.materials[0], EnergyWindowMaterialSimpleGlazSys):
228
+ return 0.84
229
+ try:
230
+ return self.materials[-1].emissivity_back
231
+ except AttributeError:
232
+ return self.materials[-1].emissivity
233
+
234
+ @property
235
+ def outside_emissivity(self):
236
+ """"The emissivity of the outside face of the construction."""
237
+ if isinstance(self.materials[0], EnergyWindowMaterialSimpleGlazSys):
238
+ return 0.84
239
+ return self.materials[0].emissivity
240
+
241
+ @property
242
+ def solar_transmittance(self):
243
+ """The solar transmittance of the window at normal incidence."""
244
+ return self.solar_optical_properties()[0]
245
+
246
+ @property
247
+ def solar_reflectance(self):
248
+ """The solar reflectance of the window at normal incidence (to the exterior).
249
+ """
250
+ return self.solar_optical_properties()[1]
251
+
252
+ @property
253
+ def solar_absorptance(self):
254
+ """Get the combined solar absorptance of all window panes at normal incidence.
255
+ """
256
+ return sum(self.solar_optical_properties()[2])
257
+
258
+ @property
259
+ def visible_transmittance(self):
260
+ """The visible transmittance of the window at normal incidence."""
261
+ return self.visible_optical_properties()[0]
262
+
263
+ @property
264
+ def visible_reflectance(self):
265
+ """The visible reflectance of the window at normal incidence (to the exterior).
266
+ """
267
+ return self.visible_optical_properties()[1]
268
+
269
+ @property
270
+ def visible_absorptance(self):
271
+ """Get the combined visible absorptance of all window panes at normal incidence.
272
+ """
273
+ return sum(self.visible_optical_properties()[2])
274
+
275
+ @property
276
+ def shgc(self):
277
+ """Get the solar heat gain coefficient (SHGC) of the construction.
278
+
279
+ If this construction is not a simple glazing system, this value is computed
280
+ by summing the transmitted and conducted portions of solar irradiance under
281
+ the NFRC summer conditions of 32C outdoor temperature, 24C indoor temperature,
282
+ and 783 W/m2 of incident solar flux.
283
+ """
284
+ if isinstance(self.materials[0], EnergyWindowMaterialSimpleGlazSys):
285
+ if self.frame is None:
286
+ return self.materials[0].shgc
287
+ t_out, t_in, sol_irr = 32, 24, 783 # NFRC 2010 summer conditions
288
+ _, r_values = self.temperature_profile(t_out, t_in, solar_irradiance=sol_irr)
289
+ heat_gen, transmitted = self._heat_gen_from_solar(sol_irr)
290
+ conducted = 0
291
+ r_factor = sum(r_values)
292
+ for i, heat_g in enumerate(heat_gen):
293
+ if heat_g != 0:
294
+ conducted += heat_g * (1 - (sum(r_values[i + 1:]) / r_factor))
295
+ if self.frame is None:
296
+ return (transmitted + conducted) / sol_irr
297
+ else: # account for the frame conduction
298
+ _, r_values = self.temperature_profile_frame(
299
+ t_out, t_in, solar_irradiance=sol_irr)
300
+ heat_gen = [0, sol_irr * self.frame.solar_absorptance, 0]
301
+ frame_conducted = 0
302
+ r_factor = sum(r_values)
303
+ for i, heat_g in enumerate(heat_gen):
304
+ if heat_g != 0:
305
+ frame_conducted += heat_g * (1 - (sum(r_values[i + 1:]) / r_factor))
306
+ frame_area = ((1 + self.frame.width) ** 2) - 1
307
+ frame_conduct = frame_conducted * frame_area
308
+ frame_sol_irr = sol_irr * frame_area
309
+ return (transmitted + conducted + frame_conduct) / (sol_irr + frame_sol_irr)
310
+
311
+ @property
312
+ def thickness(self):
313
+ """Thickness of the construction [m]."""
314
+ thickness = 0
315
+ for mat in self.materials:
316
+ thickness += mat.thickness
317
+ return thickness
318
+
319
+ @property
320
+ def inside_solar_reflectance(self):
321
+ """The solar reflectance of the inside face of the construction."""
322
+ return self.materials[-1].solar_reflectance_back
323
+
324
+ @property
325
+ def inside_visible_reflectance(self):
326
+ """The visible reflectance of the inside face of the construction."""
327
+ return self.materials[-1].visible_reflectance_back
328
+
329
+ @property
330
+ def outside_solar_reflectance(self):
331
+ """The solar reflectance of the outside face of the construction."""
332
+ return self.materials[0].solar_reflectance
333
+
334
+ @property
335
+ def outside_visible_reflectance(self):
336
+ """The visible reflectance of the outside face of the construction."""
337
+ return self.materials[0].visible_reflectance
338
+
339
+ @property
340
+ def has_frame(self):
341
+ """Get a boolean noting whether the construction has a frame assigned to it."""
342
+ return self.frame is not None
343
+
344
+ @property
345
+ def glazing_count(self):
346
+ """The number of glazing materials contained within the window construction.
347
+ """
348
+ count = 0
349
+ for mat in self.materials:
350
+ if isinstance(mat, _EnergyWindowMaterialGlazingBase):
351
+ count += 1
352
+ return count
353
+
354
+ @property
355
+ def gap_count(self):
356
+ """The number of gas gaps contained within the window construction."""
357
+ count = 0
358
+ for mat in self.materials:
359
+ if isinstance(mat, _EnergyWindowMaterialGasBase):
360
+ count += 1
361
+ return count
362
+
363
+ @property
364
+ def glazing_materials(self):
365
+ """The only the glazing materials contained within the window construction.
366
+ """
367
+ return [mat for mat in self.materials
368
+ if isinstance(mat, _EnergyWindowMaterialGlazingBase)]
369
+
370
+ @property
371
+ def inside_material(self):
372
+ """The the inside material layer of the construction.
373
+
374
+ Useful for checking that an asymmetric construction is correctly assigned.
375
+ """
376
+ return self.materials[-1]
377
+
378
+ @property
379
+ def outside_material(self):
380
+ """The the outside material layer of the construction.
381
+
382
+ Useful for checking that an asymmetric construction is correctly assigned.
383
+ """
384
+ return self.materials[0]
385
+
386
+ def solar_optical_properties(self):
387
+ """Get solar transmittance + reflectance, and absorptances for each glass pane.
388
+
389
+ Returns:
390
+ A tuple of three values.
391
+
392
+ - transmittance: A transmittance value through all glass panes.
393
+
394
+ - reflectance: A reflectance value from the front of all glass panes.
395
+
396
+ - absorptances: A list of absorptance values through each pane
397
+ of glass. The values in this list correspond to the glass panes
398
+ in the construction.
399
+ """
400
+ # first check whether the construction is a simple glazing system
401
+ if isinstance(self.materials[0], EnergyWindowMaterialSimpleGlazSys):
402
+ transmittance = self.materials[0].solar_transmittance
403
+ reflectance = self.materials[0].solar_reflectance
404
+ absorptances = [1 - transmittance - reflectance]
405
+ return transmittance, reflectance, absorptances
406
+
407
+ # if it's multi-layered, then compute the optical properties across each gap
408
+ glz_mats = self.glazing_materials
409
+ forward_ref, backward_ref, backward_abs = \
410
+ self._solar_optical_properties_by_gap(glz_mats)
411
+ # set initial properties based on the first pane
412
+ trans = glz_mats[0].solar_transmittance # will decrease with each pane
413
+ reflect = glz_mats[0].solar_reflectance # will increase with each pane
414
+ absorb = [1 - trans - reflect] # will increase and get new value with each pane
415
+ # loop through the panes of glass and update the initial properties
416
+ for i in range(len(glz_mats) - 1):
417
+ # get the two panes across the gap and their optical properties
418
+ mat = glz_mats[i + 1]
419
+ fw_ref, back_ref, back_abs = forward_ref[i], backward_ref[i], backward_abs[i]
420
+ # get the incident solar on the pane, including back reflection
421
+ incident = (trans + trans * fw_ref)
422
+ absorb[i] += back_abs * trans
423
+ back_out = back_ref * trans
424
+ for p in range(i, 0, -1):
425
+ prev_mat, p_prev_mat = glz_mats[p], glz_mats[p - 1]
426
+ prev_incident = back_out * p_prev_mat.solar_reflectance_back
427
+ incident += prev_incident * prev_mat.solar_transmittance
428
+ absorb[p] += prev_incident * prev_mat.solar_absorptance
429
+ absorb[p - 1] += back_out * p_prev_mat.solar_absorptance
430
+ back_out = back_out * p_prev_mat.solar_transmittance
431
+ reflect += back_out
432
+ # pass the incident solar through the glass pane
433
+ absorb.append(incident * mat.solar_absorptance)
434
+ trans = incident * mat.solar_transmittance
435
+ return trans, reflect, absorb
436
+
437
+ def visible_optical_properties(self):
438
+ """Get visible transmittance + reflectance, and absorptances for each glass pane.
439
+
440
+ Returns:
441
+ A tuple of three values.
442
+
443
+ - transmittance: A transmittance value through all glass panes.
444
+
445
+ - reflectance: A reflectance value from the front of all glass panes.
446
+
447
+ - absorptances: A list of absorptance values through each pane
448
+ of glass. The values in this list correspond to the glass panes
449
+ in the construction.
450
+ """
451
+ # first check whether the construction is a simple glazing system
452
+ if isinstance(self.materials[0], EnergyWindowMaterialSimpleGlazSys):
453
+ transmittance = self.materials[0].vt
454
+ reflectance = self.materials[0].visible_reflectance
455
+ absorptances = [1 - transmittance - reflectance]
456
+ return transmittance, reflectance, absorptances
457
+
458
+ # if it's multi-layered, then compute the optical properties across each gap
459
+ glz_mats = self.glazing_materials
460
+ forward_ref, backward_ref, backward_abs = \
461
+ self._visible_optical_properties_by_gap(glz_mats)
462
+ # set initial properties based on the first pane
463
+ trans = glz_mats[0].visible_transmittance # will decrease with each pane
464
+ reflect = glz_mats[0].visible_reflectance # will increase with each pane
465
+ absorb = [1 - trans - reflect] # will increase and get new value with each pane
466
+ # loop through the panes of glass and update the initial properties
467
+ for i in range(len(glz_mats) - 1):
468
+ # get the two panes across the gap and their optical properties
469
+ mat = glz_mats[i + 1]
470
+ fw_ref, back_ref, back_abs = forward_ref[i], backward_ref[i], backward_abs[i]
471
+ # get the incident visible on the pane, including back reflection
472
+ incident = (trans + trans * fw_ref)
473
+ absorb[i] += back_abs * trans
474
+ back_out = back_ref * trans
475
+ for p in range(i, 0, -1):
476
+ prev_mat, p_prev_mat = glz_mats[p], glz_mats[p - 1]
477
+ prev_incident = back_out * p_prev_mat.visible_reflectance_back
478
+ incident += prev_incident * prev_mat.visible_transmittance
479
+ absorb[p] += prev_incident * prev_mat.visible_absorptance
480
+ absorb[p - 1] += back_out * p_prev_mat.visible_absorptance
481
+ back_out = back_out * p_prev_mat.visible_transmittance
482
+ reflect += back_out
483
+ # pass the incident visible through the glass pane
484
+ absorb.append(incident * mat.visible_absorptance)
485
+ trans = incident * mat.visible_transmittance
486
+ return trans, reflect, absorb
487
+
488
+ def temperature_profile(
489
+ self, outside_temperature=-18, inside_temperature=21,
490
+ wind_speed=6.7, solar_irradiance=0,
491
+ height=1.0, angle=90.0, pressure=101325
492
+ ):
493
+ """Get a list of temperatures at each material boundary across the construction.
494
+
495
+ Args:
496
+ outside_temperature: The temperature on the outside of the
497
+ construction [C]. (Default: -18, consistent with NFRC 100-2010).
498
+ inside_temperature: The temperature on the inside of the
499
+ construction [C]. (Default: 21, consistent with NFRC 100-2010).
500
+ wind_speed: The average outdoor wind speed [m/s]. This affects outdoor
501
+ convective heat transfer coefficient. (Default: 6.7 m/s).
502
+ solar_irradiance: An optional value for solar irradiance that is incident
503
+ on the front (exterior) of the construction [W/m2]. (Default: 0 W/m2).
504
+ height: An optional height for the surface in meters. (Default: 1.0 m).
505
+ angle: An angle in degrees between 0 and 180.
506
+
507
+ * 0 = A horizontal surface with the outside boundary on the top.
508
+ * 90 = A vertical surface
509
+ * 180 = A horizontal surface with the outside boundary on the bottom.
510
+
511
+ pressure: The average pressure of in Pa. (Default: 101325 Pa for
512
+ standard pressure at sea level).
513
+
514
+ Returns:
515
+ A tuple with two elements
516
+
517
+ - temperatures: A list of temperature values [C].
518
+ The first value will always be the outside temperature and the
519
+ second will be the exterior surface temperature.
520
+ The last value will always be the inside temperature and the second
521
+ to last will be the interior surface temperature.
522
+
523
+ - r_values: A list of R-values for each of the material layers [m2-K/W].
524
+ The first value will always be the resistance of the exterior air
525
+ and the last value is the resistance of the interior air.
526
+ The sum of this list is the R-factor for this construction given
527
+ the input parameters.
528
+ """
529
+ # reverse the angle if the outside temperature is greater than the inside one
530
+ if angle != 90 and outside_temperature > inside_temperature:
531
+ angle = abs(180 - angle)
532
+ gap_count = self.gap_count
533
+
534
+ # compute delta temperature from solar irradiance if applicable
535
+ heat_gen = None
536
+ if solar_irradiance != 0:
537
+ heat_gen, _ = self._heat_gen_from_solar(solar_irradiance)
538
+
539
+ # single pane or simple glazing system
540
+ if gap_count == 0:
541
+ in_r_init = 1 / self.in_h_simple()
542
+ r_values = [1 / self.out_h(wind_speed, outside_temperature + 273.15),
543
+ self.materials[0].r_value, in_r_init]
544
+ in_delta_t = (in_r_init / sum(r_values)) * \
545
+ (outside_temperature - inside_temperature)
546
+ r_values[-1] = 1 / self.in_h(inside_temperature - (in_delta_t / 2) + 273.15,
547
+ in_delta_t, height, angle, pressure)
548
+ temperatures = self._temperature_profile_from_r_values(
549
+ r_values, outside_temperature, inside_temperature, heat_gen)
550
+ return temperatures, r_values
551
+
552
+ # multi-layered window construction
553
+ guess = abs(inside_temperature - outside_temperature) / 2
554
+ guess = 1 if guess < 1 else guess # prevents zero division with gas conductance
555
+ avg_guess = ((inside_temperature + outside_temperature) / 2) + 273.15
556
+ r_values, emissivities = self._layered_r_value_initial(
557
+ gap_count, guess, avg_guess, wind_speed)
558
+ r_last = 0
559
+ r_next = sum(r_values)
560
+ while abs(r_next - r_last) > 0.001: # 0.001 is the r-value tolerance
561
+ r_last = sum(r_values)
562
+ temperatures = self._temperature_profile_from_r_values(
563
+ r_values, outside_temperature, inside_temperature, heat_gen)
564
+ r_values = self._layered_r_value(
565
+ temperatures, r_values, emissivities, height, angle, pressure)
566
+ r_next = sum(r_values)
567
+ temperatures = self._temperature_profile_from_r_values(
568
+ r_values, outside_temperature, inside_temperature, heat_gen)
569
+ return temperatures, r_values
570
+
571
+ def temperature_profile_frame(
572
+ self, outside_temperature=-18, inside_temperature=21,
573
+ outside_wind_speed=6.7, solar_irradiance=0,
574
+ height=1.0, angle=90.0, pressure=101325
575
+ ):
576
+ """Get a list of temperatures across the frame of the construction.
577
+
578
+ Note that this method will return None if no frame is assigned to
579
+ the construction.
580
+
581
+ Args:
582
+ outside_temperature: The temperature on the outside of the
583
+ construction [C]. (Default: -18, consistent with NFRC 100-2010).
584
+ inside_temperature: The temperature on the inside of the
585
+ construction [C]. (Default: 21, consistent with NFRC 100-2010).
586
+ wind_speed: The average outdoor wind speed [m/s]. This affects outdoor
587
+ convective heat transfer coefficient. (Default: 6.7 m/s).
588
+ solar_irradiance: An optional value for solar irradiance that is incident
589
+ on the front (exterior) of the construction [W/m2]. (Default: 0 W/m2).
590
+ height: An optional height for the surface in meters. (Default: 1.0 m).
591
+ angle: An angle in degrees between 0 and 180.
592
+
593
+ * 0 = A horizontal surface with the outside boundary on the bottom.
594
+ * 90 = A vertical surface
595
+ * 180 = A horizontal surface with the outside boundary on the top.
596
+
597
+ pressure: The average pressure of in Pa. (Default: 101325 Pa for
598
+ standard pressure at sea level).
599
+
600
+ Returns:
601
+ A tuple with two elements
602
+
603
+ - temperatures: A list of temperature values [C].
604
+ The first value will always be the outside temperature and the
605
+ second will be the exterior surface temperature.
606
+ The last value will always be the inside temperature and the second
607
+ to last will be the interior surface temperature.
608
+
609
+ - r_values: A list of R-values for each of the material layers [m2-K/W].
610
+ The first value will always be the resistance of the exterior air
611
+ and the last value is the resistance of the interior air.
612
+ The sum of this list is the R-factor for this construction given
613
+ the input parameters.
614
+ """
615
+ # first check to e sure that the construction has a frame
616
+ if self.frame is None:
617
+ return None
618
+
619
+ # reverse the angle if the outside temperature is greater than the inside one
620
+ if angle != 90 and outside_temperature > inside_temperature:
621
+ angle = abs(180 - angle)
622
+
623
+ # compute delta temperature from solar irradiance if applicable
624
+ heat_gen = None
625
+ if solar_irradiance != 0:
626
+ heat_gen = self.frame.solar_absorptance * solar_irradiance
627
+
628
+ # use the r-values to get the temperature profile
629
+ in_r_init = 1 / self.in_h_simple()
630
+ r_values = [1 / self.out_h(outside_wind_speed, outside_temperature + 273.15),
631
+ self.frame.r_value, in_r_init]
632
+ in_delta_t = (in_r_init / sum(r_values)) * \
633
+ (outside_temperature - inside_temperature)
634
+ r_values[-1] = 1 / self.in_h(inside_temperature - (in_delta_t / 2) + 273.15,
635
+ in_delta_t, height, angle, pressure)
636
+ temperatures = self._temperature_profile_from_r_values(
637
+ r_values, outside_temperature, inside_temperature, heat_gen)
638
+ return temperatures, r_values
639
+
640
+ @classmethod
641
+ def from_idf(cls, idf_string, ep_mat_strings):
642
+ """Create an WindowConstruction from an EnergyPlus text string.
643
+
644
+ Args:
645
+ idf_string: A text string fully describing an EnergyPlus construction.
646
+ ep_mat_strings: A list of text strings for each of the materials in
647
+ the construction.
648
+ """
649
+ materials_dict = cls._idf_materials_dictionary(ep_mat_strings)
650
+ ep_strs = parse_idf_string(idf_string)
651
+ try:
652
+ materials = [materials_dict[mat.upper()] for mat in ep_strs[1:]]
653
+ except KeyError as e:
654
+ raise ValueError('Failed to find {} in the input ep_mat_strings.'.format(e))
655
+ return cls(ep_strs[0], materials)
656
+
657
+ @classmethod
658
+ def from_dict(cls, data):
659
+ """Create a WindowConstruction from a dictionary.
660
+
661
+ Note that the dictionary must be a non-abridged version for this
662
+ classmethod to work.
663
+
664
+ Args:
665
+ data: A python dictionary in the following format
666
+
667
+ .. code-block:: python
668
+
669
+ {
670
+ "type": 'WindowConstruction',
671
+ "identifier": 'Generic Double Pane U-250 SHGC-035',
672
+ "display_name": 'Double Pane Window',
673
+ "materials": [], # list of material objects (from outside to inside)
674
+ "frame": {} # an optional frame object for the construction
675
+ }
676
+ """
677
+ assert data['type'] == 'WindowConstruction', \
678
+ 'Expected WindowConstruction. Got {}.'.format(data['type'])
679
+ mat_layers = cls._old_schema_materials(data) if 'layers' in data else \
680
+ [dict_to_material(mat) for mat in data['materials']]
681
+ new_obj = cls(data['identifier'], mat_layers)
682
+ if 'frame' in data and data['frame'] is not None:
683
+ new_obj.frame = EnergyWindowFrame.from_dict(data['frame'])
684
+ if 'display_name' in data and data['display_name'] is not None:
685
+ new_obj.display_name = data['display_name']
686
+ if 'user_data' in data and data['user_data'] is not None:
687
+ new_obj.user_data = data['user_data']
688
+ if 'properties' in data and data['properties'] is not None:
689
+ new_obj.properties._load_extension_attr_from_dict(data['properties'])
690
+ return new_obj
691
+
692
+ @classmethod
693
+ def from_dict_abridged(cls, data, materials):
694
+ """Create a WindowConstruction from an abridged dictionary.
695
+
696
+ Args:
697
+ data: An WindowConstructionAbridged dictionary with the format below.
698
+ materials: A dictionary with identifiers of materials as keys and
699
+ Python material objects as values.
700
+
701
+ .. code-block:: python
702
+
703
+ {
704
+ "type": 'WindowConstructionAbridged',
705
+ "identifier": 'Generic Double Pane U-250 SHGC-035',
706
+ "display_name": 'Double Pane Window',
707
+ "materials": [], # list of material identifiers (from outside to inside)
708
+ "frame": 'AL with thermal break' # identifier of frame material
709
+ }
710
+ """
711
+ assert data['type'] == 'WindowConstructionAbridged', \
712
+ 'Expected WindowConstructionAbridged. Got {}.'.format(data['type'])
713
+ mat_key = 'layers' if 'layers' in data else 'materials'
714
+ try:
715
+ mat_layers = [materials[mat_id] for mat_id in data[mat_key]]
716
+ except KeyError as e:
717
+ raise ValueError('Failed to find {} in materials.'.format(e))
718
+ new_obj = cls(data['identifier'], mat_layers)
719
+ if 'frame' in data and data['frame'] is not None:
720
+ new_obj.frame = materials[data['frame']]
721
+ if 'display_name' in data and data['display_name'] is not None:
722
+ new_obj.display_name = data['display_name']
723
+ if 'user_data' in data and data['user_data'] is not None:
724
+ new_obj.user_data = data['user_data']
725
+ if 'properties' in data and data['properties'] is not None:
726
+ new_obj.properties._load_extension_attr_from_dict(data['properties'])
727
+ return new_obj
728
+
729
+ def to_idf(self):
730
+ """IDF string representation of construction object.
731
+
732
+ Note that this method only outputs a single string for the construction and,
733
+ to write the full construction into an IDF, the construction's unique_materials
734
+ must also be written. If the construction has a frame, the frame definition
735
+ must also be written.
736
+
737
+ Returns:
738
+ construction_idf -- Text string representation of the construction.
739
+
740
+ .. code-block::
741
+
742
+ Construction,
743
+ Generic Double Pane, !- name
744
+ Generic Low-e Glass, !- layer 1
745
+ Generic Window Air Gap, !- layer 2
746
+ Generic Clear Glass; !- layer 3
747
+ """
748
+ return self._generate_idf_string('window', self.identifier, self.materials)
749
+
750
+ def to_radiance_solar(self):
751
+ """Honeybee Radiance modifier with the solar transmittance."""
752
+ try:
753
+ from honeybee_radiance.modifier.material import Glass
754
+ from honeybee_radiance.modifier.material import Trans
755
+ except ImportError as e:
756
+ raise ImportError('honeybee_radiance library must be installed to use '
757
+ 'to_radiance_solar() method. {}'.format(e))
758
+ diffusing = False
759
+ for mat in self.materials:
760
+ if isinstance(mat, EnergyWindowMaterialGlazing) and mat.solar_diffusing:
761
+ diffusing = True
762
+ if not diffusing:
763
+ tm_sivity = Glass.transmissivity_from_transmittance(self.solar_transmittance)
764
+ tm_sivity = 1 if tm_sivity > 1 else tm_sivity
765
+ return Glass.from_single_transmissivity(
766
+ clean_rad_string(self.identifier), tm_sivity)
767
+ else:
768
+ _, ref, absorb = self.solar_optical_properties()
769
+ rgb_ref = 1 - (sum(absorb) / (1 - ref))
770
+ return Trans.from_single_reflectance(
771
+ clean_rad_string(self.identifier), rgb_reflectance=rgb_ref,
772
+ specularity=ref, transmitted_diff=1, transmitted_spec=0)
773
+
774
+ def to_radiance_visible(self):
775
+ """Honeybee Radiance modifier with the visible transmittance."""
776
+ try:
777
+ from honeybee_radiance.modifier.material import Glass
778
+ from honeybee_radiance.modifier.material import Trans
779
+ except ImportError as e:
780
+ raise ImportError('honeybee_radiance library must be installed to use '
781
+ 'to_radiance_visible() method. {}'.format(e))
782
+ diffusing = False
783
+ for mat in self.materials:
784
+ if isinstance(mat, EnergyWindowMaterialGlazing) and mat.solar_diffusing:
785
+ diffusing = True
786
+ if not diffusing:
787
+ return Glass.from_single_transmittance(
788
+ clean_rad_string(self.identifier), self.visible_transmittance)
789
+ else:
790
+ _, ref, absorb = self.visible_optical_properties()
791
+ rgb_ref = 1 - (sum(absorb) / (1 - ref))
792
+ return Trans.from_single_reflectance(
793
+ clean_rad_string(self.identifier), rgb_reflectance=rgb_ref,
794
+ specularity=ref, transmitted_diff=1, transmitted_spec=0)
795
+
796
+ def to_dict(self, abridged=False):
797
+ """Window construction dictionary representation.
798
+
799
+ Args:
800
+ abridged: Boolean to note whether the full dictionary describing the
801
+ object should be returned (False) or just an abridged version (True),
802
+ which only specifies the identifiers of material layers. Default: False.
803
+ """
804
+ base = {'type': 'WindowConstruction'} if not \
805
+ abridged else {'type': 'WindowConstructionAbridged'}
806
+ base['identifier'] = self.identifier
807
+ base['materials'] = self.layers if abridged else \
808
+ [m.to_dict() for m in self.materials]
809
+ if self.frame is not None:
810
+ base['frame'] = self.frame.identifier if abridged else self.frame.to_dict()
811
+ if self._display_name is not None:
812
+ base['display_name'] = self.display_name
813
+ if self._user_data is not None:
814
+ base['user_data'] = self.user_data
815
+ prop_dict = self.properties.to_dict()
816
+ if prop_dict is not None:
817
+ base['properties'] = prop_dict
818
+ return base
819
+
820
+ def to_simple_construction(self):
821
+ """Get a version of this construction that uses a SimpleGlazSys material.
822
+
823
+ This is useful when translating to gbXML and other formats that do not
824
+ support layered window constructions.
825
+ """
826
+ if isinstance(self.materials[0], EnergyWindowMaterialSimpleGlazSys):
827
+ return self
828
+ simple_mat = EnergyWindowMaterialSimpleGlazSys(
829
+ '{}_SimpleGlazSys'.format(self.identifier),
830
+ self.u_factor, self.shgc, self.visible_transmittance
831
+ )
832
+ new_con = WindowConstruction(self.identifier, [simple_mat])
833
+ if self._display_name is not None:
834
+ new_con._display_name = self._display_name
835
+ return new_con
836
+
837
+ @staticmethod
838
+ def extract_all_from_idf_file(idf_file):
839
+ """Get all WindowConstruction objects in an EnergyPlus IDF file.
840
+
841
+ Args:
842
+ idf_file: A path to an IDF file containing objects for window
843
+ constructions and corresponding materials. For example, the
844
+ IDF Report output by LBNL WINDOW.
845
+
846
+ Returns:
847
+ A tuple with two elements
848
+
849
+ - constructions: A list of all WindowConstruction objects in the IDF
850
+ file as honeybee_energy WindowConstruction objects.
851
+
852
+ - materials: A list of all window materials in the IDF file as
853
+ honeybee_energy Material objects.
854
+ """
855
+ # read the file and remove lines of comments
856
+ file_contents = clean_idf_file_contents(idf_file)
857
+ # extract all material objects
858
+ mat_pattern = re.compile(r"(?i)(WindowMaterial:[\s\S]*?;)")
859
+ material_str = mat_pattern.findall(file_contents)
860
+ materials_dict = WindowConstruction._idf_materials_dictionary(material_str)
861
+ materials = list(materials_dict.values())
862
+ # extract all of the construction objects
863
+ constr_pattern = re.compile(r"(?i)(Construction,[\s\S]*?;)")
864
+ constr_props = tuple(parse_idf_string(idf_string) for
865
+ idf_string in constr_pattern.findall(file_contents))
866
+ constructions = []
867
+ for constr in constr_props:
868
+ try:
869
+ constr_mats = [materials_dict[mat.upper()] for mat in constr[1:]]
870
+ try:
871
+ constructions.append(WindowConstruction(constr[0], constr_mats))
872
+ except (ValueError, AssertionError):
873
+ pass # it likely has a blind or a shade and is not serialize-able
874
+ except KeyError:
875
+ pass # it's an opaque construction or window shaded construction
876
+ # extract all of the frame objects
877
+ frame_pattern = re.compile(r"(?i)(WindowProperty:FrameAndDivider,[\s\S]*?;)")
878
+ frame_strings = frame_pattern.findall(file_contents)
879
+ frame_materials = []
880
+ for fr_str in frame_strings:
881
+ try:
882
+ frame_obj = EnergyWindowFrame.from_idf(fr_str.strip())
883
+ frame_materials.append(frame_obj)
884
+ except AssertionError: # invalid window frame material, continue
885
+ pass
886
+ # if there's only one frame in the file, assume it applies to all constructions
887
+ # this is the convention used by LBNL WINDOW
888
+ if len(frame_materials) == 1:
889
+ for construct in constructions:
890
+ construct.frame = frame_materials[0]
891
+ return constructions, materials + frame_materials
892
+
893
+ def lock(self):
894
+ """The lock() method will also lock the materials."""
895
+ self._locked = True
896
+ for mat in self.materials:
897
+ mat.lock()
898
+ if self.has_frame:
899
+ self.frame.lock()
900
+
901
+ def unlock(self):
902
+ """The unlock() method will also unlock the materials."""
903
+ self._locked = False
904
+ for mat in self.materials:
905
+ mat.unlock()
906
+ if self.has_frame:
907
+ self.frame.unlock()
908
+
909
+ @staticmethod
910
+ def _idf_materials_dictionary(ep_mat_strings):
911
+ """Get a dictionary of window EnergyMaterial objects from an IDF string list."""
912
+ materials_dict = {}
913
+ for mat_str in ep_mat_strings:
914
+ mat_str = mat_str.strip()
915
+ mat_obj = None
916
+ if mat_str.startswith('WindowMaterial:SimpleGlazingSystem,'):
917
+ mat_obj = EnergyWindowMaterialSimpleGlazSys.from_idf(mat_str)
918
+ elif mat_str.startswith('WindowMaterial:Glazing,'):
919
+ mat_obj = EnergyWindowMaterialGlazing.from_idf(mat_str)
920
+ elif mat_str.startswith('WindowMaterial:Gas,'):
921
+ try:
922
+ mat_obj = EnergyWindowMaterialGas.from_idf(mat_str)
923
+ except AssertionError: # likely a custom gas to serialize differently
924
+ mat_obj = EnergyWindowMaterialGasCustom.from_idf(mat_str)
925
+ elif mat_str.startswith('WindowMaterial:GasMixture,'):
926
+ mat_obj = EnergyWindowMaterialGasMixture.from_idf(mat_str)
927
+ elif mat_str.startswith('WindowMaterial:Shade,'):
928
+ mat_obj = EnergyWindowMaterialShade.from_idf(mat_str)
929
+ elif mat_str.startswith('WindowMaterial:Blind,'):
930
+ mat_obj = EnergyWindowMaterialBlind.from_idf(mat_str)
931
+ if mat_obj is not None:
932
+ materials_dict[mat_obj.identifier.upper()] = mat_obj
933
+ return materials_dict
934
+
935
+ def _heat_gen_from_solar(self, solar_irradiance):
936
+ """Get heat generated in each material layer given incident irradiance.
937
+
938
+ Args:
939
+ solar_irradiance: The solar irradiance incident on the exterior
940
+ of the window construction in W/m2.
941
+
942
+ Returns:
943
+ A tuple with two values
944
+
945
+ - heat_gen: A list of heat absorbed in each material and air film layer.
946
+
947
+ - transmitted: The solar irradiance directly transmitted through
948
+ the construction.
949
+ """
950
+ # get the amount of solar absorbed by each glass pane
951
+ transmittance, _, absorb = self.solar_optical_properties()
952
+ transmitted = solar_irradiance * transmittance
953
+ # turn the absorbed solar into delta temperatures
954
+ heat_gen = [0]
955
+ for m_abs in absorb:
956
+ heat_gen.append(solar_irradiance * m_abs) # heat absorbed in glass
957
+ heat_gen.append(0) # heat not absorbed by the following gap
958
+ return heat_gen, transmitted
959
+
960
+ def _solve_r_values(self, r_vals, emissivities):
961
+ """Iteratively solve for R-values."""
962
+ r_last = 0
963
+ r_next = sum(r_vals)
964
+ while abs(r_next - r_last) > 0.001: # 0.001 is the r-value tolerance
965
+ r_last = sum(r_vals)
966
+ temperatures = self._temperature_profile_from_r_values(r_vals)
967
+ r_vals = self._layered_r_value(temperatures, r_vals, emissivities)
968
+ r_next = sum(r_vals)
969
+ return r_vals
970
+
971
+ def _layered_r_value_initial(self, gap_count, delta_t_guess=15,
972
+ avg_t_guess=273.15, wind_speed=6.7):
973
+ """Compute initial r-values of each layer within a layered construction."""
974
+ r_vals = [1 / self.out_h(wind_speed, avg_t_guess - delta_t_guess)]
975
+ emiss = []
976
+ delta_t = delta_t_guess / gap_count
977
+ for i, mat in enumerate(self.materials):
978
+ if isinstance(mat, _EnergyWindowMaterialGlazingBase):
979
+ r_vals.append(mat.r_value)
980
+ emiss.append(None)
981
+ else: # gas material
982
+ e_front = self.materials[i + 1].emissivity
983
+ try:
984
+ e_back = self.materials[i - 1].emissivity_back
985
+ except AttributeError:
986
+ e_back = self.materials[i - 1].emissivity
987
+ r_vals.append(1 / mat.u_value(
988
+ delta_t, e_back, e_front, t_kelvin=avg_t_guess))
989
+ emiss.append((e_back, e_front))
990
+ r_vals.append(1 / self.in_h_simple())
991
+ return r_vals, emiss
992
+
993
+ def _layered_r_value(self, temperatures, r_values_init, emiss,
994
+ height=1.0, angle=90.0, pressure=101325):
995
+ """Compute delta_t adjusted r-values of each layer within a construction."""
996
+ r_vals = [r_values_init[0]]
997
+ for i, mat in enumerate(self.materials):
998
+ if isinstance(mat, _EnergyWindowMaterialGlazingBase):
999
+ r_vals.append(r_values_init[i + 1])
1000
+ else: # gas material
1001
+ delta_t = abs(temperatures[i + 1] - temperatures[i + 2])
1002
+ avg_temp = ((temperatures[i + 1] + temperatures[i + 2]) / 2) + 273.15
1003
+ r_vals.append(1 / mat.u_value_at_angle(
1004
+ delta_t, emiss[i][0], emiss[i][1], height, angle,
1005
+ avg_temp, pressure))
1006
+ delta_t = abs(temperatures[-1] - temperatures[-2])
1007
+ avg_temp = ((temperatures[-1] + temperatures[-2]) / 2) + 273.15
1008
+ r_vals.append(1 / self.in_h(avg_temp, delta_t, height, angle, pressure))
1009
+ return r_vals
1010
+
1011
+ @staticmethod
1012
+ def _solar_optical_properties_by_gap(glazing_materials):
1013
+ """Get forward_reflectance, back_reflectance, and back_absorptance across gaps.
1014
+ """
1015
+ forward_reflectance = []
1016
+ backward_reflectance = []
1017
+ backward_absorptance = []
1018
+ for i, mat in enumerate(glazing_materials[1:]):
1019
+ # compute the fraction of inter-reflected solar off previous panes
1020
+ prev_pane = glazing_materials[i]
1021
+ back_ref = mat.solar_reflectance * prev_pane.solar_transmittance
1022
+ back_abs = mat.solar_reflectance * prev_pane.solar_absorptance
1023
+ fwrd_ref = mat.solar_reflectance * prev_pane.solar_reflectance_back
1024
+ b_ref_i, b_abs_i, f_ref_i = back_ref, fwrd_ref, back_abs
1025
+ for r in range(3): # simulate 3 bounces back and forth
1026
+ f_ref_i = f_ref_i ** 2
1027
+ b_ref_i = f_ref_i * prev_pane.solar_transmittance
1028
+ b_abs_i = f_ref_i * prev_pane.solar_absorptance
1029
+ fwrd_ref += f_ref_i
1030
+ back_ref += b_ref_i
1031
+ back_abs += b_abs_i
1032
+ forward_reflectance.append(fwrd_ref)
1033
+ backward_reflectance.append(back_ref)
1034
+ backward_absorptance.append(back_abs)
1035
+ return forward_reflectance, backward_reflectance, backward_absorptance
1036
+
1037
+ @staticmethod
1038
+ def _visible_optical_properties_by_gap(glazing_materials):
1039
+ """Get forward_reflectance, back_reflectance, and back_absorptance across gaps.
1040
+ """
1041
+ forward_reflectance = []
1042
+ backward_reflectance = []
1043
+ backward_absorptance = []
1044
+ for i, mat in enumerate(glazing_materials[1:]):
1045
+ # compute the fraction of inter-reflected visible off previous panes
1046
+ prev_pane = glazing_materials[i]
1047
+ back_ref = mat.visible_reflectance * prev_pane.visible_transmittance
1048
+ back_abs = mat.visible_reflectance * prev_pane.visible_absorptance
1049
+ fwrd_ref = mat.visible_reflectance * prev_pane.visible_reflectance_back
1050
+ b_ref_i, b_abs_i, f_ref_i = back_ref, fwrd_ref, back_abs
1051
+ for r in range(3): # simulate 3 bounces back and forth
1052
+ f_ref_i = f_ref_i ** 2
1053
+ b_ref_i = f_ref_i * prev_pane.visible_transmittance
1054
+ b_abs_i = f_ref_i * prev_pane.visible_absorptance
1055
+ fwrd_ref += f_ref_i
1056
+ back_ref += b_ref_i
1057
+ back_abs += b_abs_i
1058
+ forward_reflectance.append(fwrd_ref)
1059
+ backward_reflectance.append(back_ref)
1060
+ backward_absorptance.append(back_abs)
1061
+ return forward_reflectance, backward_reflectance, backward_absorptance
1062
+
1063
+ @staticmethod
1064
+ def _old_schema_materials(data):
1065
+ """Get material objects from an old schema definition of WindowConstruction.
1066
+
1067
+ The schema is from before May 2021 and this method should eventually be removed.
1068
+ """
1069
+ materials = {}
1070
+ for mat in data['materials']:
1071
+ materials[mat['identifier']] = dict_to_material(mat)
1072
+ try:
1073
+ mat_layers = [materials[mat_id] for mat_id in data['layers']]
1074
+ except KeyError as e:
1075
+ raise ValueError(
1076
+ 'Failed to find {} in window construction materials.'.format(e))
1077
+ return mat_layers
1078
+
1079
+ def __copy__(self):
1080
+ new_con = self.__class__(
1081
+ self.identifier, [mat.duplicate() for mat in self.materials])
1082
+ if self.has_frame:
1083
+ new_con._frame = self.frame.duplicate()
1084
+ new_con._display_name = self._display_name
1085
+ new_con._user_data = None if self._user_data is None else self._user_data.copy()
1086
+ new_con._properties._duplicate_extension_attr(self._properties)
1087
+ return new_con
1088
+
1089
+ def __key(self):
1090
+ """A tuple based on the object properties, useful for hashing."""
1091
+ return (self.identifier,) + tuple(hash(mat) for mat in self.materials) + \
1092
+ (self.frame,)
1093
+
1094
+ def __repr__(self):
1095
+ """Represent window energy construction."""
1096
+ return self._generate_idf_string('window', self.identifier, self.materials)