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.
- honeybee_energy/__init__.py +24 -0
- honeybee_energy/__main__.py +4 -0
- honeybee_energy/_extend_honeybee.py +145 -0
- honeybee_energy/altnumber.py +21 -0
- honeybee_energy/baseline/__init__.py +2 -0
- honeybee_energy/baseline/create.py +608 -0
- honeybee_energy/baseline/data/__init__.py +1 -0
- honeybee_energy/baseline/data/constructions.csv +64 -0
- honeybee_energy/baseline/data/fen_ratios.csv +15 -0
- honeybee_energy/baseline/data/lpd_building.csv +21 -0
- honeybee_energy/baseline/data/pci_2016.csv +22 -0
- honeybee_energy/baseline/data/pci_2019.csv +22 -0
- honeybee_energy/baseline/data/pci_2022.csv +22 -0
- honeybee_energy/baseline/data/shw.csv +21 -0
- honeybee_energy/baseline/pci.py +512 -0
- honeybee_energy/baseline/result.py +371 -0
- honeybee_energy/boundarycondition.py +128 -0
- honeybee_energy/cli/__init__.py +69 -0
- honeybee_energy/cli/baseline.py +475 -0
- honeybee_energy/cli/edit.py +327 -0
- honeybee_energy/cli/lib.py +1154 -0
- honeybee_energy/cli/result.py +810 -0
- honeybee_energy/cli/setconfig.py +124 -0
- honeybee_energy/cli/settings.py +569 -0
- honeybee_energy/cli/simulate.py +380 -0
- honeybee_energy/cli/translate.py +1714 -0
- honeybee_energy/cli/validate.py +224 -0
- honeybee_energy/config.json +11 -0
- honeybee_energy/config.py +842 -0
- honeybee_energy/construction/__init__.py +1 -0
- honeybee_energy/construction/_base.py +374 -0
- honeybee_energy/construction/air.py +325 -0
- honeybee_energy/construction/dictutil.py +89 -0
- honeybee_energy/construction/dynamic.py +607 -0
- honeybee_energy/construction/opaque.py +460 -0
- honeybee_energy/construction/shade.py +319 -0
- honeybee_energy/construction/window.py +1096 -0
- honeybee_energy/construction/windowshade.py +847 -0
- honeybee_energy/constructionset.py +1655 -0
- honeybee_energy/dictutil.py +56 -0
- honeybee_energy/generator/__init__.py +5 -0
- honeybee_energy/generator/loadcenter.py +204 -0
- honeybee_energy/generator/pv.py +535 -0
- honeybee_energy/hvac/__init__.py +21 -0
- honeybee_energy/hvac/_base.py +124 -0
- honeybee_energy/hvac/_template.py +270 -0
- honeybee_energy/hvac/allair/__init__.py +22 -0
- honeybee_energy/hvac/allair/_base.py +349 -0
- honeybee_energy/hvac/allair/furnace.py +168 -0
- honeybee_energy/hvac/allair/psz.py +131 -0
- honeybee_energy/hvac/allair/ptac.py +163 -0
- honeybee_energy/hvac/allair/pvav.py +109 -0
- honeybee_energy/hvac/allair/vav.py +128 -0
- honeybee_energy/hvac/detailed.py +337 -0
- honeybee_energy/hvac/doas/__init__.py +28 -0
- honeybee_energy/hvac/doas/_base.py +345 -0
- honeybee_energy/hvac/doas/fcu.py +127 -0
- honeybee_energy/hvac/doas/radiant.py +329 -0
- honeybee_energy/hvac/doas/vrf.py +81 -0
- honeybee_energy/hvac/doas/wshp.py +91 -0
- honeybee_energy/hvac/heatcool/__init__.py +23 -0
- honeybee_energy/hvac/heatcool/_base.py +177 -0
- honeybee_energy/hvac/heatcool/baseboard.py +61 -0
- honeybee_energy/hvac/heatcool/evapcool.py +72 -0
- honeybee_energy/hvac/heatcool/fcu.py +92 -0
- honeybee_energy/hvac/heatcool/gasunit.py +53 -0
- honeybee_energy/hvac/heatcool/radiant.py +269 -0
- honeybee_energy/hvac/heatcool/residential.py +77 -0
- honeybee_energy/hvac/heatcool/vrf.py +54 -0
- honeybee_energy/hvac/heatcool/windowac.py +70 -0
- honeybee_energy/hvac/heatcool/wshp.py +62 -0
- honeybee_energy/hvac/idealair.py +699 -0
- honeybee_energy/internalmass.py +310 -0
- honeybee_energy/lib/__init__.py +1 -0
- honeybee_energy/lib/_loadconstructions.py +194 -0
- honeybee_energy/lib/_loadconstructionsets.py +117 -0
- honeybee_energy/lib/_loadmaterials.py +83 -0
- honeybee_energy/lib/_loadprogramtypes.py +125 -0
- honeybee_energy/lib/_loadschedules.py +87 -0
- honeybee_energy/lib/_loadtypelimits.py +64 -0
- honeybee_energy/lib/constructions.py +207 -0
- honeybee_energy/lib/constructionsets.py +95 -0
- honeybee_energy/lib/materials.py +67 -0
- honeybee_energy/lib/programtypes.py +125 -0
- honeybee_energy/lib/schedules.py +61 -0
- honeybee_energy/lib/scheduletypelimits.py +31 -0
- honeybee_energy/load/__init__.py +1 -0
- honeybee_energy/load/_base.py +190 -0
- honeybee_energy/load/daylight.py +397 -0
- honeybee_energy/load/dictutil.py +47 -0
- honeybee_energy/load/equipment.py +771 -0
- honeybee_energy/load/hotwater.py +543 -0
- honeybee_energy/load/infiltration.py +460 -0
- honeybee_energy/load/lighting.py +480 -0
- honeybee_energy/load/people.py +497 -0
- honeybee_energy/load/process.py +472 -0
- honeybee_energy/load/setpoint.py +816 -0
- honeybee_energy/load/ventilation.py +550 -0
- honeybee_energy/material/__init__.py +1 -0
- honeybee_energy/material/_base.py +166 -0
- honeybee_energy/material/dictutil.py +59 -0
- honeybee_energy/material/frame.py +367 -0
- honeybee_energy/material/gas.py +1087 -0
- honeybee_energy/material/glazing.py +854 -0
- honeybee_energy/material/opaque.py +1351 -0
- honeybee_energy/material/shade.py +1360 -0
- honeybee_energy/measure.py +472 -0
- honeybee_energy/programtype.py +723 -0
- honeybee_energy/properties/__init__.py +1 -0
- honeybee_energy/properties/aperture.py +333 -0
- honeybee_energy/properties/door.py +342 -0
- honeybee_energy/properties/extension.py +244 -0
- honeybee_energy/properties/face.py +274 -0
- honeybee_energy/properties/model.py +2640 -0
- honeybee_energy/properties/room.py +1747 -0
- honeybee_energy/properties/shade.py +314 -0
- honeybee_energy/properties/shademesh.py +262 -0
- honeybee_energy/reader.py +48 -0
- honeybee_energy/result/__init__.py +1 -0
- honeybee_energy/result/colorobj.py +648 -0
- honeybee_energy/result/emissions.py +290 -0
- honeybee_energy/result/err.py +101 -0
- honeybee_energy/result/eui.py +100 -0
- honeybee_energy/result/generation.py +160 -0
- honeybee_energy/result/loadbalance.py +890 -0
- honeybee_energy/result/match.py +202 -0
- honeybee_energy/result/osw.py +90 -0
- honeybee_energy/result/rdd.py +59 -0
- honeybee_energy/result/zsz.py +190 -0
- honeybee_energy/run.py +1577 -0
- honeybee_energy/schedule/__init__.py +1 -0
- honeybee_energy/schedule/day.py +626 -0
- honeybee_energy/schedule/dictutil.py +59 -0
- honeybee_energy/schedule/fixedinterval.py +1012 -0
- honeybee_energy/schedule/rule.py +619 -0
- honeybee_energy/schedule/ruleset.py +1867 -0
- honeybee_energy/schedule/typelimit.py +310 -0
- honeybee_energy/shw.py +315 -0
- honeybee_energy/simulation/__init__.py +1 -0
- honeybee_energy/simulation/control.py +214 -0
- honeybee_energy/simulation/daylightsaving.py +185 -0
- honeybee_energy/simulation/dictutil.py +51 -0
- honeybee_energy/simulation/output.py +646 -0
- honeybee_energy/simulation/parameter.py +606 -0
- honeybee_energy/simulation/runperiod.py +443 -0
- honeybee_energy/simulation/shadowcalculation.py +295 -0
- honeybee_energy/simulation/sizing.py +546 -0
- honeybee_energy/ventcool/__init__.py +5 -0
- honeybee_energy/ventcool/_crack_data.py +91 -0
- honeybee_energy/ventcool/afn.py +289 -0
- honeybee_energy/ventcool/control.py +269 -0
- honeybee_energy/ventcool/crack.py +126 -0
- honeybee_energy/ventcool/fan.py +493 -0
- honeybee_energy/ventcool/opening.py +365 -0
- honeybee_energy/ventcool/simulation.py +314 -0
- honeybee_energy/writer.py +1078 -0
- honeybee_energy-1.116.106.dist-info/METADATA +113 -0
- honeybee_energy-1.116.106.dist-info/RECORD +162 -0
- honeybee_energy-1.116.106.dist-info/WHEEL +5 -0
- honeybee_energy-1.116.106.dist-info/entry_points.txt +2 -0
- honeybee_energy-1.116.106.dist-info/licenses/LICENSE +661 -0
- honeybee_energy-1.116.106.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""honeybee-energy schedules."""
|
|
@@ -0,0 +1,626 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
"""Schedule describing a single day."""
|
|
3
|
+
from __future__ import division
|
|
4
|
+
|
|
5
|
+
from .typelimit import ScheduleTypeLimit
|
|
6
|
+
from ..reader import parse_idf_string
|
|
7
|
+
from ..writer import generate_idf_string
|
|
8
|
+
|
|
9
|
+
from honeybee._lockable import lockable
|
|
10
|
+
from honeybee.typing import valid_ep_string, tuple_with_length
|
|
11
|
+
|
|
12
|
+
from ladybug.datacollection import HourlyContinuousCollection
|
|
13
|
+
from ladybug.header import Header
|
|
14
|
+
from ladybug.analysisperiod import AnalysisPeriod
|
|
15
|
+
from ladybug.dt import Date, Time
|
|
16
|
+
from ladybug.datatype.generic import GenericType
|
|
17
|
+
|
|
18
|
+
from collections import deque
|
|
19
|
+
try:
|
|
20
|
+
from collections.abc import Iterable # python < 3.7
|
|
21
|
+
except ImportError:
|
|
22
|
+
from collections import Iterable # python >= 3.8
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@lockable
|
|
26
|
+
class ScheduleDay(object):
|
|
27
|
+
"""Schedule for a single day.
|
|
28
|
+
|
|
29
|
+
Note that a ScheduleDay cannot be assigned to Rooms, Shades, etc. The ScheduleDay
|
|
30
|
+
must be added to a ScheduleRuleset or a ScheduleRule and then the ScheduleRuleset
|
|
31
|
+
can be applied to such objects.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
identifier: Text string for a unique ScheduleDay ID. Must be < 100 characters
|
|
35
|
+
and not contain any EnergyPlus special characters. This will be used to
|
|
36
|
+
identify the object across a model and in the exported IDF.
|
|
37
|
+
values: A list of floats or integers for the values of the schedule.
|
|
38
|
+
The length of this list must match the length of the times list.
|
|
39
|
+
times: A list of ladybug Time objects with the same length as the input
|
|
40
|
+
values. Each time represents the time of day that the corresponding
|
|
41
|
+
value begins to take effect. For example [0:00, 9:00, 17:00] in
|
|
42
|
+
combination with the values [0, 1, 0] denotes a schedule value of
|
|
43
|
+
0 from 0:00 to 9:00, a value of 1 from 9:00 to 17:00 and 0 from 17:00
|
|
44
|
+
to the end of the day.
|
|
45
|
+
If this input is None, the default will be a single time at 0:00,
|
|
46
|
+
indicating the `values` input should be a single constant value that
|
|
47
|
+
goes all of the way until the end of the day.
|
|
48
|
+
Note that these times follow a different convention than EnergyPlus,
|
|
49
|
+
which uses "time until" instead of "time of beginning".
|
|
50
|
+
interpolate: Boolean to note whether values in between times should be
|
|
51
|
+
linearly interpolated or whether successive values should take effect
|
|
52
|
+
immediately upon the beginning time corresponding to them. Default: False
|
|
53
|
+
|
|
54
|
+
Properties:
|
|
55
|
+
* identifier
|
|
56
|
+
* display_name
|
|
57
|
+
* times
|
|
58
|
+
* values
|
|
59
|
+
* interpolate
|
|
60
|
+
* is_constant
|
|
61
|
+
"""
|
|
62
|
+
__slots__ = ('_identifier', '_display_name', '_values', '_times',
|
|
63
|
+
'_interpolate', '_parent', '_locked')
|
|
64
|
+
|
|
65
|
+
_start_of_day = Time(0, 0)
|
|
66
|
+
VALIDTIMESTEPS = (1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30, 60)
|
|
67
|
+
|
|
68
|
+
def __init__(self, identifier, values, times=None, interpolate=False):
|
|
69
|
+
"""Initialize Schedule Day."""
|
|
70
|
+
self._locked = False # unlocked by default
|
|
71
|
+
self._parent = None # no parent ScheduleRuleset by default
|
|
72
|
+
self.identifier = identifier
|
|
73
|
+
self._display_name = None
|
|
74
|
+
|
|
75
|
+
# assign the times and values
|
|
76
|
+
if times is None:
|
|
77
|
+
self._times = (self._start_of_day,)
|
|
78
|
+
else:
|
|
79
|
+
if not isinstance(times, tuple):
|
|
80
|
+
try:
|
|
81
|
+
times = tuple(times)
|
|
82
|
+
except (ValueError, TypeError):
|
|
83
|
+
raise TypeError('ScheduleDay times must be iterable.')
|
|
84
|
+
for time in times:
|
|
85
|
+
self._check_time(time)
|
|
86
|
+
self._times = times
|
|
87
|
+
self.values = values
|
|
88
|
+
|
|
89
|
+
# if times are not ordered chronologically, sort them
|
|
90
|
+
if not self._are_chronological(self._times):
|
|
91
|
+
self._times, self._values = zip(*sorted(zip(self._times, self._values)))
|
|
92
|
+
|
|
93
|
+
# ensure that the schedule always starts from 0:00
|
|
94
|
+
assert self._times[0] == self._start_of_day, 'ScheduleDay times must always ' \
|
|
95
|
+
'start with 0:00. Got {}.'.format(self._times[0])
|
|
96
|
+
|
|
97
|
+
self.interpolate = interpolate
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def identifier(self):
|
|
101
|
+
"""Get or set a text string for a unique schedule day identifier."""
|
|
102
|
+
return self._identifier
|
|
103
|
+
|
|
104
|
+
@identifier.setter
|
|
105
|
+
def identifier(self, identifier):
|
|
106
|
+
self._identifier = valid_ep_string(identifier, 'schedule day identifier')
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def display_name(self):
|
|
110
|
+
"""Get or set a string for the object name without any character restrictions.
|
|
111
|
+
|
|
112
|
+
If not set, this will be equal to the identifier.
|
|
113
|
+
"""
|
|
114
|
+
if self._display_name is None:
|
|
115
|
+
return self._identifier
|
|
116
|
+
return self._display_name
|
|
117
|
+
|
|
118
|
+
@display_name.setter
|
|
119
|
+
def display_name(self, value):
|
|
120
|
+
if value is not None:
|
|
121
|
+
try:
|
|
122
|
+
value = str(value)
|
|
123
|
+
except UnicodeEncodeError: # Python 2 machine lacking the character set
|
|
124
|
+
pass # keep it as unicode
|
|
125
|
+
self._display_name = value
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
def values(self):
|
|
129
|
+
"""Get or set the schedule's numerical values, which correspond to the times."""
|
|
130
|
+
return self._values
|
|
131
|
+
|
|
132
|
+
@values.setter
|
|
133
|
+
def values(self, values):
|
|
134
|
+
self._values = self._check_values(values)
|
|
135
|
+
|
|
136
|
+
@property
|
|
137
|
+
def times(self):
|
|
138
|
+
"""Get or set the Schedule's times, which correspond to the numerical values."""
|
|
139
|
+
return self._times
|
|
140
|
+
|
|
141
|
+
@times.setter
|
|
142
|
+
def times(self, times):
|
|
143
|
+
self._times = self._check_times(times)
|
|
144
|
+
|
|
145
|
+
@property
|
|
146
|
+
def interpolate(self):
|
|
147
|
+
"""Get or set a boolean noting whether values should be interpolated."""
|
|
148
|
+
return self._interpolate
|
|
149
|
+
|
|
150
|
+
@interpolate.setter
|
|
151
|
+
def interpolate(self, interpolate):
|
|
152
|
+
self._interpolate = bool(interpolate)
|
|
153
|
+
|
|
154
|
+
@property
|
|
155
|
+
def is_constant(self):
|
|
156
|
+
"""Boolean noting whether the schedule is representable with a single value."""
|
|
157
|
+
return len(self) == 1
|
|
158
|
+
|
|
159
|
+
def add_value(self, value, time):
|
|
160
|
+
"""Add a value to the schedule along with the time it begins to take effect.
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
value: A number for the schedule value.
|
|
164
|
+
time: The ladybug Time object for the time at which the value begins to
|
|
165
|
+
take effect.
|
|
166
|
+
"""
|
|
167
|
+
self._check_time(time)
|
|
168
|
+
value = self._check_value(value)
|
|
169
|
+
self._times = self._times + (time,)
|
|
170
|
+
self._values = self._values + (value,)
|
|
171
|
+
if self._times[-1] < self._times[-2]: # ensure times are chronological
|
|
172
|
+
self._times, self._values = zip(*sorted(zip(self._times, self._values)))
|
|
173
|
+
|
|
174
|
+
def remove_value(self, value_index):
|
|
175
|
+
"""Remove a value from the schedule by its index.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
value_index: An integer for the index of the value to remove.
|
|
179
|
+
"""
|
|
180
|
+
assert len(self._values) > 1, 'ScheduleDay must have at least one value.'
|
|
181
|
+
assert value_index != 0, 'ScheduleDay cannot remove value at index 0.'
|
|
182
|
+
if value_index < 0:
|
|
183
|
+
value_index = len(self._values) + value_index
|
|
184
|
+
self._values = tuple(x for i, x in enumerate(self._values) if i != value_index)
|
|
185
|
+
self._times = tuple(x for i, x in enumerate(self._times) if i != value_index)
|
|
186
|
+
|
|
187
|
+
def remove_value_by_time(self, time):
|
|
188
|
+
"""Remove a value from the schedule by its time in the times property.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
time: An ladybug Time for the time and the value to remove.
|
|
192
|
+
"""
|
|
193
|
+
self.remove_value(self._times.index(time))
|
|
194
|
+
|
|
195
|
+
def replace_value(self, value_index, new_value):
|
|
196
|
+
"""Replace an existing value in the schedule with a new one.
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
value_index: An integer for the index of the value to replace.
|
|
200
|
+
new_value: A number for the new value to use at the given index.
|
|
201
|
+
"""
|
|
202
|
+
val_list = list(self._values)
|
|
203
|
+
val_list[value_index] = self._check_value(new_value)
|
|
204
|
+
self._values = tuple(val_list)
|
|
205
|
+
|
|
206
|
+
def replace_value_by_time(self, time, new_value):
|
|
207
|
+
"""Replace an existing value in the schedule using its time.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
time: An ladybug Time for the time and the value to replace.
|
|
211
|
+
new_value: A number for the new value to use at the given time.
|
|
212
|
+
"""
|
|
213
|
+
self.replace_value(self._times.index(time), new_value)
|
|
214
|
+
|
|
215
|
+
def values_at_timestep(self, timestep=1):
|
|
216
|
+
"""Get a list of sequential schedule values over the day at a given timestep.
|
|
217
|
+
|
|
218
|
+
Note that there are two possible ways that these values can be mapped to
|
|
219
|
+
corresponding times (here referred to as the "Ladybug Tools Interpretation"
|
|
220
|
+
and the "EnergyPlus Interpretation"). Both of these interpretations ultimately
|
|
221
|
+
refer to the exact same schedule in the calculations of EnergyPlus but the
|
|
222
|
+
times of day that each of the values are mapped to differ.
|
|
223
|
+
|
|
224
|
+
Ladybug Tools Interpretation - The first value in the returned list here
|
|
225
|
+
corresponds to the time 0:00 and the value for this time is applied over
|
|
226
|
+
the rest of the following timestep. In this way, an office schedule that is set
|
|
227
|
+
to be occupied from 9:00 until 17:00 will show 9:00 as occupied but 17:00 as
|
|
228
|
+
unoccupied.
|
|
229
|
+
|
|
230
|
+
EnergyPlus Interpretation - The first value in the returned list here
|
|
231
|
+
corresponds to the timestep after 0:00. For example, if the timestep is 1,
|
|
232
|
+
the time mapped to the first value is 1:00. If the timestep is 6, the first
|
|
233
|
+
value corresponds to 0:10. In this interpretation, the value for this time is
|
|
234
|
+
applied over all of the previous timestep. In this way, an office schedule that
|
|
235
|
+
is set to be occupied from 9:00 until 17:00 will show 9:00 as unoccupied but
|
|
236
|
+
17:00 as occupied.
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
timestep: An integer for the number of steps per hour at which to return
|
|
240
|
+
the resulting values.
|
|
241
|
+
"""
|
|
242
|
+
assert timestep in self.VALIDTIMESTEPS, 'ScheduleDay timestep "{}" is invalid.' \
|
|
243
|
+
' Must be one of the following:\n{}'.format(timestep, self.VALIDTIMESTEPS)
|
|
244
|
+
values = []
|
|
245
|
+
minute_delta = 60 / timestep
|
|
246
|
+
mod = 0 # track the minute of day through iteration
|
|
247
|
+
time_index = 1 # track the index of the next time of change
|
|
248
|
+
until_mod = self._get_until_mod(time_index) # get the mod of the next change
|
|
249
|
+
if not self.interpolate:
|
|
250
|
+
for _ in range(24 * timestep):
|
|
251
|
+
if mod >= until_mod:
|
|
252
|
+
time_index += 1
|
|
253
|
+
until_mod = self._get_until_mod(time_index)
|
|
254
|
+
values.append(self._values[time_index - 1])
|
|
255
|
+
mod += minute_delta
|
|
256
|
+
else:
|
|
257
|
+
for _ in range(24 * timestep):
|
|
258
|
+
if mod >= until_mod:
|
|
259
|
+
i = 0
|
|
260
|
+
delta = self._values[time_index] - self._values[time_index - 1]
|
|
261
|
+
until_mod = self._get_until_mod(time_index + 1)
|
|
262
|
+
n_steps = (until_mod - self._times[time_index].mod) / minute_delta
|
|
263
|
+
values.append(self._values[time_index - 1])
|
|
264
|
+
time_index += 1
|
|
265
|
+
elif time_index == 1:
|
|
266
|
+
values.append(self._values[time_index - 1])
|
|
267
|
+
else:
|
|
268
|
+
i += 1
|
|
269
|
+
values.append(self._values[time_index - 2] + ((i / n_steps) * delta))
|
|
270
|
+
mod += minute_delta
|
|
271
|
+
del values[0] # delete first value, which is makes interpolation off by one
|
|
272
|
+
values.append(self._values[-1]) # add the final value that is reached
|
|
273
|
+
return values
|
|
274
|
+
|
|
275
|
+
def data_collection(self, date=Date(1, 1), schedule_type_limit=None, timestep=1):
|
|
276
|
+
"""Get a ladybug DataCollection representing this schedule at a given timestep.
|
|
277
|
+
|
|
278
|
+
Note that ladybug DataCollections always follow the "Ladybug Tools
|
|
279
|
+
Interpretation" of date time values as noted in the values_at_timestep
|
|
280
|
+
documentation.
|
|
281
|
+
|
|
282
|
+
Args:
|
|
283
|
+
date: A ladybug Date object for the day of the year the DataCollection
|
|
284
|
+
is representing. (Default: 1 Jan)
|
|
285
|
+
schedule_type_limit: A ScheduleTypeLimit object that describes the schedule,
|
|
286
|
+
which will be used to make the header for the DataCollection. If None,
|
|
287
|
+
a generic "Unknown" type will be used. (Default: None)
|
|
288
|
+
timestep: An integer for the number of steps per hour at which to make
|
|
289
|
+
the resulting DataCollection.
|
|
290
|
+
"""
|
|
291
|
+
assert isinstance(date, Date), \
|
|
292
|
+
'Expected ladybug Date. Got {}.'.format(type(date))
|
|
293
|
+
if schedule_type_limit is not None:
|
|
294
|
+
assert isinstance(schedule_type_limit, ScheduleTypeLimit), 'Expected ' \
|
|
295
|
+
'Honeybee ScheduleTypeLimit. Got {}.'.format(type(schedule_type_limit))
|
|
296
|
+
d_type = schedule_type_limit.data_type
|
|
297
|
+
unit = schedule_type_limit.unit
|
|
298
|
+
else:
|
|
299
|
+
d_type = GenericType('Unknown Data Type', 'unknown')
|
|
300
|
+
unit = 'unknown'
|
|
301
|
+
a_period = AnalysisPeriod(date.month, date.day, 0, date.month, date.day, 23,
|
|
302
|
+
timestep, date.leap_year)
|
|
303
|
+
header = Header(d_type, unit, a_period, metadata={'schedule': self.identifier})
|
|
304
|
+
return HourlyContinuousCollection(header, self.values_at_timestep(timestep))
|
|
305
|
+
|
|
306
|
+
def shift_by_step(self, step_count=1, timestep=1):
|
|
307
|
+
"""Get a version of this object where the values are shifted in time.
|
|
308
|
+
|
|
309
|
+
This is useful when attempting to derive a set of diversified schedules
|
|
310
|
+
from a single average schedule.
|
|
311
|
+
|
|
312
|
+
Args:
|
|
313
|
+
step_count: An integer for the number of timesteps at which the schedule
|
|
314
|
+
will be shifted. Positive values indicate a shift of values forward
|
|
315
|
+
in time while negative values indicate a shift backwards in
|
|
316
|
+
time. (Default: 1).
|
|
317
|
+
timestep: An integer for the number of timesteps per hour at which the
|
|
318
|
+
shifting is occurring. This must be a value between 1 and 60, which
|
|
319
|
+
is evenly divisible by 60. 1 indicates that each step is an hour
|
|
320
|
+
while 60 indicates that each step is a minute. (Default: 1)
|
|
321
|
+
"""
|
|
322
|
+
value_deque = deque(self.values_at_timestep(timestep))
|
|
323
|
+
value_deque.rotate(step_count)
|
|
324
|
+
new_id = '{}_Shift_{}mins'.format(
|
|
325
|
+
self.identifier, int((60 / timestep) * step_count))
|
|
326
|
+
return ScheduleDay.from_values_at_timestep(new_id, list(value_deque), timestep)
|
|
327
|
+
|
|
328
|
+
@classmethod
|
|
329
|
+
def from_values_at_timestep(cls, identifier, values, timestep=1,
|
|
330
|
+
remove_repeated=True):
|
|
331
|
+
"""Make a ScheduleDay from a list of values at a certain timestep.
|
|
332
|
+
|
|
333
|
+
Args:
|
|
334
|
+
identifier: Text string for a unique Schedule ID. Must be < 100 characters
|
|
335
|
+
and not contain any EnergyPlus special characters. This will be used to
|
|
336
|
+
identify the object across a model and in the exported IDF.
|
|
337
|
+
values: A list of numerical values with a length equal to 24 * timestep.
|
|
338
|
+
timestep: An integer for the number of steps per hour that the input
|
|
339
|
+
values correspond to. For example, if each value represents 30
|
|
340
|
+
minutes, the timestep is 2. For 15 minutes, it is 4. Default is 1,
|
|
341
|
+
meaning each value represents a single hour. Must be one of the
|
|
342
|
+
following: (1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30, 60)
|
|
343
|
+
remove_repeated: Boolean to note whether sequentially repeated values
|
|
344
|
+
should be removed from the resulting `values` and `times` comprising
|
|
345
|
+
the schedule. Default is True, which results in a lighter, more compact
|
|
346
|
+
schedule. However, you may want to set this to False when planning to
|
|
347
|
+
set the schedule's `interpolate` property to True as this avoids
|
|
348
|
+
interpolation over long, multi-hour periods.
|
|
349
|
+
"""
|
|
350
|
+
# check the inputs
|
|
351
|
+
assert timestep in cls.VALIDTIMESTEPS, 'ScheduleDay timestep "{}" is invalid.' \
|
|
352
|
+
' Must be one of the following:\n{}'.format(timestep, cls.VALIDTIMESTEPS)
|
|
353
|
+
n_vals = 24 * timestep
|
|
354
|
+
assert len(values) == n_vals, 'There must be {} ScheduleDay values when' \
|
|
355
|
+
'the timestep is {}. Got {}.'.format(n_vals, timestep, len(values))
|
|
356
|
+
|
|
357
|
+
# build the list of schedule values and times
|
|
358
|
+
schedule_times = [cls._start_of_day]
|
|
359
|
+
minute_delta = 60 / timestep
|
|
360
|
+
mod = minute_delta
|
|
361
|
+
if remove_repeated:
|
|
362
|
+
schedule_values = [values[0]]
|
|
363
|
+
for i in range(1, n_vals):
|
|
364
|
+
if values[i] != schedule_values[-1]: # non-repeated value
|
|
365
|
+
schedule_times.append(Time.from_mod(mod))
|
|
366
|
+
schedule_values.append(values[i])
|
|
367
|
+
mod += minute_delta
|
|
368
|
+
else:
|
|
369
|
+
schedule_values = values # we don't care if there are repeated values
|
|
370
|
+
for i in range(1, n_vals):
|
|
371
|
+
schedule_times.append(Time.from_mod(mod))
|
|
372
|
+
mod += minute_delta
|
|
373
|
+
|
|
374
|
+
return cls(identifier, schedule_values, schedule_times)
|
|
375
|
+
|
|
376
|
+
@classmethod
|
|
377
|
+
def from_idf(cls, idf_string):
|
|
378
|
+
"""Create a ScheduleDay from an EnergyPlus IDF text string.
|
|
379
|
+
|
|
380
|
+
Note that this method can accept all 3 types of EnergyPlus Schedule:Day
|
|
381
|
+
(Schedule:Day:Interval, Schedule:Day:Hourly, and Schedule:Day:List).
|
|
382
|
+
|
|
383
|
+
Args:
|
|
384
|
+
idf_string: A text string fully describing an EnergyPlus
|
|
385
|
+
Schedule:Day:Interval.
|
|
386
|
+
"""
|
|
387
|
+
if idf_string.startswith('Schedule:Day:Hourly,'):
|
|
388
|
+
ep_strs = parse_idf_string(idf_string)
|
|
389
|
+
hour_vals = [float(val) for val in ep_strs[2:]]
|
|
390
|
+
return cls.from_values_at_timestep(ep_strs[0], hour_vals)
|
|
391
|
+
if idf_string.startswith('Schedule:Day:List,'):
|
|
392
|
+
ep_strs = parse_idf_string(idf_string)
|
|
393
|
+
interpolate = False if ep_strs[2] == 'No' or ep_strs[2] == '' else True
|
|
394
|
+
timestep = int(60 / int(ep_strs[3]))
|
|
395
|
+
timestep_vals = [float(val) for val in ep_strs[4:]]
|
|
396
|
+
remove_repeated = True if not interpolate else False
|
|
397
|
+
sched_day = cls.from_values_at_timestep(
|
|
398
|
+
ep_strs[0], timestep_vals, timestep, remove_repeated)
|
|
399
|
+
sched_day.interpolate = interpolate
|
|
400
|
+
return sched_day
|
|
401
|
+
else:
|
|
402
|
+
ep_strs = parse_idf_string(idf_string, 'Schedule:Day:Interval,')
|
|
403
|
+
interpolate = False if ep_strs[2] == 'No' or ep_strs[2] == '' else True
|
|
404
|
+
length = len(ep_strs)
|
|
405
|
+
values = tuple(float(ep_strs[i]) for i in range(4, length + 1, 2))
|
|
406
|
+
times = [cls._start_of_day]
|
|
407
|
+
for i in range(3, length, 2):
|
|
408
|
+
try:
|
|
409
|
+
times.append(Time.from_time_string(ep_strs[i]))
|
|
410
|
+
except ValueError: # 24:00
|
|
411
|
+
pass
|
|
412
|
+
return cls(ep_strs[0], values, times, interpolate)
|
|
413
|
+
|
|
414
|
+
@classmethod
|
|
415
|
+
def from_dict(cls, data):
|
|
416
|
+
"""Create a ScheduleDay from a dictionary.
|
|
417
|
+
|
|
418
|
+
Args:
|
|
419
|
+
data: ScheduleDay dictionary following the format below.
|
|
420
|
+
|
|
421
|
+
.. code-block:: python
|
|
422
|
+
|
|
423
|
+
{
|
|
424
|
+
"type": 'ScheduleDay',
|
|
425
|
+
"identifier": 'Office_Occ_900_1700',
|
|
426
|
+
"display_name": 'Office Occupancy',
|
|
427
|
+
"values": [0, 1, 0],
|
|
428
|
+
"times": [(0, 0), (9, 0), (17, 0)],
|
|
429
|
+
"interpolate": False
|
|
430
|
+
}
|
|
431
|
+
"""
|
|
432
|
+
assert data['type'] == 'ScheduleDay', \
|
|
433
|
+
'Expected ScheduleDay. Got {}.'.format(data['type'])
|
|
434
|
+
|
|
435
|
+
if 'times' in data and data['times'] is not None:
|
|
436
|
+
times = tuple(Time.from_array(tim) for tim in data['times'])
|
|
437
|
+
else:
|
|
438
|
+
times = None
|
|
439
|
+
interpolate = data['interpolate'] if 'interpolate' in data else None
|
|
440
|
+
|
|
441
|
+
new_obj = cls(data['identifier'], data['values'], times, interpolate)
|
|
442
|
+
if 'display_name' in data and data['display_name'] is not None:
|
|
443
|
+
new_obj.display_name = data['display_name']
|
|
444
|
+
return new_obj
|
|
445
|
+
|
|
446
|
+
def to_idf(self, schedule_type_limit=None):
|
|
447
|
+
"""IDF string representation of ScheduleDay object.
|
|
448
|
+
|
|
449
|
+
Args:
|
|
450
|
+
schedule_type_limit: Optional ScheduleTypeLimit object, which will
|
|
451
|
+
be written into the IDF string in order to validate the values
|
|
452
|
+
within the schedule during the EnergyPlus run.
|
|
453
|
+
|
|
454
|
+
.. code-block:: shell
|
|
455
|
+
|
|
456
|
+
Schedule:Day:Interval,
|
|
457
|
+
dd winter rel humidity, !- Name
|
|
458
|
+
Percent, !- Schedule Type Limits Name
|
|
459
|
+
No, !- Interpolate to Timestep
|
|
460
|
+
until: 24:00, !- Time 1
|
|
461
|
+
74; !- Value Until Time 1
|
|
462
|
+
"""
|
|
463
|
+
fields = [self.identifier, ''] if schedule_type_limit is None else \
|
|
464
|
+
[self.identifier, schedule_type_limit.identifier]
|
|
465
|
+
fields.append('No' if not self.interpolate else 'Linear')
|
|
466
|
+
comments = ['schedule name', 'schedule type limits', 'interpolate to timestep']
|
|
467
|
+
for i in range(len(self._values)):
|
|
468
|
+
count = i + 1
|
|
469
|
+
try:
|
|
470
|
+
fields.append(self._times[count])
|
|
471
|
+
except IndexError: # the last "time until"
|
|
472
|
+
fields.append('24:00')
|
|
473
|
+
comments.append('time %s {hh:mm}' % count)
|
|
474
|
+
fields.append(self._values[i])
|
|
475
|
+
comments.append('value until time %s' % count)
|
|
476
|
+
return generate_idf_string('Schedule:Day:Interval', fields, comments)
|
|
477
|
+
|
|
478
|
+
def to_dict(self):
|
|
479
|
+
"""ScheduleDay dictionary representation."""
|
|
480
|
+
base = {'type': 'ScheduleDay'}
|
|
481
|
+
base['identifier'] = self.identifier
|
|
482
|
+
base['values'] = self.values
|
|
483
|
+
base['times'] = [time.to_array() for time in self.times]
|
|
484
|
+
base['interpolate'] = self.interpolate
|
|
485
|
+
if self._display_name is not None:
|
|
486
|
+
base['display_name'] = self.display_name
|
|
487
|
+
return base
|
|
488
|
+
|
|
489
|
+
def duplicate(self):
|
|
490
|
+
"""Get a copy of this object."""
|
|
491
|
+
return self.__copy__()
|
|
492
|
+
|
|
493
|
+
@staticmethod
|
|
494
|
+
def average_schedules(identifier, schedules, weights=None, timestep_resolution=1):
|
|
495
|
+
"""Create a ScheduleDay that is a weighted average between other ScheduleDays.
|
|
496
|
+
|
|
497
|
+
Args:
|
|
498
|
+
identifier: Text string for a unique ID for the new unique ScheduleDay.
|
|
499
|
+
Must be < 100 characters and not contain any EnergyPlus special
|
|
500
|
+
characters. This will be used to identify the object across a
|
|
501
|
+
model and in the exported IDF.
|
|
502
|
+
schedules: A list of ScheduleDay objects that will be averaged together
|
|
503
|
+
to make a new ScheduleDay.
|
|
504
|
+
weights: An optional list of fractional numbers with the same length
|
|
505
|
+
as the input schedules that sum to 1. These will be used to weight
|
|
506
|
+
each of the ScheduleDay objects in the resulting average schedule.
|
|
507
|
+
If None, the individual schedules will be weighted equally.
|
|
508
|
+
timestep_resolution: An optional integer for the timestep resolution
|
|
509
|
+
at which the schedules will be averaged. Any schedule details
|
|
510
|
+
smaller than this timestep will be lost in the averaging process.
|
|
511
|
+
Default: 1.
|
|
512
|
+
"""
|
|
513
|
+
# check the inputs
|
|
514
|
+
assert isinstance(schedules, (list, tuple)), 'Expected a list of ScheduleDay ' \
|
|
515
|
+
'objects for average_schedules. Got {}.'.format(type(schedules))
|
|
516
|
+
if weights is None:
|
|
517
|
+
weight = 1 / len(schedules)
|
|
518
|
+
weights = [weight for i in schedules]
|
|
519
|
+
else:
|
|
520
|
+
weights = tuple_with_length(weights, len(schedules), float,
|
|
521
|
+
'average schedules weights')
|
|
522
|
+
assert sum(weights) == 1, 'Average schedule weights must sum to 1. ' \
|
|
523
|
+
'Got {}.'.format(sum(weights))
|
|
524
|
+
|
|
525
|
+
# create a weighted average list of values
|
|
526
|
+
all_values = [sch.values_at_timestep(timestep_resolution) for sch in schedules]
|
|
527
|
+
sch_vals = [sum([val * weights[i] for i, val in enumerate(values)])
|
|
528
|
+
for values in zip(*all_values)]
|
|
529
|
+
|
|
530
|
+
# return the final list
|
|
531
|
+
return ScheduleDay.from_values_at_timestep(
|
|
532
|
+
identifier, sch_vals, timestep_resolution)
|
|
533
|
+
|
|
534
|
+
def _get_until_mod(self, time_index):
|
|
535
|
+
"""Get the minute of the day until a value is applied given a time_index."""
|
|
536
|
+
try:
|
|
537
|
+
return self._times[time_index].mod
|
|
538
|
+
except IndexError: # constant value until the end of the day
|
|
539
|
+
return 1440
|
|
540
|
+
|
|
541
|
+
def _check_values(self, values):
|
|
542
|
+
"""Check values whenever they come through the values setter."""
|
|
543
|
+
assert isinstance(values, Iterable) and not \
|
|
544
|
+
isinstance(values, (str, dict, bytes, bytearray)), \
|
|
545
|
+
'values should be a list or tuple. Got {}'.format(type(values))
|
|
546
|
+
assert len(values) == len(self._times), \
|
|
547
|
+
'Length of values list must match length of times list. {} != {}'.format(
|
|
548
|
+
len(values), len(self._times))
|
|
549
|
+
assert len(values) > 0, 'ScheduleDay must include at least one value.'
|
|
550
|
+
try:
|
|
551
|
+
return tuple(float(val) for val in values)
|
|
552
|
+
except (ValueError, TypeError):
|
|
553
|
+
raise TypeError('ScheduleDay values must be numbers.')
|
|
554
|
+
|
|
555
|
+
def _check_times(self, times):
|
|
556
|
+
"""Check times whenever they come through the times setter."""
|
|
557
|
+
if not isinstance(times, tuple):
|
|
558
|
+
try:
|
|
559
|
+
times = tuple(times)
|
|
560
|
+
except (ValueError, TypeError):
|
|
561
|
+
raise TypeError('ScheduleDay times must be iterable.')
|
|
562
|
+
for time in times:
|
|
563
|
+
self._check_time(time)
|
|
564
|
+
assert len(times) == len(self._values), \
|
|
565
|
+
'Length of values list must match length of datetimes list. {} != {}'.format(
|
|
566
|
+
len(times), len(self._values))
|
|
567
|
+
if not self._are_chronological(times):
|
|
568
|
+
times, self._values = zip(*sorted(zip(times, self._values)))
|
|
569
|
+
# ensure that the schedule always starts from 0:00
|
|
570
|
+
assert times[0] == self._start_of_day, \
|
|
571
|
+
'ScheduleDay times must always start with 0:00. Got {}.'.format(times[0])
|
|
572
|
+
return times
|
|
573
|
+
|
|
574
|
+
@staticmethod
|
|
575
|
+
def _check_value(value):
|
|
576
|
+
"""Check that an individual input value is a number."""
|
|
577
|
+
try:
|
|
578
|
+
return float(value)
|
|
579
|
+
except (ValueError, TypeError):
|
|
580
|
+
raise TypeError('ScheduleDay values must be numbers.')
|
|
581
|
+
|
|
582
|
+
@staticmethod
|
|
583
|
+
def _check_time(time):
|
|
584
|
+
"""Check that an individual time value is a ladybug Time."""
|
|
585
|
+
assert isinstance(time, Time), \
|
|
586
|
+
'Expected ladybug Time for ScheduleDay. Got {}.'.format(type(time))
|
|
587
|
+
|
|
588
|
+
@staticmethod
|
|
589
|
+
def _are_chronological(times):
|
|
590
|
+
"""Test whether a list of times is chronological."""
|
|
591
|
+
return all(times[i] < times[i + 1] for i in range(len(times) - 1))
|
|
592
|
+
|
|
593
|
+
def __len__(self):
|
|
594
|
+
return len(self.values)
|
|
595
|
+
|
|
596
|
+
def __getitem__(self, key):
|
|
597
|
+
return self.values[key]
|
|
598
|
+
|
|
599
|
+
def __iter__(self):
|
|
600
|
+
return iter(self.values)
|
|
601
|
+
|
|
602
|
+
def __key(self):
|
|
603
|
+
"""A tuple based on the object properties, useful for hashing."""
|
|
604
|
+
return (self.identifier,) + self.values + tuple(hash(t) for t in self.times) + \
|
|
605
|
+
(self.interpolate,)
|
|
606
|
+
|
|
607
|
+
def __hash__(self):
|
|
608
|
+
return hash(self.__key())
|
|
609
|
+
|
|
610
|
+
def __eq__(self, other):
|
|
611
|
+
return isinstance(other, ScheduleDay) and self.__key() == other.__key()
|
|
612
|
+
|
|
613
|
+
def __ne__(self, other):
|
|
614
|
+
return not self.__eq__(other)
|
|
615
|
+
|
|
616
|
+
def __copy__(self):
|
|
617
|
+
new_obj = ScheduleDay(self.identifier, self.values, self.times, self.interpolate)
|
|
618
|
+
new_obj._display_name = self._display_name
|
|
619
|
+
return new_obj
|
|
620
|
+
|
|
621
|
+
def ToString(self):
|
|
622
|
+
"""Overwrite .NET ToString."""
|
|
623
|
+
return self.__repr__()
|
|
624
|
+
|
|
625
|
+
def __repr__(self):
|
|
626
|
+
return self.to_idf()
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
"""Utilities to convert schedule dictionaries to Python objects."""
|
|
3
|
+
from honeybee_energy.schedule.ruleset import ScheduleRuleset
|
|
4
|
+
from honeybee_energy.schedule.fixedinterval import ScheduleFixedInterval
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
SCHEDULE_TYPES = ('ScheduleRuleset', 'ScheduleFixedInterval')
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def dict_to_schedule(sch_dict, raise_exception=True):
|
|
11
|
+
"""Get a Python object of any Schedule from a dictionary.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
sch_dict: A dictionary of any Honeybee energy schedules. Note
|
|
15
|
+
that this should be a non-abridged dictionary to be valid.
|
|
16
|
+
raise_exception: Boolean to note whether an exception should be raised
|
|
17
|
+
if the object is not identified as a schedule. Default: True.
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
A Python object derived from the input sch_dict.
|
|
21
|
+
"""
|
|
22
|
+
try: # get the type key from the dictionary
|
|
23
|
+
sch_type = sch_dict['type']
|
|
24
|
+
except KeyError:
|
|
25
|
+
raise ValueError('Schedule dictionary lacks required "type" key.')
|
|
26
|
+
|
|
27
|
+
if sch_type == 'ScheduleRuleset':
|
|
28
|
+
return ScheduleRuleset.from_dict(sch_dict)
|
|
29
|
+
elif sch_type == 'ScheduleFixedInterval':
|
|
30
|
+
return ScheduleFixedInterval.from_dict(sch_dict)
|
|
31
|
+
elif raise_exception:
|
|
32
|
+
raise ValueError('{} is not a recognized energy Schedule type'.format(sch_type))
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def dict_abridged_to_schedule(sch_dict, schedule_type_limits, raise_exception=True):
|
|
36
|
+
"""Get a Python object of any Schedule from an abridged dictionary.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
sch_dict: A dictionary of any Honeybee energy schedules. Note
|
|
40
|
+
that this should be a non-abridged dictionary to be valid.
|
|
41
|
+
schedule_type_limits: Dictionary of all schedule type limit objects that
|
|
42
|
+
might be used in the schedule with the type limit identifiers as the keys.
|
|
43
|
+
raise_exception: Boolean to note whether an exception should be raised
|
|
44
|
+
if the object is not identified as a schedule. Default: True.
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
A Python object derived from the input sch_dict.
|
|
48
|
+
"""
|
|
49
|
+
try: # get the type key from the dictionary
|
|
50
|
+
sch_type = sch_dict['type']
|
|
51
|
+
except KeyError:
|
|
52
|
+
raise ValueError('Schedule dictionary lacks required "type" key.')
|
|
53
|
+
|
|
54
|
+
if sch_type == 'ScheduleRulesetAbridged':
|
|
55
|
+
return ScheduleRuleset.from_dict_abridged(sch_dict, schedule_type_limits)
|
|
56
|
+
elif sch_type == 'ScheduleFixedIntervalAbridged':
|
|
57
|
+
return ScheduleFixedInterval.from_dict_abridged(sch_dict, schedule_type_limits)
|
|
58
|
+
elif raise_exception:
|
|
59
|
+
raise ValueError('{} is not a recognized energy Schedule type'.format(sch_type))
|