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,543 @@
1
+ # coding=utf-8
2
+ """Complete definition of service hot water, including schedule and load."""
3
+ from __future__ import division
4
+
5
+ from honeybee._lockable import lockable
6
+ from honeybee.typing import float_in_range, float_positive, clean_and_id_ep_string
7
+
8
+ from ._base import _LoadBase
9
+ from ..schedule.ruleset import ScheduleRuleset
10
+ from ..schedule.fixedinterval import ScheduleFixedInterval
11
+ from ..reader import parse_idf_string
12
+ from ..writer import generate_idf_string
13
+ from ..lib.schedules import always_on
14
+ from ..properties.extension import ServiceHotWaterProperties
15
+
16
+
17
+ @lockable
18
+ class ServiceHotWater(_LoadBase):
19
+ """A complete definition of service hot water, including schedules and load.
20
+
21
+ Args:
22
+ identifier: Text string for a unique ServiceHotWater ID. Must be < 100 characters
23
+ and not contain any EnergyPlus special characters. This will be used to
24
+ identify the object across a model and in the exported IDF.
25
+ flow_per_area: A numerical value for the total volume flow rate of water
26
+ per unit area of floor (L/h-m2).
27
+ schedule: A ScheduleRuleset or ScheduleFixedInterval for the use of hot water
28
+ over the course of the year. The type of this schedule should be
29
+ Fractional and the fractional values will get multiplied by the
30
+ flow_per_area to yield a complete water usage profile.
31
+ target_temperature: The target temperature of the water out of the tap in
32
+ Celsius. This the temperature after the hot water has been mixed
33
+ with cold water from the water mains. The default essentially assumes
34
+ that the flow_per_area on this object is only for water straight out
35
+ of the water heater. (Default: 60C).
36
+ sensible_fraction: A number between 0 and 1 for the fraction of the total
37
+ hot water load given off as sensible heat in the zone. (Default: 0.2).
38
+ latent_fraction: A number between 0 and 1 for the fraction of the total
39
+ hot water load that is latent (as opposed to sensible). (Default: 0.05).
40
+
41
+ Properties:
42
+ * identifier
43
+ * display_name
44
+ * flow_per_area
45
+ * schedule
46
+ * target_temperature
47
+ * sensible_fraction
48
+ * latent_fraction
49
+ * lost_fraction
50
+ * standard_watts_per_area
51
+ * user_data
52
+ """
53
+ __slots__ = ('_flow_per_area', '_schedule', '_target_temperature',
54
+ '_sensible_fraction', '_latent_fraction')
55
+ WATER_HEAT_CAPACITY = 4179600 # volumetric heat capacity of water at 25 C (J/m3-K)
56
+
57
+ def __init__(self, identifier, flow_per_area, schedule, target_temperature=60,
58
+ sensible_fraction=0.2, latent_fraction=0.05):
59
+ """Initialize ServiceHotWater."""
60
+ _LoadBase.__init__(self, identifier)
61
+ self._latent_fraction = 0 # starting value so that check runs correctly
62
+
63
+ self.flow_per_area = flow_per_area
64
+ self.schedule = schedule
65
+ self.target_temperature = target_temperature
66
+ self.sensible_fraction = sensible_fraction
67
+ self.latent_fraction = latent_fraction
68
+ self._properties = ServiceHotWaterProperties(self)
69
+
70
+ @property
71
+ def flow_per_area(self):
72
+ """Get or set the hot water volume flow rate per unit area of floor (L/h-m2)."""
73
+ return self._flow_per_area
74
+
75
+ @flow_per_area.setter
76
+ def flow_per_area(self, value):
77
+ self._flow_per_area = float_positive(value, 'hot water flow per area')
78
+
79
+ @property
80
+ def schedule(self):
81
+ """Get or set a ScheduleRuleset or ScheduleFixedInterval for hot water usage."""
82
+ return self._schedule
83
+
84
+ @schedule.setter
85
+ def schedule(self, value):
86
+ assert isinstance(value, (ScheduleRuleset, ScheduleFixedInterval)), \
87
+ 'Expected ScheduleRuleset or ScheduleFixedInterval for hot water ' \
88
+ 'schedule. Got {}.'.format(type(value))
89
+ self._check_fractional_schedule_type(value, 'ServiceHotWater')
90
+ value.lock() # lock editing in case schedule has multiple references
91
+ self._schedule = value
92
+
93
+ @property
94
+ def target_temperature(self):
95
+ """Get or set the temperature out of the tap (C)."""
96
+ return self._target_temperature
97
+
98
+ @target_temperature.setter
99
+ def target_temperature(self, value):
100
+ self._target_temperature = float_in_range(
101
+ value, 0.0, 100.0, 'hot water target temperature')
102
+
103
+ @property
104
+ def sensible_fraction(self):
105
+ """Get or set the fraction of hot water heat given off as zone sensible heat."""
106
+ return self._sensible_fraction
107
+
108
+ @sensible_fraction.setter
109
+ def sensible_fraction(self, value):
110
+ self._sensible_fraction = float_in_range(
111
+ value, 0.0, 1.0, 'hot water sensible fraction')
112
+ self._check_fractions()
113
+
114
+ @property
115
+ def latent_fraction(self):
116
+ """Get or set the fraction of hot water heat that is latent."""
117
+ return self._latent_fraction
118
+
119
+ @latent_fraction.setter
120
+ def latent_fraction(self, value):
121
+ self._latent_fraction = float_in_range(
122
+ value, 0.0, 1.0, 'hot water latent fraction')
123
+ self._check_fractions()
124
+
125
+ @property
126
+ def lost_fraction(self):
127
+ """Get the fraction of hot water heat that is lost down the drain."""
128
+ return 1 - self._sensible_fraction - self._latent_fraction
129
+
130
+ @property
131
+ def standard_watts_per_area(self):
132
+ """Get the hot water power density (W/m2) assuming a standard mains temperature.
133
+
134
+ Standard water mains temperature is 10C, which is the default water mains
135
+ temperature in EnergyPlus when none is specified.
136
+ """
137
+ flow_m3_s_m2 = self._flow_per_area / (1000. * 3600.)
138
+ delta_t = self.target_temperature - 10
139
+ return flow_m3_s_m2 * self.WATER_HEAT_CAPACITY * delta_t
140
+
141
+ def set_watts_per_area(self, watts_per_area, water_mains_temperature=10):
142
+ """Set the volume flow rate per floor area using the hot water power density.
143
+
144
+ Args:
145
+ watts_per_area: The desired hot water power density (W/m2).
146
+ water_mains_temperature: The average annual temperature of the water
147
+ mains that supply the water heater in Celsius. This should be
148
+ close to the average annual temperature. (Default: 10C).
149
+ """
150
+ delta_t = self.target_temperature - water_mains_temperature
151
+ flow_m3_s_m2 = watts_per_area / (self.WATER_HEAT_CAPACITY * delta_t)
152
+ self._flow_per_area = flow_m3_s_m2 * 1000. * 3600.
153
+
154
+ def diversify(self, count, flow_stdev=20, schedule_offset=1, timestep=1,
155
+ schedule_indices=None):
156
+ """Get an array of diversified ServiceHotWater derived from this "average" one.
157
+
158
+ Approximately 2/3 of the schedules in the output objects will be offset
159
+ from the mean by the input schedule_offset (1/3 ahead and another 1/3 behind).
160
+
161
+ Args:
162
+ count: An positive integer for the number of diversified objects to
163
+ generate from this mean object.
164
+ flow_stdev: A number between 0 and 100 for the percent of the flow_per_area
165
+ representing one standard deviation of diversification from
166
+ the mean. (Default 20 percent).
167
+ schedule_offset: A positive integer for the number of timesteps at which
168
+ the lighting schedule of the resulting objects will be shifted - roughly
169
+ 1/3 of the objects ahead and another 1/3 behind. (Default: 1).
170
+ timestep: An integer for the number of timesteps per hour at which the
171
+ shifting is occurring. This must be a value between 1 and 60, which
172
+ is evenly divisible by 60. 1 indicates that each step is an hour
173
+ while 60 indicates that each step is a minute. (Default: 1).
174
+ schedule_indices: An optional list of integers from 0 to 2 with a length
175
+ equal to the input count, which will be used to set whether a given
176
+ schedule is behind (0), ahead (2), or the same (1). This can be
177
+ used to coordinate schedules across diversified programs. If None
178
+ a random list of integers will be genrated. (Default: None).
179
+ """
180
+ # generate shifted schedules and a gaussian distribution of flow_per_area
181
+ usage_schs = self._shift_schedule(self.schedule, schedule_offset, timestep)
182
+ stdev = self.flow_per_area * (flow_stdev / 100)
183
+ new_loads, sch_ints = self._gaussian_values(count, self.flow_per_area, stdev)
184
+ sch_ints = sch_ints if schedule_indices is None else schedule_indices
185
+
186
+ # generate the new objects and return them
187
+ new_objects = []
188
+ for load_val, sch_int in zip(new_loads, sch_ints):
189
+ new_obj = self.duplicate()
190
+ new_obj.identifier = clean_and_id_ep_string(self.identifier)
191
+ new_obj.flow_per_area = load_val
192
+ new_obj.schedule = usage_schs[sch_int]
193
+ new_objects.append(new_obj)
194
+ return new_objects
195
+
196
+ @classmethod
197
+ def from_watts_per_area(
198
+ cls, identifier, watts_per_area, schedule, target_temperature=60,
199
+ sensible_fraction=0.2, latent_fraction=0.05, water_mains_temperature=10):
200
+ """Create a ServiceHotWater object from hot water power density (W/m2).
201
+
202
+ Args:
203
+ identifier: Text string for a unique ServiceHotWater ID. Must be < 100
204
+ characters and not contain any EnergyPlus special characters.
205
+ This will be used to identify the object across a model and in
206
+ the exported IDF.
207
+ watts_per_area: The desired hot water power density (W/m2).
208
+ schedule: A ScheduleRuleset or ScheduleFixedInterval for the use of hot
209
+ water over the course of the year. The type of this schedule should be
210
+ Fractional and the fractional values will get multiplied by the
211
+ watts_per_area to yield a complete hot water profile.
212
+ target_temperature: The target temperature of the water out of the tap in
213
+ Celsius. This the temperature after the hot water has been mixed
214
+ with cold water from the water mains. The default essentially assumes
215
+ that the flow_per_area on this object is only for water straight out
216
+ of the water heater. (Default: 60C).
217
+ sensible_fraction: A number between 0 and 1 for the fraction of the total
218
+ hot water load given off as sensible heat in the zone. (Default: 0.2).
219
+ latent_fraction: A number between 0 and 1 for the fraction of the total
220
+ hot water load that is latent (as opposed to sensible). (Default: 0.05).
221
+ water_mains_temperature: The average annual temperature of the water
222
+ mains that supply the water heater in Celsius. This should be
223
+ close to the average annual temperature. (Default: 10C).
224
+ """
225
+ shw = cls(identifier, 0, schedule, target_temperature,
226
+ sensible_fraction, latent_fraction)
227
+ shw.set_watts_per_area(watts_per_area, water_mains_temperature)
228
+ return shw
229
+
230
+ @classmethod
231
+ def from_idf(cls, idf_string, floor_area, schedule_dict):
232
+ """Create a ServiceHotWater object from an IDF WaterUse:Equipment string.
233
+
234
+ Args:
235
+ idf_string: A text string of an EnergyPlus WaterUse:Equipment definition.
236
+ floor_area: A number for the floor area of the room to which the
237
+ WaterUse:Equipment definition is assigned.
238
+ schedule_dict: A dictionary with schedule identifiers as keys and honeybee
239
+ schedule objects as values (either ScheduleRuleset or
240
+ ScheduleFixedInterval). These will be used to assign the schedules to
241
+ the ServiceHotWater object.
242
+
243
+ Returns:
244
+ A tuple with two elements
245
+
246
+ - shw: A ServiceHotWater object loaded from the idf_string.
247
+
248
+ - zone_identifier: The identifier of the zone to which the ServiceHotWater
249
+ object should be assigned. Will be None if no zone is found.
250
+
251
+ - total_flow: Number for the absolute flow rate of the ServiceHotWater
252
+ object in L/h.
253
+ """
254
+ # check the inputs
255
+ ep_strs = parse_idf_string(idf_string, 'WaterUse:Equipment,')
256
+
257
+ # extract the flow rate
258
+ total_flow = float(ep_strs[2]) * 1000. * 3600.
259
+ flow_per_area = total_flow / floor_area if floor_area != 0 else 0
260
+
261
+ # extract the schedule from the string
262
+ sched = always_on
263
+ if len(ep_strs) > 3 and ep_strs[3] != '':
264
+ try:
265
+ sched = schedule_dict[ep_strs[3]]
266
+ except KeyError as e:
267
+ raise ValueError('Failed to find {} in the schedule_dict.'.format(e))
268
+
269
+ # try to extract the target temperature
270
+ target = cls._schedule_single_value(ep_strs, 4, None, schedule_dict)
271
+ sens = cls._schedule_single_value(ep_strs, 8, 0, schedule_dict)
272
+ latent = cls._schedule_single_value(ep_strs, 9, 0, schedule_dict)
273
+
274
+ # return the hot water object and the zone id if it exists
275
+ obj_id = ep_strs[0].split('..')[0]
276
+ zone_id = ep_strs[7] if len(ep_strs) > 7 and ep_strs[7] != '' else None
277
+ shw = cls(obj_id, flow_per_area, sched, target, sens, latent)
278
+ return shw, zone_id, total_flow
279
+
280
+ @classmethod
281
+ def from_dict(cls, data):
282
+ """Create a ServiceHotWater object from a dictionary.
283
+
284
+ Note that the dictionary must be a non-abridged version for this classmethod
285
+ to work.
286
+
287
+ Args:
288
+ data: A ServiceHotWater dictionary in following the format below.
289
+
290
+ .. code-block:: python
291
+
292
+ {
293
+ "type": 'ServiceHotWater',
294
+ "identifier": 'Residential_SHW_015',
295
+ "display_name": 'Residential Hot Water',
296
+ "flow_per_area": 0.15, # how water L/h per square meter of floor area
297
+ "schedule": {}, # ScheduleRuleset/ScheduleFixedInterval dictionary
298
+ "target_temperature": 60, # target temperature in C
299
+ "sensible_fraction": 0.2, # fraction of heat that is sensible
300
+ "latent_fraction": 0.05 # fraction of heat that is latent
301
+ }
302
+ """
303
+ assert data['type'] == 'ServiceHotWater', \
304
+ 'Expected ServiceHotWater dictionary. Got {}.'.format(data['type'])
305
+ sched = cls._get_schedule_from_dict(data['schedule'])
306
+ target, sens_fract, lat_fract = cls._optional_dict_keys(data)
307
+ new_obj = cls(data['identifier'], data['flow_per_area'], sched,
308
+ target, sens_fract, lat_fract)
309
+ if 'user_data' in data and data['user_data'] is not None:
310
+ new_obj.user_data = data['user_data']
311
+ if 'properties' in data and data['properties'] is not None:
312
+ new_obj.properties._load_extension_attr_from_dict(data['properties'])
313
+ return cls._apply_optional_dict_props(new_obj, data)
314
+
315
+ @classmethod
316
+ def from_dict_abridged(cls, data, schedule_dict):
317
+ """Create a ServiceHotWater object from an abridged dictionary.
318
+
319
+ Args:
320
+ data: A ServiceHotWaterAbridged dictionary in following the format below.
321
+ schedule_dict: A dictionary with schedule identifiers as keys and
322
+ honeybee schedule objects as values (either ScheduleRuleset or
323
+ ScheduleFixedInterval). These will be used to assign the schedules
324
+ to the ServiceHotWater object.
325
+
326
+ .. code-block:: python
327
+
328
+ {
329
+ "type": 'ServiceHotWaterAbridged',
330
+ "identifier": 'Residential_SHW_015',
331
+ "display_name": 'Residential Hot Water',
332
+ "flow_per_area": 0.15, # how water L/h per square meter of floor area
333
+ "schedule": 'Residential DHW Usage', # schedule identifier
334
+ "target_temperature": 60, # target temperature in C
335
+ "sensible_fraction": 0.2, # fraction of heat that is sensible
336
+ "latent_fraction": 0.05 # fraction of heat that is latent
337
+ }
338
+ """
339
+ assert data['type'] == 'ServiceHotWaterAbridged', \
340
+ 'Expected ServiceHotWaterAbridged dictionary. Got {}.'.format(data['type'])
341
+ try:
342
+ sched = schedule_dict[data['schedule']]
343
+ except KeyError as e:
344
+ raise ValueError('Failed to find {} in the schedule_dict.'.format(e))
345
+ target, sens_fract, lat_fract = cls._optional_dict_keys(data)
346
+ new_obj = cls(data['identifier'], data['flow_per_area'], sched,
347
+ target, sens_fract, lat_fract)
348
+ if 'user_data' in data and data['user_data'] is not None:
349
+ new_obj.user_data = data['user_data']
350
+ if 'properties' in data and data['properties'] is not None:
351
+ new_obj.properties._load_extension_attr_from_dict(data['properties'])
352
+ return cls._apply_optional_dict_props(new_obj, data)
353
+
354
+ def to_idf(self, room):
355
+ """IDF string representation of ServiceHotWater object.
356
+
357
+ Note that this method only outputs a string for the WaterUse:Equipment
358
+ object and a Schedule:Constant for the target temperature. Thus, to write
359
+ everything needed to describe the object into an IDF, this object's
360
+ schedule must also be written.
361
+
362
+ Args:
363
+ room: The honeybee Room to which this ServiceHotWater object is being
364
+ applied. This is needed for both to convert the flow_per_area to
365
+ an absolute flow and to assign the hot water object to the Room
366
+ (such that sensible/latent heat gains are transferred to the Room).
367
+
368
+ Returns:
369
+ A tuple with two values.
370
+
371
+ - water_use: A WaterUse:Equipment string for the ServiceHotWater.
372
+
373
+ - schedules: A list of Schedule:Constant strings for the schedules
374
+ needed to describe the target temperatures as well as the sensible
375
+ and latent fractions.
376
+
377
+ .. code-block:: shell
378
+
379
+ WaterUse:Equipment,
380
+ Showers, !- Name
381
+ Domestic Hot Water, !- End-Use Subcategory
382
+ 0.0002, !- Peak Flow Rate {m3/s}
383
+ Shower Schedule, !- Flow Rate Fraction Schedule Name
384
+ Shower Target Temp, !- Target Temperature Schedule Name
385
+ Hot Water Temp, !- Hot Water Supply Temperature Schedule Name
386
+ , !- Cold Water Supply Temperature Schedule Name
387
+ Shower Room, !- Zone Name
388
+ Sensible Frac Schedule, !- Sensible Fraction Schedule Name
389
+ Latent Frac Schedule; !- Latent Fraction Schedule Name
390
+ """
391
+ # create the Schedule:Constant strings
392
+ u_id = '{}..{}'.format(self.identifier, room.identifier)
393
+ s_com, s_obj = ('name', 'schedule type limits', 'value'), 'Schedule:Constant'
394
+ schedules = []
395
+ sens_sch, lat_sch = '', ''
396
+ hot_fields = ('{}_SHW_Target'.format(u_id), '', self.target_temperature)
397
+ schedules.append(generate_idf_string(s_obj, hot_fields, s_com))
398
+ if self.sensible_fraction != 0:
399
+ sens_sch = '{}_SHW_Sensible'.format(u_id)
400
+ sens_fields = (sens_sch, '', self.sensible_fraction)
401
+ schedules.append(generate_idf_string(s_obj, sens_fields, s_com))
402
+ if self.latent_fraction != 0:
403
+ lat_sch = '{}_SHW_Latent'.format(u_id)
404
+ lat_fields = (lat_sch, '', self.latent_fraction)
405
+ schedules.append(generate_idf_string(s_obj, lat_fields, s_com))
406
+
407
+ # create the Water Use string
408
+ total_flow = (self.flow_per_area / 3600000.) * room.floor_area
409
+ values = (u_id, 'General', total_flow, self.schedule.identifier, hot_fields[0],
410
+ hot_fields[0], '', room.zone, sens_sch, lat_sch)
411
+ comments = ('name', 'end use subcategory', 'peak flow rate {m/s}',
412
+ 'schedule name', 'target temp schedule', 'hot water temp schedule',
413
+ 'cold water temp schedule', 'zone name',
414
+ 'sensible fraction', 'latent fraction')
415
+ water_use = generate_idf_string('WaterUse:Equipment', values, comments)
416
+ return water_use, schedules
417
+
418
+ def to_dict(self, abridged=False):
419
+ """ServiceHotWater dictionary representation.
420
+
421
+ Args:
422
+ abridged: Boolean to note whether the full dictionary describing the
423
+ object should be returned (False) or just an abridged version (True),
424
+ which only specifies the identifiers of schedules. (Default: False).
425
+ """
426
+ base = {'type': 'ServiceHotWater'} if not abridged else \
427
+ {'type': 'ServiceHotWaterAbridged'}
428
+ base['identifier'] = self.identifier
429
+ base['flow_per_area'] = self.flow_per_area
430
+ base['target_temperature'] = self.target_temperature
431
+ base['sensible_fraction'] = self.sensible_fraction
432
+ base['latent_fraction'] = self.latent_fraction
433
+ base['schedule'] = self.schedule.to_dict() if not \
434
+ abridged else self.schedule.identifier
435
+ if self._display_name is not None:
436
+ base['display_name'] = self.display_name
437
+ if self._user_data is not None:
438
+ base['user_data'] = self.user_data
439
+ prop_dict = self.properties.to_dict()
440
+ if prop_dict is not None:
441
+ base['properties'] = prop_dict
442
+ return base
443
+
444
+ @staticmethod
445
+ def average(identifier, hot_waters, weights=None, timestep_resolution=1):
446
+ """Get a ServiceHotWater object that's a weighted average between other objects.
447
+
448
+ Args:
449
+ identifier: Text string for a unique ID for the new averaged ServiceHotWater.
450
+ Must be < 100 characters and not contain any EnergyPlus special
451
+ characters. This will be used to identify the object across a model
452
+ and in the exported IDF.
453
+ hot_waters: A list of ServiceHotWater objects that will be averaged
454
+ together to make a new ServiceHotWater.
455
+ weights: An optional list of fractional numbers with the same length
456
+ as the input hot_waters. These will be used to weight each of the
457
+ ServiceHotWater objects in the resulting average. Note that these weights
458
+ can sum to less than 1 in which case the average flow_per_area will
459
+ assume 0 for the unaccounted fraction of the weights.
460
+ If None, the objects will be weighted equally. (Default: None).
461
+ timestep_resolution: An optional integer for the timestep resolution
462
+ at which the schedules will be averaged. Any schedule details
463
+ smaller than this timestep will be lost in the averaging process.
464
+ (Default: 1).
465
+ """
466
+ weights, u_weights = ServiceHotWater._check_avg_weights(
467
+ hot_waters, weights, 'ServiceHotWater')
468
+
469
+ # calculate the average values
470
+ flow_d = sum([s.flow_per_area * w for s, w in zip(hot_waters, weights)])
471
+ target = sum([s.target_temperature * w for s, w in zip(hot_waters, u_weights)])
472
+ sen_fract = sum([s.sensible_fraction * w for s, w in zip(hot_waters, u_weights)])
473
+ lat_fract = sum([s.latent_fraction * w for s, w in zip(hot_waters, u_weights)])
474
+
475
+ # calculate the average schedules
476
+ sched = ServiceHotWater._average_schedule(
477
+ '{} Schedule'.format(identifier), [s.schedule for s in hot_waters],
478
+ u_weights, timestep_resolution)
479
+
480
+ # return the averaged object
481
+ return ServiceHotWater(identifier, flow_d, sched, target, sen_fract, lat_fract)
482
+
483
+ def _check_fractions(self):
484
+ """Check that the fractions sum to less than 1."""
485
+ tot = (self._sensible_fraction, self._latent_fraction)
486
+ assert sum(tot) <= 1 + 1e-9, 'Sum of equipment sensible_fraction and ' \
487
+ 'latent_fraction ({}) is greater than 1.'.format(sum(tot))
488
+
489
+ @staticmethod
490
+ def _schedule_single_value(ep_strs, index, default, schedule_dict):
491
+ """Extract a single value from a schedule."""
492
+ if len(ep_strs) > index and ep_strs[index] != '':
493
+ try:
494
+ t_sched = schedule_dict[ep_strs[index]]
495
+ if isinstance(t_sched, ScheduleRuleset):
496
+ return t_sched.default_day_schedule.values[0]
497
+ else: # FixedInterval schedule
498
+ return t_sched.values[0]
499
+ except KeyError as e:
500
+ raise ValueError('Failed to find {} in the schedule_dict.'.format(e))
501
+ return default
502
+
503
+ @staticmethod
504
+ def _optional_dict_keys(data):
505
+ """Get the optional keys from an ServiceHotWater dictionary."""
506
+ target = data['target_temperature'] if 'target_temperature' in data else 60
507
+ sens_fract = data['sensible_fraction'] if 'sensible_fraction' in data else 0.2
508
+ lat_fract = data['latent_fraction'] if 'latent_fraction' in data else 0.05
509
+ return target, sens_fract, lat_fract
510
+
511
+ @staticmethod
512
+ def _apply_optional_dict_props(new_obj, data):
513
+ """Apply optional properties like display_name to an object from a dictionary."""
514
+ if 'display_name' in data and data['display_name'] is not None:
515
+ new_obj.display_name = data['display_name']
516
+ return new_obj
517
+
518
+ def __key(self):
519
+ """A tuple based on the object properties, useful for hashing."""
520
+ return (self.identifier, self.flow_per_area, hash(self.schedule),
521
+ self.target_temperature, self.sensible_fraction, self.latent_fraction)
522
+
523
+ def __hash__(self):
524
+ return hash(self.__key())
525
+
526
+ def __eq__(self, other):
527
+ return isinstance(other, ServiceHotWater) and self.__key() == other.__key()
528
+
529
+ def __ne__(self, other):
530
+ return not self.__eq__(other)
531
+
532
+ def __copy__(self):
533
+ new_obj = ServiceHotWater(
534
+ self.identifier, self.flow_per_area, self.schedule,
535
+ self.target_temperature, self.sensible_fraction, self.latent_fraction)
536
+ new_obj._display_name = self._display_name
537
+ new_obj._user_data = None if self._user_data is None else self._user_data.copy()
538
+ new_obj._properties._duplicate_extension_attr(self._properties)
539
+ return new_obj
540
+
541
+ def __repr__(self):
542
+ return 'ServiceHotWater: {} [{} L/h-m2] [schedule: {}]'.format(
543
+ self.identifier, self.flow_per_area, self.schedule.identifier)