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,1012 @@
1
+ # coding=utf-8
2
+ """Annual schedule defined by a list of values at a fixed interval or timestep."""
3
+ from __future__ import division
4
+
5
+ import os
6
+ import re
7
+ try:
8
+ from collections.abc import Iterable # python < 3.7
9
+ except ImportError:
10
+ from collections import Iterable # python >= 3.8
11
+ try:
12
+ from itertools import izip as zip # python 2
13
+ except ImportError:
14
+ xrange = range # python 3
15
+
16
+ from ladybug.datacollection import HourlyContinuousCollection
17
+ from ladybug.header import Header
18
+ from ladybug.analysisperiod import AnalysisPeriod
19
+ from ladybug.dt import Date, DateTime
20
+ from ladybug.datatype.generic import GenericType
21
+ from ladybug.futil import write_to_file, csv_to_matrix
22
+
23
+ from honeybee._lockable import lockable
24
+ from honeybee.typing import valid_ep_string, float_in_range, int_in_range, \
25
+ tuple_with_length
26
+
27
+ from .typelimit import ScheduleTypeLimit
28
+ from ..reader import parse_idf_string, clean_idf_file_contents
29
+ from ..writer import generate_idf_string
30
+ from ..properties.extension import ScheduleFixedIntervalProperties
31
+
32
+
33
+ @lockable
34
+ class ScheduleFixedInterval(object):
35
+ """An annual schedule defined by a list of values at a fixed interval or timestep.
36
+
37
+ Args:
38
+ identifier: Text string for a unique Schedule ID. Must be < 100 characters
39
+ and not contain any EnergyPlus special characters. This will be used to
40
+ identify the object across a model and in the exported IDF.
41
+ values: A list of values occurring at a fixed interval over the simulation.
42
+ Typically, this should be a list of 8760 values for each hour of the
43
+ year but it can be a shorter list if you don't plan on using it in
44
+ an annual simulation. In this case, the start_date should probably be
45
+ different than the default 1 Jan (it should instead be the start date
46
+ of your simulation). This list can also have a length much greater
47
+ than 8760 if a timestep greater than 1 is used.
48
+ schedule_type_limit: A ScheduleTypeLimit object that will be used to
49
+ validate schedule values against upper/lower limits and assign units
50
+ to the schedule values. If None, no validation will occur.
51
+ timestep: An integer for the number of steps per hour that the input
52
+ values correspond to. For example, if each value represents 30
53
+ minutes, the timestep is 2. For 15 minutes, it is 4. Default is 1,
54
+ meaning each value represents a single hour. Must be one of the
55
+ following: (1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30, 60).
56
+ start_date: A ladybug Date object to note when the input values begin
57
+ to take effect. Default is 1 Jan for a non-leap year. Note that this
58
+ default usually should not be changed unless you plan to run a
59
+ simulation that is much shorter than a year and/or you plan to run
60
+ the simulation for a leap year.
61
+ placeholder_value: A value that will be used for all times not covered
62
+ by the input values. Typically, your simulation should not need to
63
+ use this value if the input values completely cover the simulation
64
+ period. However, a default value may still be necessary for EnergyPlus
65
+ to run. Default: 0.
66
+ interpolate: Boolean to note whether values in between intervals should be
67
+ linearly interpolated or whether successive values should take effect
68
+ immediately upon the beginning time corresponding to them. Default: False
69
+
70
+ Properties:
71
+ * identifier
72
+ * display_name
73
+ * values
74
+ * schedule_type_limit
75
+ * timestep
76
+ * start_date
77
+ * placeholder_value
78
+ * interpolate
79
+ * end_date_time
80
+ * is_leap_year
81
+ * is_constant
82
+ * data_collection
83
+ * user_data
84
+ """
85
+ __slots__ = ('_identifier', '_display_name', '_values', '_schedule_type_limit',
86
+ '_start_date', '_placeholder_value', '_timestep', '_interpolate',
87
+ '_locked', '_properties', '_user_data')
88
+ _schedule_file_comments = \
89
+ ('schedule name', 'schedule type limits', 'file name', 'column number',
90
+ 'rows to skip', 'number of hours of data', 'column separator',
91
+ 'interpolate to timestep', 'minutes per item')
92
+ VALIDTIMESTEPS = (1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30, 60)
93
+
94
+ def __init__(self, identifier, values, schedule_type_limit=None, timestep=1,
95
+ start_date=Date(1, 1), placeholder_value=0, interpolate=False):
96
+ """Initialize Schedule FixedInterval."""
97
+ self._locked = False # unlocked by default
98
+
99
+ # set all of the properties that impact how many values can be assigned
100
+ self._timestep = int_in_range(timestep, 1, 60, 'schedule timestep')
101
+ assert self._timestep in self.VALIDTIMESTEPS, 'ScheduleFixedInterval timestep ' \
102
+ '"{}" is invalid. Must be one of the following:\n{}'.format(
103
+ timestep, self.VALIDTIMESTEPS)
104
+ start_date = Date(1, 1) if start_date is None else start_date
105
+ assert isinstance(start_date, Date), 'Expected ladybug Date for ' \
106
+ 'ScheduleFixedInterval start_date. Got {}.'.format(type(start_date))
107
+ self._start_date = start_date
108
+
109
+ # set the values and all properties that can be re-set
110
+ self.identifier = identifier
111
+ self._display_name = None
112
+ self.values = values
113
+ self.schedule_type_limit = schedule_type_limit
114
+ self.placeholder_value = placeholder_value
115
+ self.interpolate = interpolate
116
+
117
+ # initialize properties for extensions and user data
118
+ self._properties = ScheduleFixedIntervalProperties(self)
119
+ self._user_data = None
120
+
121
+ @property
122
+ def identifier(self):
123
+ """Get or set the text string for unique schedule identifier."""
124
+ return self._identifier
125
+
126
+ @identifier.setter
127
+ def identifier(self, identifier):
128
+ self._identifier = valid_ep_string(
129
+ identifier, 'schedule fixed interval identifier')
130
+
131
+ @property
132
+ def display_name(self):
133
+ """Get or set a string for the object name without any character restrictions.
134
+
135
+ If not set, this will be equal to the identifier.
136
+ """
137
+ if self._display_name is None:
138
+ return self._identifier
139
+ return self._display_name
140
+
141
+ @display_name.setter
142
+ def display_name(self, value):
143
+ if value is not None:
144
+ try:
145
+ value = str(value)
146
+ except UnicodeEncodeError: # Python 2 machine lacking the character set
147
+ pass # keep it as unicode
148
+ self._display_name = value
149
+
150
+ @property
151
+ def values(self):
152
+ """Get or set the schedule's numerical values, which occur at a fixed interval.
153
+ """
154
+ return self._values
155
+
156
+ @values.setter
157
+ def values(self, values):
158
+ self._values = self._check_values(values)
159
+
160
+ @property
161
+ def schedule_type_limit(self):
162
+ """Get or set a ScheduleTypeLimit object used to assign units to schedule values.
163
+ """
164
+ return self._schedule_type_limit
165
+
166
+ @schedule_type_limit.setter
167
+ def schedule_type_limit(self, schedule_type):
168
+ if schedule_type is not None:
169
+ assert isinstance(schedule_type, ScheduleTypeLimit), 'Expected ' \
170
+ 'ScheduleTypeLimit for ScheduleRuleset schedule_type_limit. ' \
171
+ 'Got {}.'.format(type(schedule_type))
172
+ self._schedule_type_limit = schedule_type
173
+
174
+ @property
175
+ def placeholder_value(self):
176
+ """Get or set the value to be used for all times not covered by the input values.
177
+ """
178
+ return self._placeholder_value
179
+
180
+ @placeholder_value.setter
181
+ def placeholder_value(self, value):
182
+ self._placeholder_value = float_in_range(
183
+ value, input_name='schedule fixed interval placeholder_value')
184
+
185
+ @property
186
+ def interpolate(self):
187
+ """Get or set a boolean noting whether values should be interpolated."""
188
+ return self._interpolate
189
+
190
+ @interpolate.setter
191
+ def interpolate(self, interpolate):
192
+ self._interpolate = bool(interpolate)
193
+
194
+ @property
195
+ def timestep(self):
196
+ """Get the integer for the schedule's number of steps per hour."""
197
+ return self._timestep
198
+
199
+ @property
200
+ def start_date(self):
201
+ """Get the ladybug Date object noting when the the input values take effect."""
202
+ return self._start_date
203
+
204
+ @property
205
+ def end_date_time(self):
206
+ """Get a ladybug DateTime object for the end time of the schedule's values."""
207
+ num_hoys = (len(self._values) - 1) / self.timestep
208
+ end_hoy = (self.start_date.doy - 1) * 24 + num_hoys
209
+ if not self.is_leap_year:
210
+ end_dt = DateTime.from_hoy(end_hoy) if end_hoy < 8760 else \
211
+ DateTime.from_hoy(end_hoy - 8760)
212
+ else:
213
+ end_dt = DateTime.from_hoy(end_hoy, True) if end_hoy < 8784 else \
214
+ DateTime.from_hoy(end_hoy - 8760, True)
215
+ return end_dt
216
+
217
+ @property
218
+ def is_leap_year(self):
219
+ """Get a boolean noting whether the schedule is over a leap year.
220
+
221
+ Note that this property originates from the leap_year property on the
222
+ input start_date.
223
+ """
224
+ return self._start_date.leap_year
225
+
226
+ @property
227
+ def is_constant(self):
228
+ """Boolean noting whether the schedule is representable with a single value."""
229
+ val_1 = self._values[0]
230
+ return all(element == val_1 for element in self._values)
231
+
232
+ @property
233
+ def data_collection(self):
234
+ """DataCollection of schedule values at this schedule's start_date and timestep.
235
+ """
236
+ end_dt = self.end_date_time
237
+ a_period = AnalysisPeriod(self.start_date.month, self.start_date.day, 0,
238
+ end_dt.month, end_dt.day, end_dt.hour, self.timestep,
239
+ self.is_leap_year)
240
+ data_type, unit = self._get_lb_data_type_and_unit()
241
+ header = Header(
242
+ data_type, unit, a_period, metadata={'schedule': self.identifier})
243
+ return HourlyContinuousCollection(header, self._values)
244
+
245
+ @property
246
+ def user_data(self):
247
+ """Get or set an optional dictionary for additional meta data for this object.
248
+
249
+ This will be None until it has been set. All keys and values of this
250
+ dictionary should be of a standard Python type to ensure correct
251
+ serialization of the object to/from JSON (eg. str, float, int, list, dict)
252
+ """
253
+ return self._user_data
254
+
255
+ @user_data.setter
256
+ def user_data(self, value):
257
+ if value is not None:
258
+ assert isinstance(value, dict), 'Expected dictionary for honeybee_energy' \
259
+ 'object user_data. Got {}.'.format(type(value))
260
+ self._user_data = value
261
+
262
+ @property
263
+ def properties(self):
264
+ """Get properties for extensions."""
265
+ return self._properties
266
+
267
+ def values_at_timestep(
268
+ self, timestep=1, start_date=None, end_date=None):
269
+ """Get a list of sequential schedule values over the year at a given timestep.
270
+
271
+ Note that there are two possible ways that these values can be mapped to
272
+ corresponding times:
273
+
274
+ * The EnergyPlus interpretation that uses "time until"
275
+ * The Ladybug Tools interpretation that uses "time of beginning"
276
+
277
+ The EnergyPlus interpretation should be used when aligning the schedule
278
+ with EnergyPlus results while the Ladybug Tools interpretation should be
279
+ used when aligning the schedule with ladybug DataCollections or other
280
+ ladybug objects. See the ScheduleDay.values_at_timestep method
281
+ documentation for a complete description of these two interpretations.
282
+
283
+ Args:
284
+ timestep: An integer for the number of steps per hour at which to return
285
+ the resulting values.
286
+ start_date: An optional ladybug Date object for when to start the list
287
+ of values. Default: 1 Jan with a leap year equal to self.start_date.
288
+ end_date: An optional ladybug Date object for when to end the list
289
+ of values. Default: 31 Dec with a leap year equal to self.start_date.
290
+ """
291
+ # ensure that the input start_date and end_date are valid
292
+ if start_date is None:
293
+ start_date = Date(1, 1, self.is_leap_year)
294
+ else:
295
+ if start_date.leap_year is not self.is_leap_year:
296
+ start_date = Date(start_date.month, start_date.day, self.is_leap_year)
297
+ if end_date is None:
298
+ end_date = Date(12, 31, self.is_leap_year)
299
+ else:
300
+ if end_date.leap_year is not self.is_leap_year:
301
+ end_date = Date(end_date.month, end_date.day, self.is_leap_year)
302
+ assert start_date <= end_date, 'ScheduleFixedInterval values_at_timestep()' \
303
+ 'start_date must come before end_date. {} comes after {}.'.format(
304
+ start_date, end_date)
305
+
306
+ # convert the schedule's values to the desired timestep
307
+ timestep = int_in_range(timestep, 1, 60, 'schedule timestep')
308
+ assert timestep in self.VALIDTIMESTEPS, 'ScheduleFixedInterval timestep ' \
309
+ '"{}" is invalid. Must be one of the following:\n{}'.format(
310
+ timestep, self.VALIDTIMESTEPS)
311
+ if timestep == self.timestep:
312
+ vals_at_step = list(self._values)
313
+ elif timestep < self.timestep:
314
+ assert self.timestep % timestep == 0, \
315
+ 'Schedule timestep({}) must be evenly divisible by target timestep({})' \
316
+ .format(self.timestep, timestep)
317
+ vals_at_step = []
318
+ ind = 0
319
+ step_ratio = self.timestep / timestep
320
+ for _ in xrange(int(len(self._values) / step_ratio)):
321
+ vals_at_step.append(self._values[int(ind)])
322
+ ind += step_ratio
323
+ else:
324
+ assert timestep % self.timestep == 0, \
325
+ 'Target timestep({}) must be evenly divisible by schedule timestep({})' \
326
+ .format(timestep, self.timestep)
327
+ vals_at_step = []
328
+ if self.interpolate:
329
+ data_len = len(self._values)
330
+ for d in xrange(data_len):
331
+ for _v in self._xxrange(self[d], self[(d + 1) % data_len], timestep):
332
+ vals_at_step.append(_v)
333
+ else:
334
+ n_step = int(timestep / self.timestep)
335
+ for val in self._values:
336
+ for _ in xrange(n_step):
337
+ vals_at_step.append(val)
338
+
339
+ # build up the full list of values accounting for start and end dates
340
+ end_dt = self.end_date_time
341
+ if self.start_date.doy <= end_dt.doy:
342
+ start_filler = []
343
+ end_filler = []
344
+ if start_date < self.start_date:
345
+ num_vals = int((self.start_date.doy - start_date.doy) * 24 * timestep)
346
+ start_filler = [self.placeholder_value for i in xrange(num_vals)]
347
+ elif start_date > self.start_date:
348
+ start_i = int((start_date.doy - self.start_date.doy) * 24 * timestep)
349
+ vals_at_step = vals_at_step[start_i:]
350
+ if ((end_dt.int_hoy + 1) / 24) < end_date.doy:
351
+ num_vals = int((end_date.doy * 24 * timestep) - 1 - (
352
+ end_dt.hoy * timestep))
353
+ end_filler = [self.placeholder_value for i in xrange(num_vals)]
354
+ elif ((end_dt.int_hoy + 1) / 24) > end_date.doy:
355
+ end_diff = int((end_dt.hoy * timestep) - (end_date.doy * 24 * timestep))
356
+ end_i = len(vals_at_step) - end_diff - 1
357
+ vals_at_step = vals_at_step[:end_i]
358
+ return start_filler + vals_at_step + end_filler
359
+ else:
360
+ n_dpy = 365 if not self.is_leap_year else 366
361
+ start_yr_i = int((n_dpy - self.start_date.doy + 1) * 24 * timestep)
362
+ n_mid = (8760 * timestep) - len(vals_at_step)
363
+ end_vals = vals_at_step[:start_yr_i]
364
+ start_vals = vals_at_step[start_yr_i:]
365
+ mid_vals = [self.placeholder_value for i in xrange(n_mid)]
366
+ all_vals = start_vals + mid_vals + end_vals
367
+ start_i = (start_date.doy - 1) * 24 * timestep
368
+ end_i = end_date.doy * 24 * timestep
369
+ return all_vals[start_i:end_i]
370
+
371
+ def data_collection_at_timestep(
372
+ self, timestep=1, start_date=Date(1, 1), end_date=Date(12, 31)):
373
+ """Get a ladybug DataCollection representing this schedule at a given timestep.
374
+
375
+ Note that ladybug DataCollections always follow the "Ladybug Tools
376
+ Interpretation" of date time values as noted in the
377
+ ScheduleDay.values_at_timestep documentation.
378
+
379
+ Args:
380
+ timestep: An integer for the number of steps per hour at which to make
381
+ the resulting DataCollection.
382
+ start_date: An optional ladybug Date object for when to start the
383
+ DataCollection. Default: 1 Jan on a non-leap year.
384
+ end_date: An optional ladybug Date object for when to end the
385
+ DataCollection. Default: 31 Dec on a non-leap year.
386
+ """
387
+ a_period = AnalysisPeriod(start_date.month, start_date.day, 0,
388
+ end_date.month, end_date.day, 23, timestep,
389
+ self.is_leap_year)
390
+ data_type, unit = self._get_lb_data_type_and_unit()
391
+ header = Header(
392
+ data_type, unit, a_period, metadata={'schedule': self.identifier})
393
+ values = self.values_at_timestep(timestep, start_date, end_date)
394
+ return HourlyContinuousCollection(header, values)
395
+
396
+ def shift_by_step(self, step_count=1, timestep=1):
397
+ """Get a version of this object where the values are shifted.
398
+
399
+ This is useful when attempting to derive a set of diversified schedules
400
+ from a single average schedule.
401
+
402
+ Args:
403
+ step_count: An integer for the number of timesteps at which the schedule
404
+ will be shifted. Positive values indicate a shift of values forward
405
+ in time while negative values indicate a shift backwards in
406
+ time. (Default: 1).
407
+ timestep: An integer for the number of timesteps per hour at which the
408
+ shifting is occurring. This must be a value between 1 and 60, which
409
+ is evenly divisible by 60. 1 indicates that each step is an hour
410
+ while 60 indicates that each step is a minute. (Default: 1).
411
+ """
412
+ # figure out the number of values to be moved
413
+ val_count = step_count * int(self.timestep / timestep)
414
+ # shift by the number of values
415
+ if val_count >= 0:
416
+ new_values = self.values[-val_count:] + self.values[:-val_count]
417
+ else:
418
+ new_values = list(self.values)
419
+ for _ in range(abs(val_count)):
420
+ new_values.append(new_values.pop(0))
421
+ # return the shifted schedule
422
+ new_id = '{}_Shift_{}mins'.format(
423
+ self.identifier, int((60 / timestep) * step_count))
424
+ return ScheduleFixedInterval(
425
+ new_id, new_values, self.schedule_type_limit, self.timestep,
426
+ self.start_date, self.placeholder_value, self.interpolate)
427
+
428
+ @classmethod
429
+ def from_idf(cls, idf_string, type_idf_string=None):
430
+ """Create a ScheduleFixedInterval from an EnergyPlus IDF text strings.
431
+
432
+ Args:
433
+ idf_string: A text string fully describing an EnergyPlus
434
+ Schedule:File.
435
+ type_idf_string: An optional text string for the ScheduleTypeLimits.
436
+ If None, the resulting schedule will have no ScheduleTypeLimit.
437
+ """
438
+ # process the schedule inputs
439
+ sch_fields = parse_idf_string(idf_string, 'Schedule:File')
440
+ schedule_type = ScheduleTypeLimit.from_idf(type_idf_string) if type_idf_string \
441
+ is not None else None
442
+ timestep = 60 / int(sch_fields[8]) if sch_fields[8] != '' else 1
443
+ start_date = Date(1, 1, False) if sch_fields[5] == '8760' else Date(1, 1, True)
444
+ interpolate = False if sch_fields[7] == 'No' or sch_fields[7] == '' else True
445
+
446
+ # load the data from the CSV file referenced in the string
447
+ assert os.path.isfile(sch_fields[2]), \
448
+ 'CSV Schedule:File "{}" was not found on this system.'.format(sch_fields[2])
449
+ all_data = csv_to_matrix(sch_fields[2])
450
+ transposed_data = tuple(zip(*all_data))
451
+ csv_data = (float(x) for x in
452
+ transposed_data[int(sch_fields[3]) - 1][int(sch_fields[4]):])
453
+
454
+ return cls(sch_fields[0], csv_data, schedule_type, timestep, start_date,
455
+ 0, interpolate)
456
+
457
+ @classmethod
458
+ def from_dict(cls, data):
459
+ """Create a ScheduleFixedInterval from a dictionary.
460
+
461
+ Note that the dictionary must be a non-abridged version for this
462
+ classmethod to work.
463
+
464
+ Args:
465
+ data: ScheduleFixedInterval dictionary following the format below.
466
+
467
+ .. code-block:: python
468
+
469
+ {
470
+ "type": 'ScheduleFixedInterval',
471
+ "identifier": 'Awning_Transmittance_X45NF23U',
472
+ "display_name": 'Automated Awning Transmittance',
473
+ "values": [], # list of numbers for the values of the schedule
474
+ "schedule_type_limit": {}, # ScheduleTypeLimit dictionary representation
475
+ "timestep": 1, # Integer for the timestep of the schedule
476
+ "start_date": (1, 1), # Date dictionary representation
477
+ "placeholder_value": 0, # Number for the values out of range
478
+ "interpolate": False # Boolean noting whether to interpolate between values
479
+ }
480
+ """
481
+ assert data['type'] == 'ScheduleFixedInterval', \
482
+ 'Expected ScheduleFixedInterval. Got {}.'.format(data['type'])
483
+
484
+ sched_type = None
485
+ if 'schedule_type_limit' in data and data['schedule_type_limit'] is not None:
486
+ sched_type = ScheduleTypeLimit.from_dict(data['schedule_type_limit'])
487
+ timestep = 1
488
+ if 'timestep' in data and data['timestep'] is not None:
489
+ timestep = data['timestep']
490
+ start_date = Date(1, 1)
491
+ if 'start_date' in data and data['start_date'] is not None:
492
+ start_date = Date.from_array(data['start_date'])
493
+ placeholder_value = 0
494
+ if 'placeholder_value' in data and data['placeholder_value'] is not None:
495
+ placeholder_value = data['placeholder_value']
496
+ interpolate = False
497
+ if 'interpolate' in data and data['interpolate'] is not None:
498
+ interpolate = data['interpolate']
499
+
500
+ new_obj = cls(data['identifier'], data['values'], sched_type, timestep,
501
+ start_date, placeholder_value, interpolate)
502
+ if 'display_name' in data and data['display_name'] is not None:
503
+ new_obj.display_name = data['display_name']
504
+ if 'user_data' in data and data['user_data'] is not None:
505
+ new_obj.user_data = data['user_data']
506
+ if 'properties' in data and data['properties'] is not None:
507
+ new_obj.properties._load_extension_attr_from_dict(data['properties'])
508
+ return new_obj
509
+
510
+ @classmethod
511
+ def from_dict_abridged(cls, data, schedule_type_limits):
512
+ """Create a ScheduleFixedInterval from an abridged dictionary.
513
+
514
+ Args:
515
+ data: ScheduleFixedIntervalAbridged dictionary with format below.
516
+ schedule_type_limits: A dictionary with identifiers of schedule type limits
517
+ as keys and Python schedule type limit objects as values.
518
+
519
+ .. code-block:: python
520
+
521
+ {
522
+ "type": 'ScheduleFixedIntervalAbridged',
523
+ "identifier": 'Awning_Transmittance_X45NF23U',
524
+ "display_name": 'Automated Awning Transmittance',
525
+ "values": [], # list of numbers for the values of the schedule
526
+ "schedule_type_limit": "", # ScheduleTypeLimit identifier
527
+ "timestep": 1, # Integer for the timestep of the schedule
528
+ "start_date": (1, 1), # Date dictionary representation
529
+ "placeholder_value": 0, # Number for the values out of range
530
+ "interpolate": False # Boolean noting whether to interpolate between values
531
+ }
532
+ """
533
+ assert data['type'] == 'ScheduleFixedIntervalAbridged', \
534
+ 'Expected ScheduleFixedIntervalAbridged. Got {}.'.format(data['type'])
535
+
536
+ data = data.copy() # copy original dictionary so we don't edit it
537
+ typ_lim = None
538
+ if 'schedule_type_limit' in data:
539
+ typ_lim = data['schedule_type_limit']
540
+ data['schedule_type_limit'] = None
541
+ data['type'] = 'ScheduleFixedInterval'
542
+ schedule = cls.from_dict(data)
543
+ schedule.schedule_type_limit = schedule_type_limits[typ_lim] if \
544
+ typ_lim is not None else None
545
+ if 'display_name' in data and data['display_name'] is not None:
546
+ schedule.display_name = data['display_name']
547
+ if 'user_data' in data and data['user_data'] is not None:
548
+ schedule.user_data = data['user_data']
549
+ if 'properties' in data and data['properties'] is not None:
550
+ schedule.properties._load_extension_attr_from_dict(data['properties'])
551
+ return schedule
552
+
553
+ def to_idf(self, schedule_directory, include_datetimes=False):
554
+ """IDF string representation of the schedule.
555
+
556
+ Note that this method does both the production of the IDF string
557
+ representation of the Schedule:File as well as the actual writing of
558
+ the schedule to a CSV format that can be read in by EnergyPlus.
559
+
560
+ Args:
561
+ schedule_directory: [Required] Text string of a path to a folder on this
562
+ machine to which the CSV version of the file will be written.
563
+ include_datetimes: Boolean to note whether a column of datetime objects
564
+ should be written into the CSV alongside the data. Default is False,
565
+ which will keep the resulting CSV lighter in file size but you may
566
+ want to include such datetimes in order to verify that values align with
567
+ the expected timestep. Note that the included datetimes will follow the
568
+ EnergyPlus interpretation of aligning values to timesteps in which case
569
+ the timestep to which the value is matched means that the value was
570
+ utilized over all of the previous timestep.
571
+
572
+ Returns:
573
+ schedule_file --
574
+ Text string representation of the Schedule:File describing this schedule.
575
+
576
+ .. code-block:: shell
577
+
578
+ Schedule:File,
579
+ elecTDVfromCZ01res, !- Name
580
+ Any Number, !- ScheduleType
581
+ TDV_kBtu_CTZ01.csv, !- Name of File
582
+ 2, !- Column Number
583
+ 4, !- Rows to Skip at Top
584
+ 8760, !- Number of Hours of Data
585
+ Comma; !- Column Separator
586
+ """
587
+ # gather all of the data to be written into the CSV
588
+ sched_data = [str(val) for val in self.values_at_timestep(self.timestep)]
589
+ if include_datetimes:
590
+ sched_a_per = AnalysisPeriod(timestep=self.timestep,
591
+ is_leap_year=self.is_leap_year)
592
+ sched_data = ('{},{}'.format(dt, val) for dt, val in
593
+ zip(sched_a_per.datetimes, sched_data))
594
+ file_path = os.path.join(schedule_directory,
595
+ '{}.csv'.format(self.identifier.replace(' ', '_')))
596
+
597
+ # write the data into the file
598
+ write_to_file(file_path, ',\n'.join(sched_data), True)
599
+
600
+ # generate the IDF strings
601
+ shc_typ = self._schedule_type_limit.identifier if \
602
+ self._schedule_type_limit is not None else ''
603
+ col_num = 1 if not include_datetimes else 2
604
+ num_hrs = 8760 if not self.is_leap_year else 8784
605
+ interp = 'No' if not self.interpolate else 'Yes'
606
+ min_per_step = int(60 / self.timestep)
607
+ fields = (self.identifier, shc_typ, file_path, col_num, 0, num_hrs, 'Comma',
608
+ interp, min_per_step)
609
+ schedule_file = generate_idf_string('Schedule:File', fields,
610
+ self._schedule_file_comments)
611
+ return schedule_file
612
+
613
+ def to_idf_compact(self):
614
+ """IDF string representation of the schedule as a Schedule:Compact.
615
+
616
+ Schedule:Compact strings contain all of the schedule values and can be
617
+ written directly into IDF files. So they are sometimes preferable to
618
+ Schedule:Files objects when it's important that all simulation data be
619
+ represented in a single IDF file. However, such a representation of the
620
+ schedule often prevents the IDF from being read by programs such as the
621
+ IDFEditor and it can increase the overall size of the schedule in the
622
+ resulting files by an order of magnitude.
623
+
624
+ .. code-block shell
625
+
626
+ Schedule:Compact,
627
+ POFF, !- Name
628
+ Fraction, !- Schedule Type Limits Name
629
+ Through: 4/30,
630
+ For: AllDays,
631
+ Until: 24:00, 1.0,
632
+ Through: 12/31,
633
+ For: Weekdays,
634
+ Until: 7:00, .1,
635
+ Until: 17:00, 1.0,
636
+ Until: 24:00, .1,
637
+ For: Weekends Holidays,
638
+ Until: 24:00, .1,
639
+ For: AllOtherDays,
640
+ Until: 24:00, .1;
641
+ """
642
+ # initialize the list of IDF properties
643
+ shc_typ = self._schedule_type_limit.identifier if \
644
+ self._schedule_type_limit is not None else ''
645
+ fields = [self.identifier, shc_typ]
646
+
647
+ # loop through all datetimes of the schedule and append them.
648
+ sched_data = self.values_at_timestep(self.timestep)
649
+ datetimes = AnalysisPeriod(timestep=self.timestep,
650
+ is_leap_year=self.is_leap_year).datetimes
651
+ day = 0
652
+ for val, d_t in zip(sched_data, datetimes):
653
+ if d_t.day != day:
654
+ fields.append('Through: {}/{}'.format(d_t.month, d_t.day))
655
+ fields.append('For: AllDays')
656
+ day = d_t.day
657
+ hour = 0 if d_t.hour == 24 and d_t.minute != 0 else d_t.hour + 1
658
+ fields.append('Until: {}:{}'.format(hour, d_t.strftime('%M')))
659
+ fields.append(val)
660
+
661
+ # return the IDF string
662
+ return generate_idf_string('Schedule:Compact', fields)
663
+
664
+ def to_dict(self, abridged=False):
665
+ """ScheduleFixedInterval dictionary representation.
666
+
667
+ Args:
668
+ abridged: Boolean to note whether the full dictionary describing the
669
+ object should be returned (False) or just an abridged version (True),
670
+ which only specifies the identifier of the ScheduleTypeLimit.
671
+ Default: False.
672
+ """
673
+ # required properties
674
+ base = {'type': 'ScheduleFixedInterval'} if not \
675
+ abridged else {'type': 'ScheduleFixedIntervalAbridged'}
676
+ base['identifier'] = self.identifier
677
+ base['values'] = self.values
678
+
679
+ # optional properties
680
+ base['timestep'] = self.timestep
681
+ base['start_date'] = self.start_date.to_array()
682
+ base['placeholder_value'] = self.placeholder_value
683
+ base['interpolate'] = self.interpolate
684
+
685
+ # optional properties that can be abridged
686
+ if self._schedule_type_limit is not None:
687
+ if not abridged:
688
+ base['schedule_type_limit'] = self._schedule_type_limit.to_dict()
689
+ else:
690
+ base['schedule_type_limit'] = self._schedule_type_limit.identifier
691
+ if self._display_name is not None:
692
+ base['display_name'] = self.display_name
693
+ if self._user_data is not None:
694
+ base['user_data'] = self.user_data
695
+ prop_dict = self.properties.to_dict()
696
+ if prop_dict is not None:
697
+ base['properties'] = prop_dict
698
+ return base
699
+
700
+ def duplicate(self):
701
+ """Get a copy of this object."""
702
+ return self.__copy__()
703
+
704
+ @staticmethod
705
+ def to_idf_collective_csv(schedules, schedule_directory, file_name,
706
+ include_datetimes=False):
707
+ """Write several ScheduleFixedIntervals into the same CSV file and get IDF text.
708
+
709
+ This method is useful when several ScheduleFixedInterval objects are serving a
710
+ similar purpose and the data would be more easily managed if they all were in
711
+ the same file.
712
+
713
+ Args:
714
+ schedules: A list of ScheduleFixedInterval objects to be written into
715
+ the same CSV.
716
+ schedule_directory: [Required] Text string of a full path to a folder on
717
+ this machine to which the CSV version of the file will be written.
718
+ file_name: Text string for the name to be used for the CSV file that
719
+ houses all of the schedule data.
720
+ include_datetimes: Boolean to note whether a column of datetime objects
721
+ should be written into the CSV alongside the data. Default is False,
722
+ which will keep the resulting CSV lighter in file size but you may
723
+ want to include such datetimes in order to verify that values align with
724
+ the expected timestep.
725
+
726
+ Returns:
727
+ schedule_files --
728
+ A list of IDF text string representations of the Schedule:File describing
729
+ this schedule.
730
+ """
731
+ # ensure that all is_leap_year values are the same
732
+ init_lp_yr = schedules[0].is_leap_year
733
+ for sch in schedules:
734
+ assert sch.is_leap_year is init_lp_yr, 'All is_leap_year properties must ' \
735
+ 'match for several ScheduleFixedIntervals to be in the same CSV.'
736
+ # find the greatest timestep of all the schedules
737
+ max_timestep = max([sched.timestep for sched in schedules])
738
+ # gather all of the data to be written into the CSV
739
+ sch_ids = [sched.identifier for sched in schedules]
740
+ sched_vals = [sched.values_at_timestep(max_timestep) for sched in schedules]
741
+ sched_data = [','.join([str(x) for x in row]) for row in zip(*sched_vals)]
742
+ if include_datetimes:
743
+ sched_a_per = AnalysisPeriod(timestep=max_timestep, is_leap_year=init_lp_yr)
744
+ sched_data = ('{},{}'.format(dt, val) for dt, val in
745
+ zip(sched_a_per.datetimes, sched_data))
746
+ sch_ids = [''] + sch_ids
747
+ sched_data = [','.join(sch_ids)] + sched_data
748
+ file_path = os.path.join(schedule_directory,
749
+ '{}.csv'.format(file_name.replace(' ', '_')))
750
+
751
+ # write the data into the file
752
+ write_to_file(file_path, ',\n'.join(sched_data), True)
753
+
754
+ # generate the IDF strings
755
+ schedule_files = []
756
+ for i, sched in enumerate(schedules):
757
+ shc_typ = sched._schedule_type_limit.identifier if \
758
+ sched._schedule_type_limit is not None else ''
759
+ col_num = 1 + i if not include_datetimes else 2 + i
760
+ num_hrs = 8760 if not sched.is_leap_year else 8784
761
+ interp = 'No' if not sched.interpolate else 'Yes'
762
+ min_per_step = int(60 / max_timestep)
763
+ fields = (sched.identifier, shc_typ, file_path, col_num, 1, num_hrs, 'Comma',
764
+ interp, min_per_step)
765
+ schedule_files.append(generate_idf_string(
766
+ 'Schedule:File', fields, ScheduleFixedInterval._schedule_file_comments))
767
+ return schedule_files
768
+
769
+ @staticmethod
770
+ def extract_all_from_idf_file(idf_file):
771
+ """Extract all ScheduleFixedInterval objects from an EnergyPlus IDF file.
772
+
773
+ Args:
774
+ idf_file: A path to an IDF file containing objects for Schedule:File
775
+ which should have correct file paths to CSVs storing the schedule
776
+ values.
777
+
778
+ Returns:
779
+ schedules --
780
+ A list of all Schedule:File objects in the IDF file as honeybee_energy
781
+ ScheduleFixedInterval objects.
782
+ """
783
+ # read the file and remove lines of comments
784
+ file_contents = clean_idf_file_contents(idf_file)
785
+ # extract all of the ScheduleTypeLimit objects
786
+ type_pattern = re.compile(r"(?i)(ScheduleTypeLimits,[\s\S]*?;)")
787
+ sch_type_str = type_pattern.findall(file_contents)
788
+ sch_type_dict = ScheduleFixedInterval._idf_schedule_type_dictionary(sch_type_str)
789
+ # extract all of the Schedule:File objects and convert to Schedule
790
+ schedules = []
791
+ sch_pattern = re.compile(r"(?i)(Schedule:File,[\s\S]*?;)")
792
+ for sch_string in sch_pattern.findall(file_contents):
793
+ schedule = ScheduleFixedInterval.from_idf(sch_string)
794
+ sch_props = parse_idf_string(sch_string)
795
+ if sch_props[1] != '':
796
+ schedule.schedule_type_limit = sch_type_dict[sch_props[1]]
797
+ schedules.append(schedule)
798
+ return schedules
799
+
800
+ @staticmethod
801
+ def average_schedules(identifier, schedules, weights=None):
802
+ """Get a ScheduleFixedInterval that's a weighted average between other schedules.
803
+
804
+ Args:
805
+ identifier: A unique ID text string for the new unique ScheduleFixedInterval.
806
+ Must be < 100 characters and not contain any EnergyPlus special
807
+ characters. This will be used to identify the object across a
808
+ model and in the exported IDF.
809
+ schedules: A list of ScheduleFixedInterval objects that will be averaged
810
+ together to make a new ScheduleFixedInterval. This list may also contain
811
+ ScheduleRulesets but it is recommend there be at least one
812
+ ScheduleFixedInterval. Otherwise, the ScheduleRuleset.average_schedules
813
+ method should be used.
814
+ weights: An optional list of fractional numbers with the same length
815
+ as the input schedules that sum to 1. These will be used to weight
816
+ each of the ScheduleFixedInterval objects in the resulting average
817
+ schedule. If None, the individual schedules will be weighted equally.
818
+ """
819
+ # check the inputs
820
+ assert isinstance(schedules, (list, tuple)), 'Expected a list of ScheduleDay ' \
821
+ 'objects for average_schedules. Got {}.'.format(type(schedules))
822
+ if weights is None:
823
+ weight = 1 / len(schedules)
824
+ weights = [weight for i in schedules]
825
+ else:
826
+ weights = tuple_with_length(weights, len(schedules), float,
827
+ 'average schedules weights')
828
+ assert abs(sum(weights) - 1.0) <= 1e-9, 'Average schedule weights must ' \
829
+ 'sum to 1. Got {}.'.format(sum(weights))
830
+ # collect all of the values at the timestep
831
+ all_values, timestep, lp_yr = \
832
+ ScheduleFixedInterval._all_schedule_values(schedules)
833
+ sch_vals = [sum([val * weights[i] for i, val in enumerate(values)])
834
+ for values in zip(*all_values)]
835
+ # return the final schedule
836
+ return ScheduleFixedInterval(
837
+ identifier, sch_vals, schedules[0].schedule_type_limit,
838
+ timestep, start_date=Date(1, 1, lp_yr)
839
+ )
840
+
841
+ @staticmethod
842
+ def max_schedules(identifier, schedules):
843
+ """Get a ScheduleFixedInterval that uses the maximum value between schedules.
844
+
845
+ Args:
846
+ identifier: A unique ID text string for the new unique ScheduleFixedInterval.
847
+ Must be < 100 characters and not contain any EnergyPlus special
848
+ characters. This will be used to identify the object across a
849
+ model and in the exported IDF.
850
+ schedules: A list of ScheduleFixedInterval objects that will have the
851
+ maximum value taken at each timestep to make a new ScheduleFixedInterval.
852
+ This list may also contain ScheduleRulesets but it is recommend there
853
+ be at least one ScheduleFixedInterval. Otherwise, the
854
+ ScheduleRuleset.max_schedules method should be used.
855
+ """
856
+ # check the inputs
857
+ assert isinstance(schedules, (list, tuple)), 'Expected a list of ScheduleDay ' \
858
+ 'objects for max_schedules. Got {}.'.format(type(schedules))
859
+ # collect all of the values at the timestep
860
+ all_values, timestep, lp_yr = \
861
+ ScheduleFixedInterval._all_schedule_values(schedules)
862
+ sch_vals = [max(values) for values in zip(*all_values)]
863
+ # return the final schedule
864
+ return ScheduleFixedInterval(
865
+ identifier, sch_vals, schedules[0].schedule_type_limit,
866
+ timestep, start_date=Date(1, 1, lp_yr)
867
+ )
868
+
869
+ @staticmethod
870
+ def min_schedules(identifier, schedules):
871
+ """Get a ScheduleFixedInterval that uses the minimum value between schedules.
872
+
873
+ Args:
874
+ identifier: A unique ID text string for the new unique ScheduleFixedInterval.
875
+ Must be < 100 characters and not contain any EnergyPlus special
876
+ characters. This will be used to identify the object across a
877
+ model and in the exported IDF.
878
+ schedules: A list of ScheduleFixedInterval objects that will have the
879
+ minimum value taken at each timestep to make a new ScheduleFixedInterval.
880
+ This list may also contain ScheduleRulesets but it is recommend there
881
+ be at least one ScheduleFixedInterval. Otherwise, the
882
+ ScheduleRuleset.min_schedules method should be used.
883
+ """
884
+ # check the inputs
885
+ assert isinstance(schedules, (list, tuple)), 'Expected a list of ScheduleDay ' \
886
+ 'objects for min_schedules. Got {}.'.format(type(schedules))
887
+ # collect all of the values at the timestep
888
+ all_values, timestep, lp_yr = \
889
+ ScheduleFixedInterval._all_schedule_values(schedules)
890
+ sch_vals = [min(values) for values in zip(*all_values)]
891
+ # return the final schedule
892
+ return ScheduleFixedInterval(
893
+ identifier, sch_vals, schedules[0].schedule_type_limit,
894
+ timestep, start_date=Date(1, 1, lp_yr)
895
+ )
896
+
897
+ def _check_values(self, values):
898
+ """Check values whenever they come through the values setter."""
899
+ assert isinstance(values, Iterable) and not \
900
+ isinstance(values, (str, dict, bytes, bytearray)), \
901
+ 'values should be a list or tuple. Got {}'.format(type(values))
902
+ if not isinstance(values, tuple):
903
+ try:
904
+ values = tuple(float(val) for val in values)
905
+ except (ValueError, TypeError):
906
+ raise TypeError('ScheduleDay values must be numbers.')
907
+ max_hour = 8760 if not self.is_leap_year else 8784
908
+ assert self._timestep * 24 <= len(values) <= self._timestep * max_hour, \
909
+ 'Length of values must be at least {} and no more than {} when timestep ' \
910
+ 'is {} and start_date leap-year value is {}. Got {}.'.format(
911
+ self._timestep * 24, self._timestep * max_hour, self._timestep,
912
+ self.is_leap_year, len(values))
913
+ return values
914
+
915
+ def _get_lb_data_type_and_unit(self):
916
+ """Get the ladybug data type and unit from the schedule_type_limit."""
917
+ if self.schedule_type_limit is not None:
918
+ data_type = self.schedule_type_limit.data_type
919
+ unit = self.schedule_type_limit.unit
920
+ else:
921
+ unit = 'unknown'
922
+ data_type = GenericType('Unknown Data Type', unit)
923
+ return data_type, unit
924
+
925
+ def _xxrange(self, start, end, step_count):
926
+ """Generate n values between start and end."""
927
+ _step = (end - start) / float(step_count)
928
+ return (start + (i * _step) for i in xrange(int(step_count)))
929
+
930
+ @staticmethod
931
+ def _all_schedule_values(schedules):
932
+ """Get all of the values across a list of input schedules."""
933
+ # determine the max timestep and leap year for the resulting schedule
934
+ t_steps = [1]
935
+ lp_yrs = []
936
+ for sched in schedules:
937
+ try:
938
+ t_steps.append(sched.timestep)
939
+ lp_yrs.append(sched.is_leap_year)
940
+ except AttributeError:
941
+ pass # ScheduleRuleset
942
+ timestep = max(t_steps)
943
+ lp_yr = lp_yrs[0] if len(lp_yrs) != 0 else False
944
+ for lp in lp_yrs:
945
+ assert lp is lp_yr, \
946
+ 'All is_leap_year properties must match to make an average schedule.'
947
+
948
+ # collect all of the values at the timestep
949
+ all_values = []
950
+ for sched in schedules:
951
+ if isinstance(sched, ScheduleFixedInterval):
952
+ all_values.append(sched.values_at_timestep(timestep))
953
+ else:
954
+ try:
955
+ all_values.append(sched.values(timestep, leap_year=lp_yr))
956
+ except AttributeError:
957
+ raise TypeError('"{}" is not an acceptable input type for '
958
+ 'ScheduleFixedInterval.'.format(type(sched)))
959
+ return all_values, timestep, lp_yr
960
+
961
+ @staticmethod
962
+ def _idf_schedule_type_dictionary(type_idf_strings):
963
+ """Get a dictionary of ScheduleTypeLimit objects from ScheduleTypeLimits strings.
964
+ """
965
+ sch_type_dict = {}
966
+ for type_str in type_idf_strings:
967
+ type_str = type_str.strip()
968
+ type_obj = ScheduleTypeLimit.from_idf(type_str)
969
+ sch_type_dict[type_obj.identifier] = type_obj
970
+ return sch_type_dict
971
+
972
+ def __len__(self):
973
+ return len(self._values)
974
+
975
+ def __getitem__(self, key):
976
+ return self._values[key]
977
+
978
+ def __iter__(self):
979
+ return iter(self._values)
980
+
981
+ def __key(self):
982
+ """A tuple based on the object properties, useful for hashing."""
983
+ return (self.identifier, self._placeholder_value, self._interpolate,
984
+ self._timestep, hash(self._start_date),
985
+ hash(self.schedule_type_limit)) + self._values
986
+
987
+ def __hash__(self):
988
+ return hash(self.__key())
989
+
990
+ def __eq__(self, other):
991
+ return isinstance(other, ScheduleFixedInterval) and self.__key() == other.__key()
992
+
993
+ def __ne__(self, other):
994
+ return not self.__eq__(other)
995
+
996
+ def __copy__(self):
997
+ new_obj = ScheduleFixedInterval(
998
+ self.identifier, self._values, self._schedule_type_limit, self._timestep,
999
+ self._start_date, self._placeholder_value, self._interpolate)
1000
+ new_obj._display_name = self._display_name
1001
+ new_obj._user_data = None if self._user_data is None else self._user_data.copy()
1002
+ new_obj._properties._duplicate_extension_attr(self._properties)
1003
+ return new_obj
1004
+
1005
+ def ToString(self):
1006
+ """Overwrite .NET ToString."""
1007
+ return self.__repr__()
1008
+
1009
+ def __repr__(self):
1010
+ return 'ScheduleFixedInterval: {} [{} - {}] [timestep: {}]'.format(
1011
+ self.display_name, self.start_date,
1012
+ self.end_date_time.strftime('%d %b'), self.timestep)