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,816 @@
1
+ # coding=utf-8
2
+ """Temperature (thermostat) and humidity (humidistat) setpoints for a thermal zone."""
3
+ from __future__ import division
4
+ import random
5
+
6
+ from honeybee._lockable import lockable
7
+ from honeybee.typing import float_positive, float_in_range, clean_and_id_ep_string
8
+
9
+ from ._base import _LoadBase
10
+ from ..schedule.ruleset import ScheduleRuleset
11
+ from ..schedule.fixedinterval import ScheduleFixedInterval
12
+ from ..reader import parse_idf_string
13
+ from ..writer import generate_idf_string
14
+ from ..properties.extension import SetpointProperties
15
+
16
+ import honeybee_energy.lib.scheduletypelimits as _type_lib
17
+
18
+
19
+ @lockable
20
+ class Setpoint(_LoadBase):
21
+ """Temperature (thermostat) and humidity (humidistat) setpoints for a thermal zone.
22
+
23
+ Args:
24
+ identifier: Text string for a unique Setpoint ID. Must be < 100 characters
25
+ and not contain any EnergyPlus special characters. This will be used to
26
+ identify the object across a model and in the exported IDF.
27
+ heating_schedule: A ScheduleRuleset or ScheduleFixedInterval for the
28
+ heating setpoint.
29
+ cooling_schedule: A ScheduleRuleset or ScheduleFixedInterval for the
30
+ cooling setpoint.
31
+ humidifying_schedule: A ScheduleRuleset or ScheduleFixedInterval for
32
+ the humidification setpoint. If None, no additional humidification
33
+ will be applied by the HVAC system. (Default: None).
34
+ dehumidifying_schedule: A ScheduleRuleset or ScheduleFixedInterval for
35
+ the dehumidification setpoint. If None, no additional dehumidification
36
+ will be performed by the HVAC system. (Default: None).
37
+ setpoint_cutout_difference: An optional positive number for the temperature
38
+ difference between the cutout temperature and the setpoint temperature.
39
+ Specifying a non-zero number here is useful for modeling the throttling
40
+ range associated with a given setup of setpoint controls and HVAC equipment.
41
+ Throttling ranges describe the range where a zone is slightly over-cooled
42
+ or over-heated beyond the thermostat setpoint. They are used to avoid
43
+ situations where HVAC systems turn on only to turn off a few minutes later,
44
+ thereby wearing out the parts of mechanical systems faster. They can
45
+ have a minor impact on energy consumption and can often have significant
46
+ impacts on occupant thermal comfort, though using the default value
47
+ of zero will often yield results that are close enough when trying
48
+ to estimate the annual heating/cooling energy use. Specifying a value
49
+ of zero effectively assumes that the system will turn on whenever
50
+ conditions are outside the setpoint range and will cut out as soon
51
+ as the setpoint is reached. (Default: 0).
52
+
53
+ Properties:
54
+ * identifier
55
+ * display_name
56
+ * heating_schedule
57
+ * cooling_schedule
58
+ * humidifying_schedule
59
+ * dehumidifying_schedule
60
+ * setpoint_cutout_difference
61
+ * heating_setpoint
62
+ * cooling_setpoint
63
+ * humidifying_setpoint
64
+ * dehumidifying_setpoint
65
+ * heating_setback
66
+ * cooling_setback
67
+ * humidifying_setback
68
+ * dehumidifying_setback
69
+ * user_data
70
+ """
71
+ __slots__ = ('_heating_schedule', '_cooling_schedule', '_humidifying_schedule',
72
+ '_dehumidifying_schedule', '_setpoint_cutout_difference')
73
+ _humidifying_schedule_no_limit = ScheduleRuleset.from_constant_value(
74
+ 'HumidNoLimit', 0, _type_lib.humidity)
75
+ _dehumidifying_schedule_no_limit = ScheduleRuleset.from_constant_value(
76
+ 'DeHumidNoLimit', 100, _type_lib.humidity)
77
+
78
+ def __init__(self, identifier, heating_schedule, cooling_schedule,
79
+ humidifying_schedule=None, dehumidifying_schedule=None,
80
+ setpoint_cutout_difference=0):
81
+ """Initialize Setpoint."""
82
+ _LoadBase.__init__(self, identifier)
83
+ # defaults that might be overwritten
84
+ self._dehumidifying_schedule = None
85
+
86
+ self.heating_schedule = heating_schedule
87
+ self.cooling_schedule = cooling_schedule
88
+ self.humidifying_schedule = humidifying_schedule
89
+ self.dehumidifying_schedule = dehumidifying_schedule
90
+ self.setpoint_cutout_difference = setpoint_cutout_difference
91
+ self._properties = SetpointProperties(self)
92
+
93
+ @property
94
+ def heating_schedule(self):
95
+ """Get or set a ScheduleRuleset or ScheduleFixedInterval for the heating setpoint.
96
+ """
97
+ return self._heating_schedule
98
+
99
+ @heating_schedule.setter
100
+ def heating_schedule(self, value):
101
+ self._check_temperature_schedule_type(value, 'Heating Setpoint')
102
+ value.lock() # lock editing in case schedule has multiple references
103
+ self._heating_schedule = value
104
+
105
+ @property
106
+ def cooling_schedule(self):
107
+ """Get or set a ScheduleRuleset or ScheduleFixedInterval for the cooling setpoint.
108
+ """
109
+ return self._cooling_schedule
110
+
111
+ @cooling_schedule.setter
112
+ def cooling_schedule(self, value):
113
+ self._check_temperature_schedule_type(value, 'Cooling Setpoint')
114
+ value.lock() # lock editing in case schedule has multiple references
115
+ self._cooling_schedule = value
116
+
117
+ @property
118
+ def humidifying_schedule(self):
119
+ """Get or set a ScheduleRuleset or ScheduleFixedInterval for humidification.
120
+ """
121
+ return self._humidifying_schedule
122
+
123
+ @humidifying_schedule.setter
124
+ def humidifying_schedule(self, value):
125
+ if value is not None:
126
+ self._check_humidity_schedule_type(value, 'Humidifying Setpoint')
127
+ value.lock() # lock editing in case schedule has multiple references
128
+ self._humidifying_schedule = value
129
+ if self._dehumidifying_schedule is None:
130
+ self._dehumidifying_schedule = self._dehumidifying_schedule_no_limit
131
+ else:
132
+ self._humidifying_schedule = None if self._dehumidifying_schedule is None \
133
+ else self._humidifying_schedule_no_limit
134
+
135
+ @property
136
+ def dehumidifying_schedule(self):
137
+ """Get or set a ScheduleRuleset or ScheduleFixedInterval for dehumidification.
138
+ """
139
+ return self._dehumidifying_schedule
140
+
141
+ @dehumidifying_schedule.setter
142
+ def dehumidifying_schedule(self, value):
143
+ if value is not None:
144
+ self._check_humidity_schedule_type(value, 'Dehumidifying Setpoint')
145
+ value.lock() # lock editing in case schedule has multiple references
146
+ self._dehumidifying_schedule = value
147
+ if self._humidifying_schedule is None:
148
+ self._humidifying_schedule = self._humidifying_schedule_no_limit
149
+ else:
150
+ self._dehumidifying_schedule = None if self._humidifying_schedule is None \
151
+ else self._dehumidifying_schedule_no_limit
152
+
153
+ @property
154
+ def setpoint_cutout_difference(self):
155
+ """Get or set the temperature difference between the cutout and the setpoint."""
156
+ return self._setpoint_cutout_difference
157
+
158
+ @setpoint_cutout_difference.setter
159
+ def setpoint_cutout_difference(self, value):
160
+ self._setpoint_cutout_difference = float_positive(
161
+ value, 'setpoint cutout difference')
162
+
163
+ @property
164
+ def heating_setpoint(self):
165
+ """Get or set a single constant temperature for the heating setpoint [C].
166
+
167
+ Note that, if a varying heating_schedule has been assigned to this object, this
168
+ property will be the highest temperature within the heating_schedule.
169
+ """
170
+ return self._max_schedule_value(self._heating_schedule)
171
+
172
+ @heating_setpoint.setter
173
+ def heating_setpoint(self, value):
174
+ value = float_in_range(value, -273.15, input_name='heating setpoint')
175
+ schedule = ScheduleRuleset.from_constant_value(
176
+ '{}_HtgSetp'.format(self.identifier), value, _type_lib.temperature)
177
+ self.heating_schedule = schedule
178
+
179
+ @property
180
+ def cooling_setpoint(self):
181
+ """Get or set a single constant temperature for the cooling setpoint [C].
182
+
183
+ Note that, if a varying cooling_schedule has been assigned to this object, this
184
+ property will be the lowest temperature within the cooling_schedule.
185
+ """
186
+ return self._min_schedule_value(self._cooling_schedule)
187
+
188
+ @cooling_setpoint.setter
189
+ def cooling_setpoint(self, value):
190
+ value = float_in_range(value, -273.15, input_name='cooling setpoint')
191
+ schedule = ScheduleRuleset.from_constant_value(
192
+ '{}_ClgSetp'.format(self.identifier), value, _type_lib.temperature)
193
+ self.cooling_schedule = schedule
194
+
195
+ @property
196
+ def humidifying_setpoint(self):
197
+ """Get or set a single constant value for the humidifying setpoint [%].
198
+
199
+ Note that, if a varying humidifying_schedule has been assigned to this object,
200
+ this property will be the lowest value within the humidifying_schedule.
201
+ """
202
+ return self._max_schedule_value(self._humidifying_schedule) if \
203
+ self._humidifying_schedule is not None else None
204
+
205
+ @humidifying_setpoint.setter
206
+ def humidifying_setpoint(self, value):
207
+ if value is not None:
208
+ value = float_in_range(value, 0, 100, 'humidifying setpoint')
209
+ schedule = ScheduleRuleset.from_constant_value(
210
+ '{}_HumidSetp'.format(self.identifier), value, _type_lib.humidity)
211
+ self.humidifying_schedule = schedule
212
+ else:
213
+ self.humidifying_schedule = None
214
+
215
+ @property
216
+ def dehumidifying_setpoint(self):
217
+ """Get or set a single constant value for the dehumidifying setpoint [%].
218
+
219
+ Note that, if a varying dehumidifying_schedule has been assigned to this object,
220
+ this property will be the lowest value within the dehumidifying_schedule.
221
+ """
222
+ return self._min_schedule_value(self._dehumidifying_schedule) if \
223
+ self._dehumidifying_schedule is not None else None
224
+
225
+ @dehumidifying_setpoint.setter
226
+ def dehumidifying_setpoint(self, value):
227
+ if value is not None:
228
+ value = float_in_range(value, 0, 100, 'dehumidifying setpoint')
229
+ schedule = ScheduleRuleset.from_constant_value(
230
+ '{}_DeHumidSetp'.format(self.identifier), value, _type_lib.humidity)
231
+ self.dehumidifying_schedule = schedule
232
+ else:
233
+ self.dehumidifying_schedule = None
234
+
235
+ @property
236
+ def heating_setback(self):
237
+ """Get the lowest temperature in the heating setpoint schedule [C].
238
+
239
+ Note that, if a constant heating_setpoint has been assigned to this object,
240
+ this property will the same as the heating_setpoint.
241
+ """
242
+ return self._min_schedule_value(self._heating_schedule)
243
+
244
+ @property
245
+ def cooling_setback(self):
246
+ """Get the highest temperature in the cooling setpoint schedule [C].
247
+
248
+ Note that, if a constant cooling_setpoint has been assigned to this object,
249
+ this property will the same as the cooling_setpoint.
250
+ """
251
+ return self._max_schedule_value(self._cooling_schedule)
252
+
253
+ @property
254
+ def humidifying_setback(self):
255
+ """Get the lowest humidity in the humidifying setpoint schedule [%].
256
+
257
+ Note that, if a constant humidifying_setpoint has been assigned to this object,
258
+ this property will the same as the humidifying_setpoint.
259
+ """
260
+ return self._min_schedule_value(self._humidifying_schedule) if \
261
+ self._humidifying_schedule is not None else None
262
+
263
+ @property
264
+ def dehumidifying_setback(self):
265
+ """Get the highest humidity in the dehumidifying setpoint schedule [%].
266
+
267
+ Note that, if a constant dehumidifying_setpoint has been assigned to this object,
268
+ this property will the same as the dehumidifying_setpoint.
269
+ """
270
+ return self._max_schedule_value(self._dehumidifying_schedule) if \
271
+ self._dehumidifying_schedule is not None else None
272
+
273
+ def remove_humidity_setpoints(self):
274
+ """Remove all humidity setpoints from this object."""
275
+ self._humidifying_schedule = None
276
+ self._dehumidifying_schedule = None
277
+
278
+ def add_humidity_from_idf(self, idf_string, schedule_dict):
279
+ """Add humidity setpoints to this object from an EnergyPlus IDF text string.
280
+
281
+ Args:
282
+ idf_string: A text string fully describing an EnergyPlus
283
+ ZoneControl:Humidistat definition.
284
+ schedule_dict: A dictionary with schedule identifiers as keys and honeybee
285
+ schedule objects as values (either ScheduleRuleset or
286
+ ScheduleFixedInterval). These will be used to assign the schedules to
287
+ the Setpoint object.
288
+ """
289
+ # check the inputs
290
+ ep_strs = parse_idf_string(idf_string, 'ZoneControl:Humidistat,')
291
+
292
+ # extract the schedules from the string
293
+ try:
294
+ try:
295
+ humid_sched = schedule_dict[ep_strs[2]] if ep_strs[2] != '' else None
296
+ dehumid_sched = schedule_dict[ep_strs[3]] if ep_strs[3] != '' else None
297
+ except KeyError as e:
298
+ raise ValueError('Failed to find {} in the schedule_dict.'.format(e))
299
+ except IndexError:
300
+ pass # shorter humidistat definition lacking values
301
+
302
+ # assign the properties to this object
303
+ self.humidifying_schedule = humid_sched
304
+ self.dehumidifying_schedule = dehumid_sched
305
+
306
+ def diversify(self, count, schedule_offset=1, timestep=1, schedule_indices=None):
307
+ """Get an array of diversified Setpoints derived from this "average" one.
308
+
309
+ Approximately 2/3 of the schedules in the output objects will be offset
310
+ from the mean by the input schedule_offset (1/3 ahead and another 1/3 behind).
311
+
312
+ Args:
313
+ count: An positive integer for the number of diversified objects to
314
+ generate from this mean object.
315
+ schedule_offset: A positive integer for the number of timesteps at which
316
+ the setpoint schedule of the resulting objects will be shifted - roughly
317
+ 1/3 of the objects ahead and another 1/3 behind. (Default: 1).
318
+ timestep: An integer for the number of timesteps per hour at which the
319
+ shifting is occurring. This must be a value between 1 and 60, which
320
+ is evenly divisible by 60. 1 indicates that each step is an hour
321
+ while 60 indicates that each step is a minute. (Default: 1).
322
+ schedule_indices: An optional list of integers from 0 to 2 with a length
323
+ equal to the input count, which will be used to set whether a given
324
+ schedule is behind (0), ahead (2), or the same (1). This can be
325
+ used to coordinate schedules across diversified programs. If None
326
+ a random list of integers will be genrated. (Default: None).
327
+ """
328
+ # generate shifted schedules
329
+ heats = self._shift_schedule(self.heating_schedule, schedule_offset, timestep)
330
+ cools = self._shift_schedule(self.cooling_schedule, schedule_offset, timestep)
331
+ if self.humidifying_schedule is not None:
332
+ humids = self._shift_schedule(
333
+ self.humidifying_schedule, schedule_offset, timestep)
334
+ dehumids = self._shift_schedule(
335
+ self.dehumidifying_schedule, schedule_offset, timestep)
336
+ if schedule_indices is None:
337
+ schedule_indices = [random.randint(0, 2) for i in range(count)]
338
+
339
+ # generate the new objects and return them
340
+ new_objects = []
341
+ for sch_int in schedule_indices:
342
+ new_obj = self.duplicate()
343
+ new_obj.identifier = clean_and_id_ep_string(self.identifier)
344
+ new_obj.heating_schedule = heats[sch_int]
345
+ new_obj.cooling_schedule = cools[sch_int]
346
+ if self.humidifying_schedule is not None:
347
+ new_obj.humidifying_schedule = humids[sch_int]
348
+ new_obj.dehumidifying_schedule = dehumids[sch_int]
349
+ new_objects.append(new_obj)
350
+ return new_objects
351
+
352
+ @classmethod
353
+ def from_idf(cls, idf_string, schedule_dict):
354
+ """Create an Setpoint object from an EnergyPlus IDF text string.
355
+
356
+ Note that this method only loads the heating and cooling setpoints from an
357
+ IDF and, to also load humidity setpoints, the add_humidity_from_idf method
358
+ should be used.
359
+
360
+ Args:
361
+ idf_string: A text string fully describing an EnergyPlus
362
+ HVACTemplate:Thermostat definition.
363
+ schedule_dict: A dictionary with schedule identifiers as keys and honeybee
364
+ schedule objects as values (either ScheduleRuleset or
365
+ ScheduleFixedInterval). These will be used to assign the schedules to
366
+ the Setpoint object.
367
+
368
+ Returns:
369
+ setpoint -- A Setpoint object loaded from the idf_string.
370
+ """
371
+ # check the inputs
372
+ ep_strs = parse_idf_string(idf_string, 'HVACTemplate:Thermostat,')
373
+
374
+ # remove the zone id from the thermostat
375
+ setp_obj_id = ep_strs[0].split('..')[0]
376
+
377
+ # extract the schedules from the string
378
+ try:
379
+ heat_sched = schedule_dict[ep_strs[1]] if ep_strs[1] != '' else None
380
+ cool_sched = schedule_dict[ep_strs[3]] if ep_strs[3] != '' else None
381
+ except KeyError as e:
382
+ raise ValueError('Failed to find {} in the schedule_dict.'.format(e))
383
+
384
+ # return the object and the zone id for the object
385
+ setpoint = cls(setp_obj_id, heat_sched, cool_sched)
386
+ return setpoint
387
+
388
+ @classmethod
389
+ def from_dict(cls, data):
390
+ """Create a Setpoint object from a dictionary.
391
+
392
+ Note that the dictionary must be a non-abridged version for this classmethod
393
+ to work.
394
+
395
+ Args:
396
+ data: A Setpoint dictionary in following the format below.
397
+
398
+ .. code-block:: python
399
+
400
+ {
401
+ "type": 'Setpoint',
402
+ "identifier": 'Hospital_Patient_Room_Setpoint_210_230',
403
+ "display_name": 'Patient Room Setpoint',
404
+ "heating_schedule": {}, # ScheduleRuleset/FixedInterval dictionary
405
+ "cooling_schedule": {}, # ScheduleRuleset/FixedInterval dictionary
406
+ "humidifying_schedule": {}, # ScheduleRuleset/FixedInterval dictionary
407
+ "dehumidifying_schedule": {}, # ScheduleRuleset/FixedInterval dictionary
408
+ "setpoint_cutout_difference": 0.5 # number for cutout difference
409
+ }
410
+ """
411
+ assert data['type'] == 'Setpoint', \
412
+ 'Expected Setpoint dictionary. Got {}.'.format(data['type'])
413
+ heat_sched = cls._get_schedule_from_dict(data['heating_schedule'])
414
+ cool_sched = cls._get_schedule_from_dict(data['cooling_schedule'])
415
+ humid_sched = cls._get_schedule_from_dict(data['humidifying_schedule']) if \
416
+ 'humidifying_schedule' in data and \
417
+ data['humidifying_schedule'] is not None else None
418
+ dehumid_sched = cls._get_schedule_from_dict(data['dehumidifying_schedule']) if \
419
+ 'dehumidifying_schedule' in data and \
420
+ data['dehumidifying_schedule'] is not None else None
421
+ cut = data['setpoint_cutout_difference'] \
422
+ if 'setpoint_cutout_difference' in data and \
423
+ data['setpoint_cutout_difference'] is not None else 0
424
+ new_obj = cls(data['identifier'], heat_sched, cool_sched,
425
+ humid_sched, dehumid_sched, cut)
426
+ if 'display_name' in data and data['display_name'] is not None:
427
+ new_obj.display_name = data['display_name']
428
+ if 'user_data' in data and data['user_data'] is not None:
429
+ new_obj.user_data = data['user_data']
430
+ if 'properties' in data and data['properties'] is not None:
431
+ new_obj.properties._load_extension_attr_from_dict(data['properties'])
432
+ return new_obj
433
+
434
+ @classmethod
435
+ def from_dict_abridged(cls, data, schedule_dict):
436
+ """Create a Setpoint object from an abridged dictionary.
437
+
438
+ Args:
439
+ data: A SetpointAbridged dictionary in following the format below.
440
+ schedule_dict: A dictionary with schedule identifiers as keys and
441
+ honeybee schedule objects as values (either ScheduleRuleset or
442
+ ScheduleFixedInterval). These will be used to assign the schedules
443
+ to the Setpoint object.
444
+
445
+ .. code-block:: python
446
+
447
+ {
448
+ "type": 'SetpointAbridged',
449
+ "identifier": 'Hospital_Patient_Room_Setpoint_210_230',
450
+ "display_name": 'Patient Room Setpoint',
451
+ "heating_schedule": "Hospital Pat Room Heating", # Schedule identifier
452
+ "cooling_schedule": "Hospital Pat Room Cooling", # Schedule identifier
453
+ "humidifying_schedule": "Hospital Pat Room Humidify", # Schedule identifier
454
+ "dehumidifying_schedule": "Hospital Pat Room Dehumidify", # Sched identifier
455
+ "setpoint_cutout_difference": 0.5 # number for cutout difference
456
+ }
457
+ """
458
+ assert data['type'] == 'SetpointAbridged', \
459
+ 'Expected SetpointAbridged dictionary. Got {}.'.format(data['type'])
460
+ try:
461
+ heat_sched = schedule_dict[data['heating_schedule']]
462
+ cool_sched = schedule_dict[data['cooling_schedule']]
463
+ except KeyError as e:
464
+ raise ValueError('Failed to find {} in the schedule_dict.'.format(e))
465
+
466
+ humid_sched = None
467
+ dehumid_sched = None
468
+ if 'humidifying_schedule' in data and data['humidifying_schedule'] is not None:
469
+ try:
470
+ humid_sched = schedule_dict[data['humidifying_schedule']]
471
+ except KeyError as e:
472
+ raise ValueError('Failed to find {} in the schedule_dict.'.format(e))
473
+ if 'dehumidifying_schedule' in data and data['dehumidifying_schedule'] is not None:
474
+ try:
475
+ dehumid_sched = schedule_dict[data['dehumidifying_schedule']]
476
+ except KeyError as e:
477
+ raise ValueError('Failed to find {} in the schedule_dict.'.format(e))
478
+ cut = data['setpoint_cutout_difference'] \
479
+ if 'setpoint_cutout_difference' in data and \
480
+ data['setpoint_cutout_difference'] is not None else 0
481
+ new_obj = cls(data['identifier'], heat_sched, cool_sched,
482
+ humid_sched, dehumid_sched, cut)
483
+ if 'display_name' in data and data['display_name'] is not None:
484
+ new_obj.display_name = data['display_name']
485
+ if 'user_data' in data and data['user_data'] is not None:
486
+ new_obj.user_data = data['user_data']
487
+ if 'properties' in data and data['properties'] is not None:
488
+ new_obj.properties._load_extension_attr_from_dict(data['properties'])
489
+ return new_obj
490
+
491
+ def to_idf(self, zone_identifier):
492
+ """IDF string representation of Setpoint object's thermostat.
493
+
494
+ Note that this method only outputs a string for the HVACTemplate:Thermostat
495
+ object and, to write everything needed to describe the object into an IDF,
496
+ this object's schedules must also be written. If the humidifying or
497
+ dehumidifying schedules are not None, the to_idf_humidistat method should also
498
+ be used to write the humidistat.
499
+
500
+ Args:
501
+ zone_identifier: Text for the zone identifier that the Setpoint
502
+ object is assigned to.
503
+
504
+ .. code-block:: shell
505
+
506
+ ZoneControl:Thermostat,
507
+ AllControlledZones Thermostat, !- Name
508
+ AllControlledZones, !- Zone Name
509
+ Zone Control Type Sched, !- Control Type Schedule Name
510
+ ThermostatSetpoint:SingleCooling, !- Control 1 Object Type
511
+ CoolingSetPoint, !- Control 1 Name
512
+ ThermostatSetpoint:SingleHeating, !- Control 2 Object Type
513
+ HeatingSetpoint, !- Control 2 Name
514
+ ThermostatSetpoint:DualSetpoint, !- Control 3 Object Type
515
+ DualSetPoint; !- Control 3 Name
516
+ """
517
+ if self.setpoint_cutout_difference == 0:
518
+ values = ('{}..{}'.format(self.identifier, zone_identifier),
519
+ self.heating_schedule.identifier, '',
520
+ self.cooling_schedule.identifier, '')
521
+ comments = ('name', 'heating setpoint schedule', 'heating setpoint {C}',
522
+ 'cooling setpoint schedule', 'cooling setpoint {C}')
523
+ return generate_idf_string('HVACTemplate:Thermostat', values, comments)
524
+ else: # write out detailed objects for the thermostat
525
+ # create the detailed thermostat object
526
+ t_stat_vals = values = (
527
+ '{}..{}'.format(self.identifier, zone_identifier),
528
+ self.heating_schedule.identifier, self.cooling_schedule.identifier)
529
+ ts_com = ('name', 'heating setpoint schedule', 'cooling setpoint schedule')
530
+ t_stat = generate_idf_string(
531
+ 'ThermostatSetpoint:DualSetpoint', t_stat_vals, ts_com)
532
+ # create the schedule to indicate heating/cooling is always active
533
+ sch_tl_id = '{} Any Number'.format(zone_identifier)
534
+ sch_tl = generate_idf_string('ScheduleTypeLimits', (sch_tl_id,), ('name',))
535
+ sch_id = '{}-Always 4'.format(zone_identifier)
536
+ sch_vals = (sch_id, sch_tl_id, 'Through: 12/31', 'For: AllDays',
537
+ 'Until: 24:00', '4')
538
+ sch_com = ('name', 'type limits', '', '', '', '')
539
+ sch = generate_idf_string('Schedule:Compact', sch_vals, sch_com)
540
+ # create the zone control object
541
+ ctrl_vals = values = (
542
+ '{} Thermostat'.format(zone_identifier), zone_identifier, sch_id,
543
+ 'ThermostatSetpoint:DualSetpoint',
544
+ '{}..{}'.format(self.identifier, zone_identifier),
545
+ '', '', '', '', '', '', self.setpoint_cutout_difference)
546
+ ctrl_com = ('name', 'zone name', 'control type schedule',
547
+ 'control object type', 'control name', '', '', '', '', '', '',
548
+ 'temperature difference between cutout and setpoint')
549
+ z_ctrl = generate_idf_string('ZoneControl:Thermostat', ctrl_vals, ctrl_com)
550
+ return '\n\n'.join((t_stat, sch_tl, sch, z_ctrl))
551
+
552
+ def to_idf_humidistat(self, zone_identifier):
553
+ """IDF string representation of Setpoint object's humidistat.
554
+
555
+ Note that this method only outputs strings for the ZoneControl:Humidistat
556
+ and, to write everything needed to describe the object into an IDF, this
557
+ object's schedules must also be written.
558
+
559
+ Also note that this method will return None if no humidity setpoint schedules
560
+ have been assigned.
561
+
562
+ Args:
563
+ zone_identifier: Text for the zone identifier that the Setpoint
564
+ object is assigned to.
565
+
566
+ .. code-block:: shell
567
+
568
+ ZoneControl:Humidistat,
569
+ Zone 2 Humidistat, !- Humidistat Name
570
+ EAST ZONE, !- Zone Name
571
+ Min Rel Hum Set Sch, !- Humidifying Relative Humidity Setpoint SCHEDULE Name
572
+ Max Rel Hum Set Sch; !- Dehumidifying Relative Humidity Setpoint SCHEDULE Name
573
+ """
574
+ if self.humidifying_schedule is not None:
575
+ values = ('{}_{}'.format(self.identifier, zone_identifier), zone_identifier,
576
+ self.humidifying_schedule.identifier,
577
+ self.dehumidifying_schedule.identifier)
578
+ comments = ('name', 'zone name', 'humidifying setpoint schedule',
579
+ 'dehumidifying setpoint schedule')
580
+ return generate_idf_string('ZoneControl:Humidistat', values, comments)
581
+ return None
582
+
583
+ def to_dict(self, abridged=False):
584
+ """Setpoint dictionary representation.
585
+
586
+ Args:
587
+ abridged: Boolean to note whether the full dictionary describing the
588
+ object should be returned (False) or just an abridged version (True),
589
+ which only specifies the identifiers of schedules. Default: False.
590
+ """
591
+ base = {'type': 'Setpoint'} if not abridged else {'type': 'SetpointAbridged'}
592
+ base['identifier'] = self.identifier
593
+ if not abridged:
594
+ base['heating_schedule'] = self.heating_schedule.to_dict()
595
+ base['cooling_schedule'] = self.cooling_schedule.to_dict()
596
+ if self.humidifying_schedule is not None:
597
+ base['humidifying_schedule'] = self.humidifying_schedule.to_dict()
598
+ base['dehumidifying_schedule'] = self.dehumidifying_schedule.to_dict()
599
+ else:
600
+ base['heating_schedule'] = self.heating_schedule.identifier
601
+ base['cooling_schedule'] = self.cooling_schedule.identifier
602
+ if self.humidifying_schedule is not None:
603
+ base['humidifying_schedule'] = self.humidifying_schedule.identifier
604
+ base['dehumidifying_schedule'] = self.dehumidifying_schedule.identifier
605
+ if self.setpoint_cutout_difference != 0:
606
+ base['setpoint_cutout_difference'] = self.setpoint_cutout_difference
607
+ if self._display_name is not None:
608
+ base['display_name'] = self.display_name
609
+ if self._user_data is not None:
610
+ base['user_data'] = self.user_data
611
+ prop_dict = self.properties.to_dict()
612
+ if prop_dict is not None:
613
+ base['properties'] = prop_dict
614
+ return base
615
+
616
+ @staticmethod
617
+ def average(identifier, setpoints, weights=None, timestep_resolution=1):
618
+ """Get a Setpoint object that's an average between other Setpoints.
619
+
620
+ Args:
621
+ identifier: Text string for a unique ID for the new averaged Setpoint.
622
+ Must be < 100 characters and not contain any EnergyPlus special
623
+ characters. This will be used to identify the object across a model
624
+ and in the exported IDF.
625
+ setpoints: A list of Setpoint objects that will be averaged
626
+ together to make a new Setpoint.
627
+ weights: An optional list of fractional numbers with the same length
628
+ as the input setpoints. These will be used to weight each of the
629
+ Setpoint objects in the resulting average. Note that, if the sum of
630
+ the weights is less than 1, the unaccounted fraction will be assumed
631
+ to be at the weighted average setpoints of the other objects.
632
+ If None, the objects will be weighted equally. Default: None.
633
+ timestep_resolution: An optional integer for the timestep resolution
634
+ at which the schedules will be averaged. Any schedule details
635
+ smaller than this timestep will be lost in the averaging process.
636
+ Default: 1.
637
+
638
+ Returns:
639
+ A Setpoint object that represents the (weighted) average between
640
+ the input setpoints.
641
+ """
642
+ weights, u_weights = Setpoint._check_avg_weights(setpoints, weights, 'Setpoint')
643
+
644
+ # calculate the average thermostat schedules
645
+ heat_sched = Setpoint._average_schedule(
646
+ '{}_HtgSetp Schedule'.format(identifier),
647
+ [setp.heating_schedule for setp in setpoints], u_weights, timestep_resolution)
648
+ cool_sched = Setpoint._average_schedule(
649
+ '{}_ClgSetp Schedule'.format(identifier),
650
+ [setp.cooling_schedule for setp in setpoints], u_weights, timestep_resolution)
651
+
652
+ # calculate the average humidistat schedules
653
+ humid_scheds = [sp.humidifying_schedule for sp in setpoints]
654
+ if all(val is None for val in humid_scheds):
655
+ humid_sched = None
656
+ dehumid_sched = None
657
+ else:
658
+ dehumid_scheds = [sp.dehumidifying_schedule for sp in setpoints]
659
+ humid_sch_id = '{}_Humid Schedule'.format(identifier)
660
+ dehumid_sch_id = '{}_Dehumid Schedule'.format(identifier)
661
+ for i, sch in enumerate(humid_scheds):
662
+ if sch is None:
663
+ humid_scheds[i] = Setpoint._humidifying_schedule_no_limit
664
+ dehumid_scheds[i] = Setpoint._dehumidifying_schedule_no_limit
665
+ humid_sched = Setpoint._average_schedule(
666
+ humid_sch_id, humid_scheds, u_weights, timestep_resolution)
667
+ dehumid_sched = Setpoint._average_schedule(
668
+ dehumid_sch_id, dehumid_scheds, u_weights, timestep_resolution)
669
+
670
+ # calculate the average setpoint_cutout_difference
671
+ cuts = [sp.setpoint_cutout_difference for sp in setpoints]
672
+ sp_cut = sum(cuts) / len(cuts)
673
+
674
+ # return the averaged object
675
+ return Setpoint(identifier, heat_sched, cool_sched,
676
+ humid_sched, dehumid_sched, sp_cut)
677
+
678
+ @staticmethod
679
+ def strictest(identifier, setpoints, timestep_resolution=1):
680
+ """Get a Setpoint object that represents the strictest between several Setpoints.
681
+
682
+ For each timestep of the heating setpoint schedule, the highest setpoint
683
+ will govern the result. For each timestep of the cooling setpoint, the
684
+ lowest setpoint will govern. Humidifying and dehumidifying schedules
685
+ will similarly be adjusted. The lowest setpoint_cutout_difference will
686
+ govern because a smaller throttling range tends to be associated
687
+ with tighter/stricter setpoint controls. Also, a lower cutout difference
688
+ helps avoid putting the heating and cooling setpoints in conflict with
689
+ one another when taking the strictest values.
690
+
691
+ This method is useful when trying to resolve a set of rooms with different
692
+ setpoint criteria that are placed within the same thermal zone.
693
+
694
+ Args:
695
+ identifier: Text string for a unique ID for the new strictest Setpoint.
696
+ Must be < 100 characters and not contain any EnergyPlus special
697
+ characters. This will be used to identify the object across a model
698
+ and in the exported IDF.
699
+ setpoints: A list of Setpoint objects that will be evaluated into a
700
+ new Setpoint that represents the strictest across the input.
701
+ timestep_resolution: An optional integer for the timestep resolution
702
+ at which the schedules will be resolved. Any schedule details smaller
703
+ than this timestep will be lost in the process. (Default: 1).
704
+
705
+ Returns:
706
+ A Setpoint object that represents the strictest values between
707
+ the input setpoints.
708
+ """
709
+ # calculate the strictest thermostat schedules
710
+ heat_sched = Setpoint._max_schedule(
711
+ '{}_HtgSetp Schedule'.format(identifier),
712
+ [setp.heating_schedule for setp in setpoints], timestep_resolution)
713
+ cool_sched = Setpoint._min_schedule(
714
+ '{}_ClgSetp Schedule'.format(identifier),
715
+ [setp.cooling_schedule for setp in setpoints], timestep_resolution)
716
+
717
+ # calculate the strictest humidistat schedules
718
+ humid_scheds = [sp.humidifying_schedule for sp in setpoints]
719
+ if all(val is None for val in humid_scheds):
720
+ humid_sched = None
721
+ dehumid_sched = None
722
+ else:
723
+ dehumid_scheds = [sp.dehumidifying_schedule for sp in setpoints]
724
+ humid_sch_id = '{}_Humid Schedule'.format(identifier)
725
+ dehumid_sch_id = '{}_Dehumid Schedule'.format(identifier)
726
+ for i, sch in enumerate(humid_scheds):
727
+ if sch is None:
728
+ humid_scheds[i] = Setpoint._humidifying_schedule_no_limit
729
+ dehumid_scheds[i] = Setpoint._dehumidifying_schedule_no_limit
730
+ humid_sched = Setpoint._max_schedule(
731
+ humid_sch_id, humid_scheds, timestep_resolution)
732
+ dehumid_sched = Setpoint._min_schedule(
733
+ dehumid_sch_id, dehumid_scheds, timestep_resolution)
734
+
735
+ # calculate the minimum setpoint_cutout_difference
736
+ sp_cut = min(sp.setpoint_cutout_difference for sp in setpoints)
737
+
738
+ # return the averaged object
739
+ return Setpoint(identifier, heat_sched, cool_sched,
740
+ humid_sched, dehumid_sched, sp_cut)
741
+
742
+ def _check_temperature_schedule_type(self, schedule, obj_name=''):
743
+ """Check that the type limit of an input schedule is temperature."""
744
+ assert isinstance(schedule, (ScheduleRuleset, ScheduleFixedInterval)), \
745
+ 'Expected ScheduleRuleset or ScheduleFixedInterval for {} ' \
746
+ 'schedule. Got {}.'.format(obj_name, type(schedule))
747
+ if schedule.schedule_type_limit is not None:
748
+ assert schedule.schedule_type_limit.unit == 'C', '{} schedule ' \
749
+ 'should be in Temperature units. Got a schedule of unit_type ' \
750
+ '{}.'.format(obj_name, schedule.schedule_type_limit.unit_type)
751
+
752
+ def _check_humidity_schedule_type(self, schedule, obj_name=''):
753
+ """Check that the type limit of an input schedule is percent."""
754
+ assert isinstance(schedule, (ScheduleRuleset, ScheduleFixedInterval)), \
755
+ 'Expected ScheduleRuleset or ScheduleFixedInterval for {} ' \
756
+ 'schedule. Got {}.'.format(obj_name, type(schedule))
757
+ if schedule.schedule_type_limit is not None:
758
+ t_lim = schedule.schedule_type_limit
759
+ assert t_lim.unit == '%', '{} schedule should be in Percent units. ' \
760
+ 'Got a schedule of unit_type {}.'.format(obj_name, t_lim.unit_type)
761
+ assert t_lim.lower_limit == 0, '{} schedule should have either no type ' \
762
+ 'limit or a lower limit of 0. Got a schedule type with lower limit ' \
763
+ '[{}].'.format(obj_name, t_lim.lower_limit)
764
+ assert t_lim.upper_limit == 100, '{} schedule should have either no type ' \
765
+ 'limit or an upper limit of 1. Got a schedule type with upper limit ' \
766
+ '[{}].'.format(obj_name, t_lim.upper_limit)
767
+
768
+ def _min_schedule_value(self, schedule):
769
+ """Extract the minimum value from a schedule."""
770
+ try: # ScheduleRuleset
771
+ vals = []
772
+ for sch in schedule.typical_day_schedules:
773
+ vals.extend(sch.values)
774
+ return min(vals)
775
+ except AttributeError: # ScheduleFixedInterval
776
+ return min(schedule.values)
777
+
778
+ def _max_schedule_value(self, schedule):
779
+ """Extract the maximum value from a schedule."""
780
+ try: # ScheduleRuleset
781
+ vals = []
782
+ for sch in schedule.typical_day_schedules:
783
+ vals.extend(sch.values)
784
+ return max(vals)
785
+ except AttributeError: # ScheduleFixedInterval
786
+ return max(schedule.values)
787
+
788
+ def __key(self):
789
+ """A tuple based on the object properties, useful for hashing."""
790
+ return (self.identifier, hash(self.heating_schedule), hash(self.cooling_schedule),
791
+ hash(self.humidifying_schedule), hash(self.dehumidifying_schedule),
792
+ self.setpoint_cutout_difference)
793
+
794
+ def __hash__(self):
795
+ return hash(self.__key())
796
+
797
+ def __eq__(self, other):
798
+ return isinstance(other, Setpoint) and self.__key() == other.__key()
799
+
800
+ def __ne__(self, other):
801
+ return not self.__eq__(other)
802
+
803
+ def __copy__(self):
804
+ new_obj = Setpoint(
805
+ self.identifier, self.heating_schedule, self.cooling_schedule,
806
+ self.humidifying_schedule, self.dehumidifying_schedule)
807
+ new_obj._setpoint_cutout_difference = self._setpoint_cutout_difference
808
+ new_obj._display_name = self._display_name
809
+ new_obj._user_data = None if self._user_data is None else self._user_data.copy()
810
+ new_obj._properties._duplicate_extension_attr(self._properties)
811
+ return new_obj
812
+
813
+ def __repr__(self):
814
+ return 'Setpoint: {} [heating: {}C] [cooling: {}C]'.format(
815
+ self.display_name, round(self.heating_setpoint, 1),
816
+ round(self.cooling_setpoint, 1))