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,1867 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
"""Complete annual schedule object built from ScheduleDay and rules for applying them."""
|
|
3
|
+
from __future__ import division
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
|
|
7
|
+
from honeybee._lockable import lockable
|
|
8
|
+
from honeybee.typing import tuple_with_length, valid_ep_string
|
|
9
|
+
from ladybug.analysisperiod import AnalysisPeriod
|
|
10
|
+
from ladybug.datacollection import HourlyContinuousCollection
|
|
11
|
+
from ladybug.datatype.generic import GenericType
|
|
12
|
+
from ladybug.dt import Date, Time
|
|
13
|
+
from ladybug.header import Header
|
|
14
|
+
|
|
15
|
+
from ..reader import parse_idf_string, clean_idf_file_contents
|
|
16
|
+
from ..writer import generate_idf_string
|
|
17
|
+
from ..properties.extension import ScheduleRulesetProperties
|
|
18
|
+
from .day import ScheduleDay
|
|
19
|
+
from .rule import ScheduleRule
|
|
20
|
+
from .typelimit import ScheduleTypeLimit
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@lockable
|
|
24
|
+
class ScheduleRuleset(object):
|
|
25
|
+
"""A complete schedule assembled from ScheduleDay and ScheduleRules.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
identifier: Text string for a unique Schedule ID. Must be < 100 characters
|
|
29
|
+
and not contain any EnergyPlus special characters. This will be used to
|
|
30
|
+
identify the object across a model and in the exported IDF.
|
|
31
|
+
default_day_schedule: A ScheduleDay object that will be used for all
|
|
32
|
+
days where there is no ScheduleRule applied.
|
|
33
|
+
schedule_rules: A list of ScheduleRule objects that note exceptions
|
|
34
|
+
to the default_day_schedule. These rules should be ordered from
|
|
35
|
+
highest to lowest priority.
|
|
36
|
+
schedule_type_limit: A ScheduleTypeLimit object that will be used to
|
|
37
|
+
validate schedule values against upper/lower limits and assign units
|
|
38
|
+
to the schedule values. If None, no validation will occur.
|
|
39
|
+
summer_designday_schedule: A ScheduleDay object that will be used for
|
|
40
|
+
the summer design day (used to size the cooling system).
|
|
41
|
+
winter_designday_schedule: A ScheduleDay object that will be used for
|
|
42
|
+
the winter design day (used to size the heating system).
|
|
43
|
+
holiday_schedule: A ScheduleDay object that will be used for holidays.
|
|
44
|
+
|
|
45
|
+
Properties:
|
|
46
|
+
* identifier
|
|
47
|
+
* display_name
|
|
48
|
+
* default_day_schedule
|
|
49
|
+
* schedule_rules
|
|
50
|
+
* schedule_type_limit
|
|
51
|
+
* summer_designday_schedule
|
|
52
|
+
* winter_designday_schedule
|
|
53
|
+
* holiday_schedule
|
|
54
|
+
* day_schedules
|
|
55
|
+
* typical_day_schedules
|
|
56
|
+
* is_constant
|
|
57
|
+
* is_single_week
|
|
58
|
+
* user_data
|
|
59
|
+
"""
|
|
60
|
+
__slots__ = ('_identifier', '_display_name', '_default_day_schedule',
|
|
61
|
+
'_schedule_rules', '_holiday_schedule', '_summer_designday_schedule',
|
|
62
|
+
'_winter_designday_schedule', '_schedule_type_limit',
|
|
63
|
+
'_locked', '_properties', '_user_data')
|
|
64
|
+
_dow_text_to_int = {'sunday': 1, 'monday': 2, 'tuesday': 3, 'wednesday': 4,
|
|
65
|
+
'thursday': 2, 'friday': 3, 'saturday': 7}
|
|
66
|
+
_schedule_week_comments = (
|
|
67
|
+
'name', 'sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday',
|
|
68
|
+
'saturday', 'holiday', 'summer design day', 'winter design day',
|
|
69
|
+
'custom day 1', 'custom day 2')
|
|
70
|
+
|
|
71
|
+
def __init__(self, identifier, default_day_schedule, schedule_rules=None,
|
|
72
|
+
schedule_type_limit=None, holiday_schedule=None,
|
|
73
|
+
summer_designday_schedule=None, winter_designday_schedule=None):
|
|
74
|
+
"""Initialize Schedule Ruleset."""
|
|
75
|
+
self._locked = False # unlocked by default
|
|
76
|
+
self.identifier = identifier
|
|
77
|
+
self._display_name = None
|
|
78
|
+
self.default_day_schedule = default_day_schedule
|
|
79
|
+
self.schedule_rules = schedule_rules
|
|
80
|
+
self.schedule_type_limit = schedule_type_limit
|
|
81
|
+
self.holiday_schedule = holiday_schedule
|
|
82
|
+
self.summer_designday_schedule = summer_designday_schedule
|
|
83
|
+
self.winter_designday_schedule = winter_designday_schedule
|
|
84
|
+
|
|
85
|
+
# initialize properties for extensions and user data
|
|
86
|
+
self._properties = ScheduleRulesetProperties(self)
|
|
87
|
+
self._user_data = None
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def identifier(self):
|
|
91
|
+
"""Get or set the text string for schedule unique identifier."""
|
|
92
|
+
return self._identifier
|
|
93
|
+
|
|
94
|
+
@identifier.setter
|
|
95
|
+
def identifier(self, identifier):
|
|
96
|
+
self._identifier = valid_ep_string(identifier, 'schedule ruleset identifier')
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def display_name(self):
|
|
100
|
+
"""Get or set a string for the object name without any character restrictions.
|
|
101
|
+
|
|
102
|
+
If not set, this will be equal to the identifier.
|
|
103
|
+
"""
|
|
104
|
+
if self._display_name is None:
|
|
105
|
+
return self._identifier
|
|
106
|
+
return self._display_name
|
|
107
|
+
|
|
108
|
+
@display_name.setter
|
|
109
|
+
def display_name(self, value):
|
|
110
|
+
if value is not None:
|
|
111
|
+
try:
|
|
112
|
+
value = str(value)
|
|
113
|
+
except UnicodeEncodeError: # Python 2 machine lacking the character set
|
|
114
|
+
pass # keep it as unicode
|
|
115
|
+
self._display_name = value
|
|
116
|
+
|
|
117
|
+
@property
|
|
118
|
+
def default_day_schedule(self):
|
|
119
|
+
"""Get or set the DaySchedule object that will be used by default."""
|
|
120
|
+
return self._default_day_schedule
|
|
121
|
+
|
|
122
|
+
@default_day_schedule.setter
|
|
123
|
+
def default_day_schedule(self, schedule):
|
|
124
|
+
assert isinstance(schedule, ScheduleDay), 'Expected ScheduleDay for ' \
|
|
125
|
+
'ScheduleRuleset default_day_schedule. Got {}.'.format(type(schedule))
|
|
126
|
+
self._check_schedule_parent(schedule, 'default_day_schedule')
|
|
127
|
+
self._default_day_schedule = schedule
|
|
128
|
+
|
|
129
|
+
@property
|
|
130
|
+
def schedule_rules(self):
|
|
131
|
+
"""Get or set an array of ScheduleRules that note exceptions to the default.
|
|
132
|
+
|
|
133
|
+
These rules are ordered from highest priority to lowest priority meaning that,
|
|
134
|
+
if two rules cover the same date range and day of the week, the rule that comes
|
|
135
|
+
first in this list will take precedence. Following this logic, you typically
|
|
136
|
+
want rules that only apply for part of a year to precede rules that are
|
|
137
|
+
applied over the whole year. This way, the schedule over the whole year doesn't
|
|
138
|
+
overwrite the partial-year schedule underneath it.
|
|
139
|
+
"""
|
|
140
|
+
return tuple(self._schedule_rules)
|
|
141
|
+
|
|
142
|
+
@schedule_rules.setter
|
|
143
|
+
def schedule_rules(self, rules):
|
|
144
|
+
self._schedule_rules = self._check_schedule_rules(rules)
|
|
145
|
+
|
|
146
|
+
@property
|
|
147
|
+
def schedule_type_limit(self):
|
|
148
|
+
"""Get or set a ScheduleTypeLimit object used to assign units to schedule values.
|
|
149
|
+
"""
|
|
150
|
+
return self._schedule_type_limit
|
|
151
|
+
|
|
152
|
+
@schedule_type_limit.setter
|
|
153
|
+
def schedule_type_limit(self, schedule_type):
|
|
154
|
+
if schedule_type is not None:
|
|
155
|
+
assert isinstance(schedule_type, ScheduleTypeLimit), 'Expected ' \
|
|
156
|
+
'ScheduleTypeLimit for ScheduleRuleset schedule_type_limit. ' \
|
|
157
|
+
'Got {}.'.format(type(schedule_type))
|
|
158
|
+
self._schedule_type_limit = schedule_type
|
|
159
|
+
|
|
160
|
+
@property
|
|
161
|
+
def holiday_schedule(self):
|
|
162
|
+
"""Get or set the DaySchedule that will be used for holidays.
|
|
163
|
+
|
|
164
|
+
Note that, if this property is None, the default_day_schedule is
|
|
165
|
+
ultimately written into the IDF for the holidays.
|
|
166
|
+
"""
|
|
167
|
+
return self._holiday_schedule
|
|
168
|
+
|
|
169
|
+
@holiday_schedule.setter
|
|
170
|
+
def holiday_schedule(self, schedule):
|
|
171
|
+
if schedule is not None:
|
|
172
|
+
assert isinstance(schedule, ScheduleDay), 'Expected ScheduleDay for ' \
|
|
173
|
+
'ScheduleRuleset holiday_schedule. Got {}.'.format(
|
|
174
|
+
type(schedule))
|
|
175
|
+
self._check_schedule_parent(schedule, 'holiday_schedule')
|
|
176
|
+
self._holiday_schedule = schedule
|
|
177
|
+
|
|
178
|
+
@property
|
|
179
|
+
def summer_designday_schedule(self):
|
|
180
|
+
"""Get or set the DaySchedule that will be used for the summer design day.
|
|
181
|
+
|
|
182
|
+
Note that, if this property is None, the default_day_schedule is
|
|
183
|
+
ultimately written into the IDF for the summer design day.
|
|
184
|
+
"""
|
|
185
|
+
return self._summer_designday_schedule
|
|
186
|
+
|
|
187
|
+
@summer_designday_schedule.setter
|
|
188
|
+
def summer_designday_schedule(self, schedule):
|
|
189
|
+
if schedule is not None:
|
|
190
|
+
assert isinstance(schedule, ScheduleDay), 'Expected ScheduleDay for ' \
|
|
191
|
+
'ScheduleRuleset summer_designday_schedule. Got {}.'.format(
|
|
192
|
+
type(schedule))
|
|
193
|
+
self._check_schedule_parent(schedule, 'summer_designday_schedule')
|
|
194
|
+
self._summer_designday_schedule = schedule
|
|
195
|
+
|
|
196
|
+
@property
|
|
197
|
+
def winter_designday_schedule(self):
|
|
198
|
+
"""Get or set the DaySchedule that will be used for the winter design day.
|
|
199
|
+
|
|
200
|
+
Note that, if this property is None, the default_day_schedule is
|
|
201
|
+
ultimately written into the IDF for the winter design day.
|
|
202
|
+
"""
|
|
203
|
+
return self._winter_designday_schedule
|
|
204
|
+
|
|
205
|
+
@winter_designday_schedule.setter
|
|
206
|
+
def winter_designday_schedule(self, schedule):
|
|
207
|
+
if schedule is not None:
|
|
208
|
+
assert isinstance(schedule, ScheduleDay), 'Expected ScheduleDay for ' \
|
|
209
|
+
'ScheduleRuleset winter_designday_schedule. Got {}.'.format(
|
|
210
|
+
type(schedule))
|
|
211
|
+
self._check_schedule_parent(schedule, 'winter_designday_schedule')
|
|
212
|
+
self._winter_designday_schedule = schedule
|
|
213
|
+
|
|
214
|
+
@property
|
|
215
|
+
def day_schedules(self):
|
|
216
|
+
"""Get a list of all unique ScheduleDay objects used in this ScheduleRuleset."""
|
|
217
|
+
day_scheds = [self.default_day_schedule]
|
|
218
|
+
if self._summer_designday_schedule is not None and not \
|
|
219
|
+
self._instance_in_array(self._summer_designday_schedule, day_scheds):
|
|
220
|
+
day_scheds.append(self._summer_designday_schedule)
|
|
221
|
+
if self._winter_designday_schedule is not None and not \
|
|
222
|
+
self._instance_in_array(self._winter_designday_schedule, day_scheds):
|
|
223
|
+
day_scheds.append(self._winter_designday_schedule)
|
|
224
|
+
if self._holiday_schedule is not None and not \
|
|
225
|
+
self._instance_in_array(self._holiday_schedule, day_scheds):
|
|
226
|
+
day_scheds.append(self._holiday_schedule)
|
|
227
|
+
for rule in self.schedule_rules:
|
|
228
|
+
if not self._instance_in_array(rule._schedule_day, day_scheds):
|
|
229
|
+
day_scheds.append(rule._schedule_day)
|
|
230
|
+
return day_scheds
|
|
231
|
+
|
|
232
|
+
@property
|
|
233
|
+
def typical_day_schedules(self):
|
|
234
|
+
"""Get a list of all unique ScheduleDay objects used over the annual run period.
|
|
235
|
+
|
|
236
|
+
This excludes the schedules of the design days and the holiday schedule,
|
|
237
|
+
which gives a sense of the typical schedule over the year.
|
|
238
|
+
"""
|
|
239
|
+
day_scheds = [self.default_day_schedule]
|
|
240
|
+
for rule in self.schedule_rules:
|
|
241
|
+
if not self._instance_in_array(rule._schedule_day, day_scheds):
|
|
242
|
+
day_scheds.append(rule._schedule_day)
|
|
243
|
+
return day_scheds
|
|
244
|
+
|
|
245
|
+
@property
|
|
246
|
+
def is_constant(self):
|
|
247
|
+
"""Boolean noting whether the schedule is representable with a single value."""
|
|
248
|
+
return self.default_day_schedule.is_constant and self._schedule_rules == [] and \
|
|
249
|
+
self._summer_designday_schedule is None and \
|
|
250
|
+
self._winter_designday_schedule is None and self._holiday_schedule is None
|
|
251
|
+
|
|
252
|
+
@property
|
|
253
|
+
def is_single_week(self):
|
|
254
|
+
"""Boolean noting whether this schedule is representable with one week schedule.
|
|
255
|
+
"""
|
|
256
|
+
if self._schedule_rules == []:
|
|
257
|
+
return True
|
|
258
|
+
elif all([sch._start_doy == 1 and sch._end_doy == 365
|
|
259
|
+
for sch in self._schedule_rules]):
|
|
260
|
+
return True
|
|
261
|
+
return False
|
|
262
|
+
|
|
263
|
+
@property
|
|
264
|
+
def user_data(self):
|
|
265
|
+
"""Get or set an optional dictionary for additional meta data for this object.
|
|
266
|
+
|
|
267
|
+
This will be None until it has been set. All keys and values of this
|
|
268
|
+
dictionary should be of a standard Python type to ensure correct
|
|
269
|
+
serialization of the object to/from JSON (eg. str, float, int, list, dict)
|
|
270
|
+
"""
|
|
271
|
+
return self._user_data
|
|
272
|
+
|
|
273
|
+
@user_data.setter
|
|
274
|
+
def user_data(self, value):
|
|
275
|
+
if value is not None:
|
|
276
|
+
assert isinstance(value, dict), 'Expected dictionary for honeybee_energy' \
|
|
277
|
+
'object user_data. Got {}.'.format(type(value))
|
|
278
|
+
self._user_data = value
|
|
279
|
+
|
|
280
|
+
@property
|
|
281
|
+
def properties(self):
|
|
282
|
+
"""Get properties for extensions."""
|
|
283
|
+
return self._properties
|
|
284
|
+
|
|
285
|
+
def add_rule(self, rule):
|
|
286
|
+
"""Add a ScheduleRule to this ScheduleRuleset.
|
|
287
|
+
|
|
288
|
+
Note that adding a rule here will add it as highest priority in the full list
|
|
289
|
+
of schedule_rules, meaning it may overwrite other rules underneath it.
|
|
290
|
+
|
|
291
|
+
Args:
|
|
292
|
+
rule: A ScheduleRule object to be added to this ScheduleRuleset.
|
|
293
|
+
ScheduleRule objects note the exceptions to the default_day_schedule.
|
|
294
|
+
"""
|
|
295
|
+
self._check_rule(rule)
|
|
296
|
+
self._check_schedule_parent(rule.schedule_day, 'schedule_rule')
|
|
297
|
+
self._schedule_rules.insert(0, rule)
|
|
298
|
+
|
|
299
|
+
def remove_rule(self, rule_index):
|
|
300
|
+
"""Remove a ScheduleRule from the schedule by its index in schedule_rules.
|
|
301
|
+
|
|
302
|
+
Args:
|
|
303
|
+
rule_index: An integer for the index of the rule to remove.
|
|
304
|
+
"""
|
|
305
|
+
self._schedule_rules[rule_index].schedule_day._parent = None
|
|
306
|
+
del self._schedule_rules[rule_index]
|
|
307
|
+
|
|
308
|
+
def reorder_rule(self, rule_index, new_index=0):
|
|
309
|
+
"""Change the priority of a ScheduleRule in the full schedule_rules list.
|
|
310
|
+
|
|
311
|
+
Lower indices (ordered first) in the schedule_rules indicate the rule has
|
|
312
|
+
a higher priority.
|
|
313
|
+
|
|
314
|
+
Args:
|
|
315
|
+
rule_index: An integer for the index of the rule to reorder.
|
|
316
|
+
new_index: An integer for the new index of the rule. The default is 0,
|
|
317
|
+
which will re-insert the selected rule at the top of the
|
|
318
|
+
priority list.
|
|
319
|
+
"""
|
|
320
|
+
self._schedule_rules.insert(new_index, self._schedule_rules.pop(rule_index))
|
|
321
|
+
|
|
322
|
+
def values(self, timestep=1, start_date=Date(1, 1), end_date=Date(12, 31),
|
|
323
|
+
start_dow='Sunday', holidays=None, leap_year=False):
|
|
324
|
+
"""Get a list of sequential schedule values over the year at a given timestep.
|
|
325
|
+
|
|
326
|
+
Note that there are two possible ways that these values can be mapped to
|
|
327
|
+
corresponding times. See the ScheduleDay.values_at_timestep method
|
|
328
|
+
documentation for a complete description of these two interpretations.
|
|
329
|
+
|
|
330
|
+
Args:
|
|
331
|
+
timestep: An integer for the number of steps per hour at which to return
|
|
332
|
+
the resulting values.
|
|
333
|
+
start_date: An optional ladybug Date object for when to start the list
|
|
334
|
+
of values. Default: 1 Jan.
|
|
335
|
+
end_date: An optional ladybug Date object for when to end the list
|
|
336
|
+
of values. Default: 31 Dec.
|
|
337
|
+
start_dow: An optional text string for the starting day of the week.
|
|
338
|
+
Default: Sunday.
|
|
339
|
+
holidays: An optional list of ladybug Date objects for the holidays. For
|
|
340
|
+
any holiday in this list, schedule rules set to apply_holiday will
|
|
341
|
+
take effect.
|
|
342
|
+
leap_year: Boolean to note whether the generated values should be for a
|
|
343
|
+
leap year (True) or a non-leap year (False). Default: False.
|
|
344
|
+
"""
|
|
345
|
+
# get the values over the day for each of the ScheduleDay objects
|
|
346
|
+
sch_day_vals = [rule.schedule_day.values_at_timestep(timestep)
|
|
347
|
+
for rule in self._schedule_rules]
|
|
348
|
+
sch_day_vals.append(self.default_day_schedule.values_at_timestep(timestep))
|
|
349
|
+
hol_vals = None
|
|
350
|
+
if self.holiday_schedule is not None and holidays is not None:
|
|
351
|
+
hol_vals = self.holiday_schedule.values_at_timestep(timestep)
|
|
352
|
+
# ensure that everything is consistent across leap years
|
|
353
|
+
if start_date.leap_year is not leap_year:
|
|
354
|
+
start_date = Date(start_date.month, start_date.day, leap_year)
|
|
355
|
+
if end_date.leap_year is not leap_year:
|
|
356
|
+
end_date = Date(end_date.month, end_date.day, leap_year)
|
|
357
|
+
# ensure start date is before end date
|
|
358
|
+
assert start_date <= end_date, 'ScheduleRuleset values() start_date must come ' \
|
|
359
|
+
'before end_date. {} comes after {}.'.format(start_date, end_date)
|
|
360
|
+
# process the holidays if they are input
|
|
361
|
+
if holidays is not None:
|
|
362
|
+
hol_doy = []
|
|
363
|
+
for hol in holidays:
|
|
364
|
+
if hol.leap_year is not leap_year:
|
|
365
|
+
hol = Date(hol.month, hol.day, leap_year)
|
|
366
|
+
hol_doy.append(hol.doy)
|
|
367
|
+
else:
|
|
368
|
+
hol_doy = []
|
|
369
|
+
# process the start_dow into an integer.
|
|
370
|
+
dow = self._dow_text_to_int[start_dow.lower()]
|
|
371
|
+
# generate the full list of annual values
|
|
372
|
+
if not leap_year:
|
|
373
|
+
return self._get_sch_values(
|
|
374
|
+
sch_day_vals, dow, start_date, end_date, hol_doy, hol_vals)
|
|
375
|
+
else:
|
|
376
|
+
return self._get_sch_values_leap_year(
|
|
377
|
+
sch_day_vals, dow, start_date, end_date, hol_doy, hol_vals)
|
|
378
|
+
|
|
379
|
+
def data_collection(self, timestep=1, start_date=Date(1, 1), end_date=Date(12, 31),
|
|
380
|
+
start_dow='Sunday', holidays=None, leap_year=False):
|
|
381
|
+
"""Get a ladybug DataCollection representing this schedule at a given timestep.
|
|
382
|
+
|
|
383
|
+
Note that ladybug DataCollections always follow the "Ladybug Tools
|
|
384
|
+
Interpretation" of date time values as noted in the
|
|
385
|
+
ScheduleDay.values_at_timestep documentation.
|
|
386
|
+
|
|
387
|
+
Args:
|
|
388
|
+
timestep: An integer for the number of steps per hour at which to make
|
|
389
|
+
the resulting DataCollection.
|
|
390
|
+
start_date: An optional ladybug Date object for when to start the
|
|
391
|
+
DataCollection. Default: 1 Jan.
|
|
392
|
+
end_date: An optional ladybug Date object for when to end the
|
|
393
|
+
DataCollection. Default: 31 Dec.
|
|
394
|
+
start_dow: An optional text string for the starting day of the week.
|
|
395
|
+
Default: Sunday.
|
|
396
|
+
holidays: An optional list of ladybug Date objects for the holidays. For
|
|
397
|
+
any holiday in this list, schedule rules set to apply_holiday will
|
|
398
|
+
take effect.
|
|
399
|
+
leap_year: Boolean to note whether the generated values should be for a
|
|
400
|
+
leap year (True) or a non-leap year (False). Default: False.
|
|
401
|
+
"""
|
|
402
|
+
a_period = AnalysisPeriod(start_date.month, start_date.day, 0,
|
|
403
|
+
end_date.month, end_date.day, 23,
|
|
404
|
+
timestep, leap_year)
|
|
405
|
+
if self.schedule_type_limit is not None:
|
|
406
|
+
data_type = self.schedule_type_limit.data_type
|
|
407
|
+
unit = self.schedule_type_limit.unit
|
|
408
|
+
else:
|
|
409
|
+
unit = 'unknown'
|
|
410
|
+
data_type = GenericType('Unknown Data Type', unit)
|
|
411
|
+
header = Header(data_type, unit, a_period,
|
|
412
|
+
metadata={'schedule': self.identifier})
|
|
413
|
+
values = self.values(timestep, start_date, end_date, start_dow,
|
|
414
|
+
holidays, leap_year)
|
|
415
|
+
return HourlyContinuousCollection(header, values)
|
|
416
|
+
|
|
417
|
+
def shift_by_step(self, step_count=1, timestep=1):
|
|
418
|
+
"""Get a version of this object where the day_schedule values are shifted.
|
|
419
|
+
|
|
420
|
+
This is useful when attempting to derive a set of diversified schedules
|
|
421
|
+
from a single average schedule.
|
|
422
|
+
|
|
423
|
+
Args:
|
|
424
|
+
step_count: An integer for the number of timesteps at which the schedule
|
|
425
|
+
will be shifted. Positive values indicate a shift of values forward
|
|
426
|
+
in time while negative values indicate a shift backwards in
|
|
427
|
+
time. (Default: 1).
|
|
428
|
+
timestep: An integer for the number of timesteps per hour at which the
|
|
429
|
+
shifting is occurring. This must be a value between 1 and 60, which
|
|
430
|
+
is evenly divisible by 60. 1 indicates that each step is an hour
|
|
431
|
+
while 60 indicates that each step is a minute. (Default: 1).
|
|
432
|
+
"""
|
|
433
|
+
# shift all of the day schedules according to the inputs
|
|
434
|
+
day_scheds = self.day_schedules
|
|
435
|
+
shift_dict = {sch.identifier: sch.shift_by_step(step_count, timestep)
|
|
436
|
+
for sch in day_scheds}
|
|
437
|
+
# figure out where each of the shifted schedules belong
|
|
438
|
+
new_default = shift_dict[self.default_day_schedule.identifier]
|
|
439
|
+
new_summer = shift_dict[self._summer_designday_schedule.identifier] \
|
|
440
|
+
if self._summer_designday_schedule is not None else None
|
|
441
|
+
new_winter = shift_dict[self._winter_designday_schedule.identifier] \
|
|
442
|
+
if self._winter_designday_schedule is not None else None
|
|
443
|
+
new_holiday = shift_dict[self._holiday_schedule.identifier] \
|
|
444
|
+
if self._holiday_schedule is not None else None
|
|
445
|
+
new_rules = []
|
|
446
|
+
for rule in self.schedule_rules:
|
|
447
|
+
new_rule = rule.duplicate()
|
|
448
|
+
new_rule.schedule_day = shift_dict[rule.schedule_day.identifier]
|
|
449
|
+
new_rules.append(new_rule)
|
|
450
|
+
# return the shifted schedule
|
|
451
|
+
new_id = '{}_Shift_{}mins'.format(
|
|
452
|
+
self.identifier, int((60 / timestep) * step_count))
|
|
453
|
+
return ScheduleRuleset(
|
|
454
|
+
new_id, new_default, new_rules, self.schedule_type_limit, new_holiday,
|
|
455
|
+
new_summer, new_winter)
|
|
456
|
+
|
|
457
|
+
@classmethod
|
|
458
|
+
def from_constant_value(cls, identifier, value, schedule_type_limit=None):
|
|
459
|
+
"""Create a ScheduleRuleset fromm a single constant value.
|
|
460
|
+
|
|
461
|
+
Args:
|
|
462
|
+
identifier: Text string for a unique Schedule ID. Must be < 100 characters
|
|
463
|
+
and not contain any EnergyPlus special characters. This will be used to
|
|
464
|
+
identify the object across a model and in the exported IDF.
|
|
465
|
+
value: A single constant value to be applied throughout the whole year.
|
|
466
|
+
schedule_type_limit: A ScheduleTypeLimit object that will be used to
|
|
467
|
+
validate schedule values against upper/lower limits and assign
|
|
468
|
+
units to the schedule values.
|
|
469
|
+
"""
|
|
470
|
+
default_sched = ScheduleDay('{}_Day Schedule'.format(identifier), [value])
|
|
471
|
+
return cls(identifier, default_sched, None, schedule_type_limit)
|
|
472
|
+
|
|
473
|
+
@classmethod
|
|
474
|
+
def from_daily_values(cls, identifier, daily_values, timestep=1,
|
|
475
|
+
schedule_type_limit=None):
|
|
476
|
+
"""Create a ScheduleRuleset from a list of repeating daily values at a timestep.
|
|
477
|
+
|
|
478
|
+
Args:
|
|
479
|
+
identifier: Text string for a unique Schedule ID. Must be < 100 characters
|
|
480
|
+
and not contain any EnergyPlus special characters. This will be used to
|
|
481
|
+
identify the object across a model and in the exported IDF.
|
|
482
|
+
daily_values: A list of [24 * timestep] numbers for schedule values.
|
|
483
|
+
timestep: An integer for the number of steps per hour that the input
|
|
484
|
+
values correspond to. For example, if each value represents 30
|
|
485
|
+
minutes, the timestep is 2. For 15 minutes, it is 4. Default is 1,
|
|
486
|
+
meaning each value represents a single hour. Must be one of the
|
|
487
|
+
following: (1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30, 60).
|
|
488
|
+
schedule_type_limit: A ScheduleTypeLimit object that will be used to
|
|
489
|
+
validate schedule values against upper/lower limits and assign units
|
|
490
|
+
to the schedule values.
|
|
491
|
+
"""
|
|
492
|
+
default_sched = ScheduleDay.from_values_at_timestep(
|
|
493
|
+
'{}_Day Schedule'.format(identifier), daily_values, timestep)
|
|
494
|
+
return cls(identifier, default_sched, None, schedule_type_limit)
|
|
495
|
+
|
|
496
|
+
@classmethod
|
|
497
|
+
def from_week_daily_values(
|
|
498
|
+
cls, identifier, sunday_values, monday_values, tuesday_values,
|
|
499
|
+
wednesday_values, thursday_values, friday_values, saturday_values,
|
|
500
|
+
holiday_values, timestep=1, schedule_type_limit=None,
|
|
501
|
+
summer_designday_values=None, winter_designday_values=None):
|
|
502
|
+
"""Create a ScheduleRuleset from lists of daily values for each day of the week.
|
|
503
|
+
|
|
504
|
+
Args:
|
|
505
|
+
identifier: Text string for a unique Schedule ID. Must be < 100 characters
|
|
506
|
+
and not contain any EnergyPlus special characters. This will be used to
|
|
507
|
+
identify the object across a model and in the exported IDF.
|
|
508
|
+
sunday_values: A list of [24 * timestep] numerical values for Sundays.
|
|
509
|
+
monday_values: A list of [24 * timestep] numerical values for Mondays.
|
|
510
|
+
tuesday_values: A list of [24 * timestep] numerical values for Tuesdays.
|
|
511
|
+
wednesday_values: A list of [24 * timestep] numerical values for Wednesdays.
|
|
512
|
+
thursday_values: A list of [24 * timestep] numerical values for Thursdays.
|
|
513
|
+
friday_values: A list of [24 * timestep] numerical values for Fridays.
|
|
514
|
+
saturday_values: A list of [24 * timestep] numerical values for Saturdays.
|
|
515
|
+
holiday_values: A list of [24 * timestep] numerical values for Holidays.
|
|
516
|
+
timestep: An integer for the number of steps per hour that the input
|
|
517
|
+
values correspond to. For example, if each value represents 30
|
|
518
|
+
minutes, the timestep is 2. For 15 minutes, it is 4. Default is 1,
|
|
519
|
+
meaning each value represents a single hour. Must be one of the
|
|
520
|
+
following: (1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30, 60).
|
|
521
|
+
schedule_type_limit: A ScheduleTypeLimit object that will be used to
|
|
522
|
+
validate schedule values against upper/lower limits and assign
|
|
523
|
+
units to the schedule values. (Default: None).
|
|
524
|
+
summer_designday_values: A list of [24 * timestep] numerical values for
|
|
525
|
+
the summer design day. If None, the daily schedule with the highest
|
|
526
|
+
average value will be used unless the schedule_type_limit has a
|
|
527
|
+
Temperature unit_type, in which case it will be the daily schedule
|
|
528
|
+
with the lowest average value. (Default: None).
|
|
529
|
+
winter_designday_values: A list of [24 * timestep] numerical values for
|
|
530
|
+
the winter design day. If None, the daily schedule with the lowest
|
|
531
|
+
average value will be used unless the schedule_type_limit has a
|
|
532
|
+
Temperature unit_type, in which case it will be the daily schedule
|
|
533
|
+
with the highest average value. (Default: None).
|
|
534
|
+
"""
|
|
535
|
+
# process the rules for the days of the week
|
|
536
|
+
schedule_rules = []
|
|
537
|
+
applied_day_values = []
|
|
538
|
+
all_vals = (sunday_values, monday_values, tuesday_values, wednesday_values,
|
|
539
|
+
thursday_values, friday_values, saturday_values)
|
|
540
|
+
for i, day_vals in enumerate(all_vals):
|
|
541
|
+
if day_vals not in applied_day_values: # make a new ScheduleDay and rule
|
|
542
|
+
d_id = '{}_{}'.format(
|
|
543
|
+
identifier, cls._schedule_week_comments[i + 1].title())
|
|
544
|
+
sch_day = ScheduleDay.from_values_at_timestep(d_id, day_vals, timestep)
|
|
545
|
+
rule = ScheduleRule(sch_day)
|
|
546
|
+
rule.apply_day_by_dow(i + 1)
|
|
547
|
+
schedule_rules.append(rule)
|
|
548
|
+
applied_day_values.append(day_vals)
|
|
549
|
+
else: # edit one of the existing rules to apply it to the new day
|
|
550
|
+
for count, sch in enumerate(applied_day_values):
|
|
551
|
+
if day_vals == sch:
|
|
552
|
+
sch_rule_index = count
|
|
553
|
+
rule = schedule_rules[sch_rule_index]
|
|
554
|
+
rule.apply_day_by_dow(i + 1)
|
|
555
|
+
|
|
556
|
+
# get ScheduleDay for the holidays
|
|
557
|
+
holiday = ScheduleDay.from_values_at_timestep(
|
|
558
|
+
'{}_Hol'.format(identifier), holiday_values, timestep)
|
|
559
|
+
|
|
560
|
+
# get ScheduleDay for summer and winter design days
|
|
561
|
+
avg_day_vals = [sum(vals) / len(vals) for vals in applied_day_values]
|
|
562
|
+
temp_type = True if schedule_type_limit is not None and \
|
|
563
|
+
schedule_type_limit.unit_type == 'Temperature' else False
|
|
564
|
+
if summer_designday_values is None:
|
|
565
|
+
sch_i = avg_day_vals.index(min(avg_day_vals)) if temp_type \
|
|
566
|
+
else avg_day_vals.index(max(avg_day_vals))
|
|
567
|
+
summer = schedule_rules[sch_i]._schedule_day.duplicate()
|
|
568
|
+
summer.identifier = '{}_SmrDsn'.format(summer.identifier)
|
|
569
|
+
else:
|
|
570
|
+
summer = ScheduleDay.from_values_at_timestep(
|
|
571
|
+
'{}_SmrDsn'.format(identifier), summer_designday_values, timestep)
|
|
572
|
+
if winter_designday_values is None:
|
|
573
|
+
sch_i = avg_day_vals.index(max(avg_day_vals)) if temp_type \
|
|
574
|
+
else avg_day_vals.index(min(avg_day_vals))
|
|
575
|
+
winter = schedule_rules[sch_i]._schedule_day.duplicate()
|
|
576
|
+
winter.identifier = '{}_WntrDsn'.format(summer.identifier)
|
|
577
|
+
else:
|
|
578
|
+
winter = ScheduleDay.from_values_at_timestep(
|
|
579
|
+
'{}_WntrDsn'.format(identifier), winter_designday_values, timestep)
|
|
580
|
+
|
|
581
|
+
return cls(identifier, schedule_rules[0].schedule_day, schedule_rules[1:],
|
|
582
|
+
schedule_type_limit, holiday, summer, winter)
|
|
583
|
+
|
|
584
|
+
@classmethod
|
|
585
|
+
def from_week_day_schedules(
|
|
586
|
+
cls, identifier, sunday_schedule, monday_schedule, tuesday_schedule,
|
|
587
|
+
wednesday_schedule, thursday_schedule, friday_schedule, saturday_schedule,
|
|
588
|
+
holiday_schedule, summer_designday_schedule, winter_designday_schedule,
|
|
589
|
+
schedule_type_limit=None):
|
|
590
|
+
"""Create a ScheduleRuleset from ScheduleDay objects for each day of the week.
|
|
591
|
+
|
|
592
|
+
Args:
|
|
593
|
+
identifier: Text string for a unique Schedule ID. Must be < 100 characters
|
|
594
|
+
and not contain any EnergyPlus special characters. This will be used to
|
|
595
|
+
identify the object across a model and in the exported IDF.
|
|
596
|
+
sunday_schedule: A ScheduleDay for Sundays.
|
|
597
|
+
monday_schedule: A ScheduleDay for Mondays.
|
|
598
|
+
tuesday_schedule: A ScheduleDay for Tuesdays.
|
|
599
|
+
wednesday_schedule: A ScheduleDay for Wednesdays.
|
|
600
|
+
thursday_schedule: A ScheduleDay for Thursdays.
|
|
601
|
+
friday_schedule: A ScheduleDay for Fridays.
|
|
602
|
+
saturday_schedule: A ScheduleDay for Saturdays.
|
|
603
|
+
holiday_schedule: A ScheduleDay for Holidays.
|
|
604
|
+
summer_designday_schedule: A ScheduleDay for the summer design day.
|
|
605
|
+
winter_designday_schedule: A ScheduleDay for the winter design day.
|
|
606
|
+
schedule_type_limit: A ScheduleTypeLimit object that will be used to
|
|
607
|
+
validate schedule values against upper/lower limits and assign
|
|
608
|
+
units to the schedule values.
|
|
609
|
+
"""
|
|
610
|
+
schedule_rules = []
|
|
611
|
+
applied_day_ids = []
|
|
612
|
+
all_sched = (sunday_schedule, monday_schedule, tuesday_schedule,
|
|
613
|
+
wednesday_schedule, thursday_schedule, friday_schedule,
|
|
614
|
+
saturday_schedule)
|
|
615
|
+
for i, day_sch in enumerate(all_sched):
|
|
616
|
+
if day_sch.identifier not in applied_day_ids: # make a new rule
|
|
617
|
+
rule = ScheduleRule(day_sch)
|
|
618
|
+
rule.apply_day_by_dow(i + 1)
|
|
619
|
+
schedule_rules.append(rule)
|
|
620
|
+
applied_day_ids.append(day_sch.identifier)
|
|
621
|
+
else: # edit one of the existing rules to apply it to the new day
|
|
622
|
+
sch_rule_index = applied_day_ids.index(day_sch.identifier)
|
|
623
|
+
rule = schedule_rules[sch_rule_index]
|
|
624
|
+
rule.apply_day_by_dow(i + 1)
|
|
625
|
+
|
|
626
|
+
# get ScheduleDay for the holidays
|
|
627
|
+
if holiday_schedule.identifier in applied_day_ids: # avoid duplicate
|
|
628
|
+
holiday_schedule = holiday_schedule.duplicate()
|
|
629
|
+
holiday_schedule.identifier = '{}_Hol'.format(holiday_schedule.identifier)
|
|
630
|
+
|
|
631
|
+
# get ScheduleDay for summer and winter design days
|
|
632
|
+
if summer_designday_schedule.identifier in applied_day_ids: # avoid duplicate
|
|
633
|
+
summer_designday_schedule = summer_designday_schedule.duplicate()
|
|
634
|
+
summer_designday_schedule.identifier = \
|
|
635
|
+
'{}_SmrDsn'.format(summer_designday_schedule.identifier)
|
|
636
|
+
if winter_designday_schedule.identifier in applied_day_ids: # avoid duplicate
|
|
637
|
+
winter_designday_schedule = winter_designday_schedule.duplicate()
|
|
638
|
+
winter_designday_schedule.identifier = \
|
|
639
|
+
'{}_WntrDsn'.format(winter_designday_schedule.identifier)
|
|
640
|
+
return cls(identifier, schedule_rules[0].schedule_day, schedule_rules[1:],
|
|
641
|
+
schedule_type_limit, holiday_schedule, summer_designday_schedule,
|
|
642
|
+
winter_designday_schedule)
|
|
643
|
+
|
|
644
|
+
@classmethod
|
|
645
|
+
def from_idf(cls, year_idf_string, week_idf_strings, day_idf_strings,
|
|
646
|
+
type_idf_string=None):
|
|
647
|
+
"""Create a ScheduleRuleset from an EnergyPlus IDF text strings.
|
|
648
|
+
|
|
649
|
+
Args:
|
|
650
|
+
year_idf_string: A text string fully describing an EnergyPlus
|
|
651
|
+
Schedule:Year.
|
|
652
|
+
week_idf_strings: A list of text strings for all of the Schedule:Week
|
|
653
|
+
objects used in the Schedule:Year.
|
|
654
|
+
day_idf_strings: A list of text strings for all of the Schedule:Day
|
|
655
|
+
objects used in the week_idf_strings.
|
|
656
|
+
type_idf_string: An optional text string for the ScheduleTypeLimits.
|
|
657
|
+
If None, the resulting schedule will have no ScheduleTypeLimit.
|
|
658
|
+
"""
|
|
659
|
+
# process the schedule components
|
|
660
|
+
day_schedule_dict = cls._idf_day_schedule_dictionary(day_idf_strings)
|
|
661
|
+
week_sch_dict, week_dd_dict = cls._idf_week_schedule_dictionary(
|
|
662
|
+
week_idf_strings, day_schedule_dict)
|
|
663
|
+
schedule_type = ScheduleTypeLimit.from_idf(type_idf_string) if type_idf_string \
|
|
664
|
+
is not None else None
|
|
665
|
+
|
|
666
|
+
# use the year schedule to bring it all together
|
|
667
|
+
year_sch = parse_idf_string(year_idf_string)
|
|
668
|
+
all_rules = []
|
|
669
|
+
for i in range(2, len(year_sch), 5):
|
|
670
|
+
rules = week_sch_dict[year_sch[i]]
|
|
671
|
+
st_date = Date(int(year_sch[i + 1]), int(year_sch[i + 2]))
|
|
672
|
+
end_date = Date(int(year_sch[i + 3]), int(year_sch[i + 4]))
|
|
673
|
+
for rule in rules:
|
|
674
|
+
rule.start_date = st_date
|
|
675
|
+
rule.end_date = end_date
|
|
676
|
+
all_rules.extend(rules)
|
|
677
|
+
default_day_schedule = all_rules[0].schedule_day
|
|
678
|
+
holiday_sch, summer_dd_sch, winter_dd_sch = week_dd_dict[year_sch[2]]
|
|
679
|
+
sched = cls(year_sch[0], default_day_schedule, all_rules[1:], schedule_type)
|
|
680
|
+
cls._apply_designdays_with_check(
|
|
681
|
+
sched, holiday_sch, summer_dd_sch, winter_dd_sch)
|
|
682
|
+
return sched
|
|
683
|
+
|
|
684
|
+
@classmethod
|
|
685
|
+
def from_idf_constant(cls, idf_string, type_idf_string=None):
|
|
686
|
+
"""Create a ScheduleRuleset from an EnergyPlus Schedule:Constant string.
|
|
687
|
+
|
|
688
|
+
Args:
|
|
689
|
+
idf_string: A text string fully describing an EnergyPlus Schedule:Constant.
|
|
690
|
+
type_idf_string: An optional text string for the ScheduleTypeLimits.
|
|
691
|
+
If None, the resulting schedule will have no ScheduleTypeLimit.
|
|
692
|
+
"""
|
|
693
|
+
const_sch = parse_idf_string(idf_string)
|
|
694
|
+
sched_val = float(const_sch[2]) if const_sch[2] != '' else 0
|
|
695
|
+
schedule_type = ScheduleTypeLimit.from_idf(type_idf_string) if type_idf_string \
|
|
696
|
+
is not None else None
|
|
697
|
+
return ScheduleRuleset.from_constant_value(
|
|
698
|
+
const_sch[0], sched_val, schedule_type)
|
|
699
|
+
|
|
700
|
+
@classmethod
|
|
701
|
+
def from_dict(cls, data):
|
|
702
|
+
"""Create a ScheduleRuleset from a dictionary.
|
|
703
|
+
|
|
704
|
+
Note that the dictionary must be a non-abridged version for this
|
|
705
|
+
classmethod to work.
|
|
706
|
+
|
|
707
|
+
Args:
|
|
708
|
+
data: ScheduleRuleset dictionary following the format below.
|
|
709
|
+
|
|
710
|
+
.. code-block:: python
|
|
711
|
+
|
|
712
|
+
{
|
|
713
|
+
"type": 'ScheduleRuleset',
|
|
714
|
+
"identifier": 'Office_Occ_900_1700_weekends',
|
|
715
|
+
"display_name": 'Office Occupancy',
|
|
716
|
+
"day_schedules": [], # Array of ScheduleDay dictionary representations
|
|
717
|
+
"default_day_schedule": str, # ScheduleDay identifier
|
|
718
|
+
"schedule_rules": [], # list of ScheduleRuleAbridged dictionaries
|
|
719
|
+
"schedule_type_limit": {}, # ScheduleTypeLimit dictionary representation
|
|
720
|
+
"holiday_schedule": str, # ScheduleDay identifier
|
|
721
|
+
"summer_designday_schedule": str, # ScheduleDay identifier
|
|
722
|
+
"winter_designday_schedule": str # ScheduleDay identifier
|
|
723
|
+
}
|
|
724
|
+
"""
|
|
725
|
+
assert data['type'] == 'ScheduleRuleset', \
|
|
726
|
+
'Expected ScheduleRuleset. Got {}.'.format(data['type'])
|
|
727
|
+
|
|
728
|
+
sch_day_dict = {}
|
|
729
|
+
for day_sch in data['day_schedules']:
|
|
730
|
+
sch_day_dict[day_sch['identifier']] = ScheduleDay.from_dict(day_sch)
|
|
731
|
+
|
|
732
|
+
default_sched = sch_day_dict[data['default_day_schedule']]
|
|
733
|
+
rules = None
|
|
734
|
+
if 'schedule_rules' in data and data['schedule_rules'] is not None:
|
|
735
|
+
rules = []
|
|
736
|
+
for rule in data['schedule_rules']:
|
|
737
|
+
sch_day = sch_day_dict[rule['schedule_day']]
|
|
738
|
+
rules.append(ScheduleRule.from_dict_abridged(rule, sch_day))
|
|
739
|
+
holiday_sched = None
|
|
740
|
+
if 'holiday_schedule' in data and data['holiday_schedule'] is not None:
|
|
741
|
+
holiday_sched = sch_day_dict[data['holiday_schedule']]
|
|
742
|
+
summer_sched = None
|
|
743
|
+
if 'summer_designday_schedule' in data and \
|
|
744
|
+
data['summer_designday_schedule'] is not None:
|
|
745
|
+
summer_sched = sch_day_dict[data['summer_designday_schedule']]
|
|
746
|
+
winter_sched = None
|
|
747
|
+
if 'winter_designday_schedule' in data and \
|
|
748
|
+
data['winter_designday_schedule'] is not None:
|
|
749
|
+
winter_sched = sch_day_dict[data['winter_designday_schedule']]
|
|
750
|
+
|
|
751
|
+
sched_type = None
|
|
752
|
+
if 'schedule_type_limit' in data and data['schedule_type_limit'] is not None:
|
|
753
|
+
sched_type = ScheduleTypeLimit.from_dict(data['schedule_type_limit'])
|
|
754
|
+
|
|
755
|
+
new_obj = cls(data['identifier'], default_sched, rules, sched_type,
|
|
756
|
+
holiday_sched, summer_sched, winter_sched)
|
|
757
|
+
if 'display_name' in data and data['display_name'] is not None:
|
|
758
|
+
new_obj.display_name = data['display_name']
|
|
759
|
+
if 'user_data' in data and data['user_data'] is not None:
|
|
760
|
+
new_obj.user_data = data['user_data']
|
|
761
|
+
if 'properties' in data and data['properties'] is not None:
|
|
762
|
+
new_obj.properties._load_extension_attr_from_dict(data['properties'])
|
|
763
|
+
return new_obj
|
|
764
|
+
|
|
765
|
+
@classmethod
|
|
766
|
+
def from_dict_abridged(cls, data, schedule_type_limits):
|
|
767
|
+
"""Create a ScheduleRuleset from an abridged dictionary.
|
|
768
|
+
|
|
769
|
+
Args:
|
|
770
|
+
data: ScheduleRulesetAbridged dictionary.
|
|
771
|
+
schedule_type_limits: A dictionary with identifiers of schedule type limits
|
|
772
|
+
as keys and Python schedule type limit objects as values.
|
|
773
|
+
|
|
774
|
+
.. code-block:: python
|
|
775
|
+
|
|
776
|
+
{
|
|
777
|
+
"type": 'ScheduleRulesetAbridged',
|
|
778
|
+
"identifier": 'Office_Occ_900_1700_weekends',
|
|
779
|
+
"display_name": 'Office Occupancy',
|
|
780
|
+
"day_schedules": [], # Array of ScheduleDay dictionary representations
|
|
781
|
+
"default_day_schedule": str, # ScheduleDay identifier
|
|
782
|
+
"schedule_rules": [], # list of ScheduleRuleAbridged dictionaries
|
|
783
|
+
"schedule_type_limit": str, # ScheduleTypeLimit identifier
|
|
784
|
+
"holiday_schedule": str, # ScheduleDay identifier
|
|
785
|
+
"summer_designday_schedule": str, # ScheduleDay identifier
|
|
786
|
+
"winter_designday_schedule": str # ScheduleDay identifier
|
|
787
|
+
}
|
|
788
|
+
"""
|
|
789
|
+
assert data['type'] == 'ScheduleRulesetAbridged', \
|
|
790
|
+
'Expected ScheduleRulesetAbridged. Got {}.'.format(data['type'])
|
|
791
|
+
|
|
792
|
+
data = data.copy() # copy original dictionary so we don't edit it
|
|
793
|
+
typ_lim = None
|
|
794
|
+
if 'schedule_type_limit' in data:
|
|
795
|
+
typ_lim = data['schedule_type_limit']
|
|
796
|
+
data['schedule_type_limit'] = None
|
|
797
|
+
data['type'] = 'ScheduleRuleset'
|
|
798
|
+
schedule = cls.from_dict(data)
|
|
799
|
+
schedule.schedule_type_limit = schedule_type_limits[typ_lim] if \
|
|
800
|
+
typ_lim is not None else None
|
|
801
|
+
if 'display_name' in data and data['display_name'] is not None:
|
|
802
|
+
schedule.display_name = data['display_name']
|
|
803
|
+
if 'user_data' in data and data['user_data'] is not None:
|
|
804
|
+
schedule.user_data = data['user_data']
|
|
805
|
+
if 'properties' in data and data['properties'] is not None:
|
|
806
|
+
schedule.properties._load_extension_attr_from_dict(data['properties'])
|
|
807
|
+
return schedule
|
|
808
|
+
|
|
809
|
+
def to_rules(self, start_date, end_date):
|
|
810
|
+
"""Get all of rules needed to implement this ScheduleRuleset over a date range.
|
|
811
|
+
|
|
812
|
+
This is useful when you want to apply this entire ScheduleRuleset over a
|
|
813
|
+
particular time period of another ScheduleRuleset.
|
|
814
|
+
|
|
815
|
+
Args:
|
|
816
|
+
start_date: A ladybug Date object for the start of the period that rules
|
|
817
|
+
should be obtained.
|
|
818
|
+
end_date: A ladybug Date object for the end of the period that rules
|
|
819
|
+
should be obtained.
|
|
820
|
+
"""
|
|
821
|
+
# check the date inputs
|
|
822
|
+
ScheduleRule._check_date(start_date)
|
|
823
|
+
ScheduleRule._check_date(end_date)
|
|
824
|
+
st_doy = ScheduleRule._doy_non_leap_year(start_date)
|
|
825
|
+
end_doy = ScheduleRule._doy_non_leap_year(end_date)
|
|
826
|
+
assert start_date <= end_date, \
|
|
827
|
+
'Start date must be before end date for ScheduleRuleset.to_rules().'
|
|
828
|
+
|
|
829
|
+
# assemble all of the rules already applied to this ScheduleRuleset
|
|
830
|
+
rules = []
|
|
831
|
+
for rule in self._schedule_rules:
|
|
832
|
+
if not rule.is_reversed:
|
|
833
|
+
if (rule._start_doy < st_doy and rule._end_doy < st_doy) or \
|
|
834
|
+
(rule._start_doy > st_doy and rule._end_doy > end_doy):
|
|
835
|
+
pass # no overlap with input period
|
|
836
|
+
else:
|
|
837
|
+
new_rule = rule.duplicate()
|
|
838
|
+
if rule._start_doy < st_doy:
|
|
839
|
+
new_rule.start_date = start_date
|
|
840
|
+
if rule._end_doy > end_doy:
|
|
841
|
+
new_rule.end_date = end_date
|
|
842
|
+
rules.append(new_rule)
|
|
843
|
+
else:
|
|
844
|
+
if rule._start_doy <= end_doy:
|
|
845
|
+
new_rule1 = rule.duplicate()
|
|
846
|
+
new_rule1.end_date = end_doy
|
|
847
|
+
rules.append(new_rule1)
|
|
848
|
+
if rule._end_doy >= st_doy:
|
|
849
|
+
new_rule2 = rule.duplicate()
|
|
850
|
+
new_rule2.start_date = st_doy
|
|
851
|
+
rules.append(new_rule2)
|
|
852
|
+
|
|
853
|
+
# add the default_day_schedule for all days not covered by rules
|
|
854
|
+
default_rule = ScheduleRule(self.default_day_schedule.duplicate(),
|
|
855
|
+
start_date=start_date, end_date=end_date)
|
|
856
|
+
for dow in range(7):
|
|
857
|
+
for rule in rules:
|
|
858
|
+
if rule.week_apply_tuple[dow]:
|
|
859
|
+
break
|
|
860
|
+
else: # no rule applies; use default_day_schedule.
|
|
861
|
+
default_rule.apply_day_by_dow(dow + 1)
|
|
862
|
+
rules.append(default_rule)
|
|
863
|
+
|
|
864
|
+
return rules
|
|
865
|
+
|
|
866
|
+
def to_idf(self):
|
|
867
|
+
"""IDF string representation of the schedule.
|
|
868
|
+
|
|
869
|
+
Note that this method only outputs Schedule:Year and Schedule:Week objects
|
|
870
|
+
(or a Schedule:Constant object if applicable). However, to write the full
|
|
871
|
+
schedule into an IDF, the schedules's day_schedules must also be
|
|
872
|
+
written as well as the ScheduleTypeLimit object.
|
|
873
|
+
|
|
874
|
+
The method is set up this way primarily to give better control over the export
|
|
875
|
+
process. For example, you usually want to see if there are other schedules
|
|
876
|
+
in a model using the same ScheduleTypeLimit object and then write it into
|
|
877
|
+
the IDF only once rather than writing it multiple times for each schedule
|
|
878
|
+
that references it. ScheduleDay objects can often follow a similar logic
|
|
879
|
+
where the same ScheduleDay objects are used by multiple ScheduleRulesets.
|
|
880
|
+
|
|
881
|
+
Returns:
|
|
882
|
+
A tuple with two elements
|
|
883
|
+
|
|
884
|
+
- year_schedule: Text string representation of the Schedule:Year
|
|
885
|
+
describing this schedule. This will be a Schedule:Constant if this
|
|
886
|
+
schedule can be described as such.
|
|
887
|
+
|
|
888
|
+
- week_schedules: A list of Schedule:Week:Daily test strings that are
|
|
889
|
+
referenced in the year_schedule. Will be None when year_schedule is
|
|
890
|
+
a Schedule:Constant.
|
|
891
|
+
|
|
892
|
+
.. code-block:: shell
|
|
893
|
+
|
|
894
|
+
Schedule:Year,
|
|
895
|
+
test_name, !- Name
|
|
896
|
+
'Availability', !- Schedule Type Limits Name
|
|
897
|
+
avail_schedge1, !- Schedule:Week Name 1
|
|
898
|
+
1, !- Start Month 1
|
|
899
|
+
1, !- Start Day 1
|
|
900
|
+
12, !- End Month 1
|
|
901
|
+
31; !- End Day 1
|
|
902
|
+
"""
|
|
903
|
+
# beginning fields used for all schedules
|
|
904
|
+
year_fields = [self.identifier]
|
|
905
|
+
shc_typ = self._schedule_type_limit.identifier if \
|
|
906
|
+
self._schedule_type_limit is not None else ''
|
|
907
|
+
year_fields.append(shc_typ)
|
|
908
|
+
year_comments = ['schedule name', 'schedule type limits']
|
|
909
|
+
|
|
910
|
+
# check if this schedule can simply be represented with a Schedule:Constant
|
|
911
|
+
if self.is_constant:
|
|
912
|
+
year_fields.append(self.default_day_schedule[0])
|
|
913
|
+
year_comments.append('value')
|
|
914
|
+
year_schedule = generate_idf_string(
|
|
915
|
+
'Schedule:Constant', year_fields, year_comments)
|
|
916
|
+
return year_schedule, None
|
|
917
|
+
|
|
918
|
+
# prepare to create a full Schedule:Year
|
|
919
|
+
date_comments = ['start month {}', 'start day {}', 'end month {}', 'end day {}']
|
|
920
|
+
week_schedules = []
|
|
921
|
+
|
|
922
|
+
if self.is_single_week: # create the only one week schedule
|
|
923
|
+
wk_sch, wk_sch_id = \
|
|
924
|
+
self._idf_week_schedule_from_rule_indices(range(len(self)), 1)
|
|
925
|
+
week_schedules.append(wk_sch)
|
|
926
|
+
yr_wk_s_ids = [wk_sch_id]
|
|
927
|
+
yr_wk_dt_range = [[Date(1, 1), Date(12, 31)]]
|
|
928
|
+
else: # create a set of week schedules throughout the year
|
|
929
|
+
# loop through 365 days of the year to find unique combinations of rules
|
|
930
|
+
rules_each_day = []
|
|
931
|
+
for doy in range(1, 366):
|
|
932
|
+
rules_on_doy = tuple(i for i, rule in enumerate(self._schedule_rules)
|
|
933
|
+
if rule.does_rule_apply_doy(doy))
|
|
934
|
+
rules_each_day.append(rules_on_doy)
|
|
935
|
+
unique_rule_sets = set(rules_each_day)
|
|
936
|
+
# check if any combination yield the same week schedule and remove duplicates
|
|
937
|
+
week_tuples = [tuple(self._get_week_list(rule_set))
|
|
938
|
+
for rule_set in unique_rule_sets]
|
|
939
|
+
unique_week_tuples = list(set(week_tuples))
|
|
940
|
+
# create the unique week schedules from the combinations of rules
|
|
941
|
+
week_sched_ids = []
|
|
942
|
+
for i, week_list in enumerate(unique_week_tuples):
|
|
943
|
+
wk_schedule, wk_sch_id = \
|
|
944
|
+
self._idf_week_schedule_from_week_list(week_list, i + 1)
|
|
945
|
+
week_schedules.append(wk_schedule)
|
|
946
|
+
week_sched_ids.append(wk_sch_id)
|
|
947
|
+
# create a dictionary mapping unique rule index lists to week schedule ids
|
|
948
|
+
rule_set_map = {}
|
|
949
|
+
for rule_i, week_list in zip(unique_rule_sets, week_tuples):
|
|
950
|
+
unique_week_i = unique_week_tuples.index(week_list)
|
|
951
|
+
rule_set_map[rule_i] = week_sched_ids[unique_week_i]
|
|
952
|
+
# loop through all 365 days of the year to find when rules change
|
|
953
|
+
yr_wk_s_ids = []
|
|
954
|
+
yr_wk_dt_range = []
|
|
955
|
+
prev_week_sched = None
|
|
956
|
+
for doy in range(1, 366):
|
|
957
|
+
week_sched = rule_set_map[rules_each_day[doy - 1]]
|
|
958
|
+
if week_sched != prev_week_sched: # change to a new rule set
|
|
959
|
+
yr_wk_s_ids.append(week_sched)
|
|
960
|
+
if doy != 1:
|
|
961
|
+
yr_wk_dt_range[-1].append(Date.from_doy(doy - 1))
|
|
962
|
+
yr_wk_dt_range.append([Date.from_doy(doy)])
|
|
963
|
+
else:
|
|
964
|
+
yr_wk_dt_range.append([Date(1, 1)])
|
|
965
|
+
prev_week_sched = week_sched
|
|
966
|
+
yr_wk_dt_range[-1].append(Date(12, 31))
|
|
967
|
+
|
|
968
|
+
# create the year fields and comments
|
|
969
|
+
for i, (wk_sch_id, dt_range) in enumerate(zip(yr_wk_s_ids, yr_wk_dt_range)):
|
|
970
|
+
year_fields.append(wk_sch_id)
|
|
971
|
+
count = i + 1
|
|
972
|
+
year_comments.append('week schedule name {}'.format(count))
|
|
973
|
+
year_fields.extend([dt_range[0].month, dt_range[0].day,
|
|
974
|
+
dt_range[1].month, dt_range[1].day])
|
|
975
|
+
for com in date_comments:
|
|
976
|
+
year_comments.append(com.format(count))
|
|
977
|
+
|
|
978
|
+
year_schedule = generate_idf_string('Schedule:Year', year_fields, year_comments)
|
|
979
|
+
return year_schedule, week_schedules
|
|
980
|
+
|
|
981
|
+
def to_dict(self, abridged=False):
|
|
982
|
+
"""Schedule Ruleset dictionary representation.
|
|
983
|
+
|
|
984
|
+
Args:
|
|
985
|
+
abridged: Boolean to note whether the full dictionary describing the
|
|
986
|
+
object should be returned (False) or just an abridged version (True),
|
|
987
|
+
which only specifies the identifier of the ScheduleTypeLimit.
|
|
988
|
+
Default: False.
|
|
989
|
+
"""
|
|
990
|
+
# required properties
|
|
991
|
+
base = {'type': 'ScheduleRuleset'} if not \
|
|
992
|
+
abridged else {'type': 'ScheduleRulesetAbridged'}
|
|
993
|
+
base['identifier'] = self.identifier
|
|
994
|
+
base['day_schedules'] = [sch_day.to_dict() for sch_day in self.day_schedules]
|
|
995
|
+
base['default_day_schedule'] = self.default_day_schedule.identifier
|
|
996
|
+
|
|
997
|
+
# optional properties
|
|
998
|
+
if len(self._schedule_rules) != 0:
|
|
999
|
+
base['schedule_rules'] = \
|
|
1000
|
+
[rule.to_dict(True) for rule in self._schedule_rules]
|
|
1001
|
+
if self._holiday_schedule is not None:
|
|
1002
|
+
base['holiday_schedule'] = self._holiday_schedule.identifier
|
|
1003
|
+
if self._summer_designday_schedule is not None:
|
|
1004
|
+
base['summer_designday_schedule'] = \
|
|
1005
|
+
self._summer_designday_schedule.identifier
|
|
1006
|
+
if self._winter_designday_schedule is not None:
|
|
1007
|
+
base['winter_designday_schedule'] = \
|
|
1008
|
+
self._winter_designday_schedule.identifier
|
|
1009
|
+
|
|
1010
|
+
# optional properties that can be abridged
|
|
1011
|
+
if self._schedule_type_limit is not None:
|
|
1012
|
+
if not abridged:
|
|
1013
|
+
base['schedule_type_limit'] = self._schedule_type_limit.to_dict()
|
|
1014
|
+
else:
|
|
1015
|
+
base['schedule_type_limit'] = self._schedule_type_limit.identifier
|
|
1016
|
+
if self._display_name is not None:
|
|
1017
|
+
base['display_name'] = self.display_name
|
|
1018
|
+
if self._user_data is not None:
|
|
1019
|
+
base['user_data'] = self.user_data
|
|
1020
|
+
prop_dict = self.properties.to_dict()
|
|
1021
|
+
if prop_dict is not None:
|
|
1022
|
+
base['properties'] = prop_dict
|
|
1023
|
+
return base
|
|
1024
|
+
|
|
1025
|
+
def duplicate(self):
|
|
1026
|
+
"""Get a copy of this object."""
|
|
1027
|
+
return self.__copy__()
|
|
1028
|
+
|
|
1029
|
+
def lock(self):
|
|
1030
|
+
"""The lock() method also locks the ScheduleDay and ScheduleRule objects."""
|
|
1031
|
+
self._locked = True
|
|
1032
|
+
self._default_day_schedule.lock()
|
|
1033
|
+
if self._holiday_schedule is not None:
|
|
1034
|
+
self._holiday_schedule.lock()
|
|
1035
|
+
if self._summer_designday_schedule is not None:
|
|
1036
|
+
self._summer_designday_schedule.lock()
|
|
1037
|
+
if self._winter_designday_schedule is not None:
|
|
1038
|
+
self._winter_designday_schedule.lock()
|
|
1039
|
+
for rule in self._schedule_rules:
|
|
1040
|
+
rule.lock()
|
|
1041
|
+
|
|
1042
|
+
def unlock(self):
|
|
1043
|
+
"""The unlock() method also unlocks the ScheduleDay and ScheduleRule objects."""
|
|
1044
|
+
self._locked = False
|
|
1045
|
+
self._default_day_schedule.unlock()
|
|
1046
|
+
if self._holiday_schedule is not None:
|
|
1047
|
+
self._holiday_schedule.unlock()
|
|
1048
|
+
if self._summer_designday_schedule is not None:
|
|
1049
|
+
self._summer_designday_schedule.unlock()
|
|
1050
|
+
if self._winter_designday_schedule is not None:
|
|
1051
|
+
self._winter_designday_schedule.unlock()
|
|
1052
|
+
for rule in self._schedule_rules:
|
|
1053
|
+
rule.unlock()
|
|
1054
|
+
|
|
1055
|
+
@staticmethod
|
|
1056
|
+
def extract_all_from_idf_file(idf_file, import_compact=False):
|
|
1057
|
+
"""Extract all ScheduleRuleset objects from an EnergyPlus IDF file.
|
|
1058
|
+
|
|
1059
|
+
Args:
|
|
1060
|
+
idf_file: A path to an IDF file containing objects for Schedule:Year and
|
|
1061
|
+
corresponding Schedule:Week and Schedule:Day objects. The Schedule:Year
|
|
1062
|
+
will be used to assemble all of these into a ScheduleRuleset.
|
|
1063
|
+
import_compact: Boolean to note whether to parse Schedule:Compact (True)
|
|
1064
|
+
or not (False).
|
|
1065
|
+
Default: False.
|
|
1066
|
+
|
|
1067
|
+
Returns:
|
|
1068
|
+
schedules --
|
|
1069
|
+
A list of all Schedule:Year objects in the IDF file as honeybee_energy
|
|
1070
|
+
ScheduleRuleset objects.
|
|
1071
|
+
"""
|
|
1072
|
+
# read the file and remove lines of comments
|
|
1073
|
+
file_contents = clean_idf_file_contents(idf_file)
|
|
1074
|
+
# extract all of the ScheduleDay objects
|
|
1075
|
+
day_pattern1 = re.compile(r"(?i)(Schedule:Day:Interval,[\s\S]*?;)")
|
|
1076
|
+
day_pattern2 = re.compile(r"(?i)(Schedule:Day:Hourly,[\s\S]*?;)")
|
|
1077
|
+
day_pattern3 = re.compile(r"(?i)(Schedule:Day:List,[\s\S]*?;)")
|
|
1078
|
+
day_sch_str = day_pattern1.findall(file_contents) + \
|
|
1079
|
+
day_pattern2.findall(file_contents) + day_pattern3.findall(file_contents)
|
|
1080
|
+
day_schedule_dict = ScheduleRuleset._idf_day_schedule_dictionary(day_sch_str)
|
|
1081
|
+
# extract all of the Schedule:Week objects
|
|
1082
|
+
week_pattern_1 = re.compile(r"(?i)(Schedule:Week:Daily,[\s\S]*?;)")
|
|
1083
|
+
week_pattern_2 = re.compile(r"(?i)(Schedule:Week:Compact,[\s\S]*?;)")
|
|
1084
|
+
week_sch_str = week_pattern_1.findall(file_contents) + \
|
|
1085
|
+
week_pattern_2.findall(file_contents)
|
|
1086
|
+
week_sch_dict, week_dd_dict = ScheduleRuleset._idf_week_schedule_dictionary(
|
|
1087
|
+
week_sch_str, day_schedule_dict)
|
|
1088
|
+
# extract all of the ScheduleTypeLimit objects
|
|
1089
|
+
type_pattern = re.compile(r"(?i)(ScheduleTypeLimits,[\s\S]*?;)")
|
|
1090
|
+
sch_type_str = type_pattern.findall(file_contents)
|
|
1091
|
+
sch_type_dict = ScheduleRuleset._idf_schedule_type_dictionary(sch_type_str)
|
|
1092
|
+
# extract all of the Schedule:Year objects and convert to ScheduleRuleset
|
|
1093
|
+
year_pattern = re.compile(r"(?i)(Schedule:Year,[\s\S]*?;)")
|
|
1094
|
+
year_props = tuple(parse_idf_string(idf_string) for
|
|
1095
|
+
idf_string in year_pattern.findall(file_contents))
|
|
1096
|
+
# extract all of the Schedule:Constant objects and convert to ScheduleRuleset
|
|
1097
|
+
constant_pattern = re.compile(r"(?i)(Schedule:Constant,[\s\S]*?;)")
|
|
1098
|
+
constant_props = tuple(parse_idf_string(idf_string) for
|
|
1099
|
+
idf_string in constant_pattern.findall(file_contents))
|
|
1100
|
+
# extract all of the Schedule:Compact objects and convert to ScheduleRuleset
|
|
1101
|
+
conpact_pattern = re.compile(r"(?i)(Schedule:Compact,[\s\S]*?;)")
|
|
1102
|
+
compact_props = (
|
|
1103
|
+
tuple(
|
|
1104
|
+
parse_idf_string(idf_string)
|
|
1105
|
+
for idf_string in conpact_pattern.findall(file_contents)
|
|
1106
|
+
)
|
|
1107
|
+
if import_compact # only if user chooses so.
|
|
1108
|
+
else []
|
|
1109
|
+
)
|
|
1110
|
+
|
|
1111
|
+
# compile all of the ScheduleRuleset objects from extracted properties
|
|
1112
|
+
schedules = []
|
|
1113
|
+
for year_sch in year_props:
|
|
1114
|
+
# gather the rules
|
|
1115
|
+
all_rules = []
|
|
1116
|
+
for i in range(2, len(year_sch), 5):
|
|
1117
|
+
rules = week_sch_dict[year_sch[i]]
|
|
1118
|
+
st_date = Date(int(year_sch[i + 1]), int(year_sch[i + 2]))
|
|
1119
|
+
end_date = Date(int(year_sch[i + 3]), int(year_sch[i + 4]))
|
|
1120
|
+
for rule in rules:
|
|
1121
|
+
rule.end_date = end_date
|
|
1122
|
+
rule.start_date = st_date
|
|
1123
|
+
all_rules.extend(rules)
|
|
1124
|
+
# gather the other day schedules
|
|
1125
|
+
holiday_sch, summer_dd_sch, winter_dd_sch = week_dd_dict[year_sch[2]]
|
|
1126
|
+
schedule_type = sch_type_dict[year_sch[1]] if year_sch[1] != '' else None
|
|
1127
|
+
# check to be sure the schedule days don't already have a parent
|
|
1128
|
+
for rule in all_rules:
|
|
1129
|
+
if rule.schedule_day._parent is not None:
|
|
1130
|
+
rule.schedule_day = rule.schedule_day.duplicate()
|
|
1131
|
+
if holiday_sch._parent is not None:
|
|
1132
|
+
holiday_sch = holiday_sch.duplicate()
|
|
1133
|
+
if summer_dd_sch._parent is not None:
|
|
1134
|
+
summer_dd_sch = summer_dd_sch.duplicate()
|
|
1135
|
+
if winter_dd_sch._parent is not None:
|
|
1136
|
+
winter_dd_sch = summer_dd_sch.duplicate()
|
|
1137
|
+
# create the ScheduleRuleset
|
|
1138
|
+
default_day_schedule = all_rules[0].schedule_day
|
|
1139
|
+
sch_ruleset = ScheduleRuleset(
|
|
1140
|
+
year_sch[0], default_day_schedule, all_rules[1:], schedule_type)
|
|
1141
|
+
ScheduleRuleset._apply_designdays_with_check(
|
|
1142
|
+
sch_ruleset, holiday_sch, summer_dd_sch, winter_dd_sch)
|
|
1143
|
+
schedules.append(sch_ruleset)
|
|
1144
|
+
for const_sch in constant_props:
|
|
1145
|
+
sched_val = float(const_sch[2]) if const_sch[2] != '' else 0
|
|
1146
|
+
schedule_type = sch_type_dict[const_sch[1]] if const_sch[1] != '' else None
|
|
1147
|
+
sch_ruleset = ScheduleRuleset.from_constant_value(
|
|
1148
|
+
const_sch[0], sched_val, schedule_type)
|
|
1149
|
+
schedules.append(sch_ruleset)
|
|
1150
|
+
for compact_sch in compact_props:
|
|
1151
|
+
schedule_type = (
|
|
1152
|
+
sch_type_dict[compact_sch[1]] if compact_sch[1] != "" else None
|
|
1153
|
+
)
|
|
1154
|
+
schedule_rules = []
|
|
1155
|
+
holiday_schedule = None
|
|
1156
|
+
winter_designday_schedule = None
|
|
1157
|
+
summer_designday_schedule = None
|
|
1158
|
+
start_date = Date.from_doy(1)
|
|
1159
|
+
end_date = Date.from_doy(365)
|
|
1160
|
+
untils = [Time(0, 0)] # initialize list of until times.
|
|
1161
|
+
n = -1 # initialize the n-th until time
|
|
1162
|
+
rules = [] # initialize list of rules.
|
|
1163
|
+
for field in compact_sch[2:]:
|
|
1164
|
+
field = field.lower()
|
|
1165
|
+
if "through" in field:
|
|
1166
|
+
# Each `through` field generates a new ScheduleRule
|
|
1167
|
+
# initialize rule with ScheduleDay as placeholder.
|
|
1168
|
+
rule = ScheduleRule(ScheduleDay(compact_sch[0], [0], [Time(0, 0)]))
|
|
1169
|
+
|
|
1170
|
+
# start_date is either Jan 1st or end_date from previous
|
|
1171
|
+
# `through` field
|
|
1172
|
+
start_date = (
|
|
1173
|
+
start_date if end_date == Date.from_doy(365) else end_date
|
|
1174
|
+
)
|
|
1175
|
+
|
|
1176
|
+
_, date = field.split(":") # get end_date from field
|
|
1177
|
+
month, day = date.split("/")
|
|
1178
|
+
end_date = Date.from_dict({"month": int(month), "day": int(day)})
|
|
1179
|
+
elif "for" in field:
|
|
1180
|
+
# reset values for new set; each `for` is a new rule
|
|
1181
|
+
n = -1 # reset the n-th until time
|
|
1182
|
+
untils = [Time(0, 0)] # reset list of until times.
|
|
1183
|
+
rules = [] # reset list of rules for this `for` field.
|
|
1184
|
+
|
|
1185
|
+
# Create a rule; all different `if` statements because we want to
|
|
1186
|
+
# catch more than one case,
|
|
1187
|
+
# eg. `For: Sunday Holidays AllOtherDays, !- Field 54`
|
|
1188
|
+
def create_rule_for(apply_to):
|
|
1189
|
+
"""Return ScheduleRule with the `apply_to` rule set to True."""
|
|
1190
|
+
apply_to_attr_map = {
|
|
1191
|
+
"alldays": "apply_all",
|
|
1192
|
+
"weekdays": "apply_weekday",
|
|
1193
|
+
"weekends": "apply_weekend",
|
|
1194
|
+
"sunday": "apply_sunday",
|
|
1195
|
+
"monday": "apply_monday",
|
|
1196
|
+
"tuesday": "apply_tuesday",
|
|
1197
|
+
"wednesday": "apply_wednesday",
|
|
1198
|
+
"thursday": "apply_thursday",
|
|
1199
|
+
"friday": "apply_friday",
|
|
1200
|
+
"saturday": "apply_saturday",
|
|
1201
|
+
}
|
|
1202
|
+
rule = ScheduleRule(ScheduleDay(apply_to, [0], [Time(0, 0)]))
|
|
1203
|
+
setattr(rule, apply_to_attr_map[apply_to], True)
|
|
1204
|
+
return rule
|
|
1205
|
+
|
|
1206
|
+
# Create rules for regular days
|
|
1207
|
+
for rule_name in [
|
|
1208
|
+
"alldays",
|
|
1209
|
+
"weekdays",
|
|
1210
|
+
"weekends",
|
|
1211
|
+
"sunday",
|
|
1212
|
+
"monday",
|
|
1213
|
+
"tuesday",
|
|
1214
|
+
"wednesday",
|
|
1215
|
+
"thursday",
|
|
1216
|
+
"friday",
|
|
1217
|
+
"saturday",
|
|
1218
|
+
]:
|
|
1219
|
+
if rule_name in field:
|
|
1220
|
+
rule = create_rule_for(rule_name)
|
|
1221
|
+
rules.append(rule)
|
|
1222
|
+
|
|
1223
|
+
# Create rules for holidays and designdays
|
|
1224
|
+
if "holiday" in field:
|
|
1225
|
+
rule = ScheduleRule(ScheduleDay("holiday", [0], [Time(0, 0)]))
|
|
1226
|
+
holiday_schedule = rule.schedule_day
|
|
1227
|
+
rules.append(rule)
|
|
1228
|
+
if "summerdesignday" in field:
|
|
1229
|
+
rule = ScheduleRule(
|
|
1230
|
+
ScheduleDay("summerdesignday", [0], [Time(0, 0)])
|
|
1231
|
+
)
|
|
1232
|
+
summer_designday_schedule = rule.schedule_day
|
|
1233
|
+
rules.append(rule)
|
|
1234
|
+
if "winterdesignday" in field:
|
|
1235
|
+
rule = ScheduleRule(
|
|
1236
|
+
ScheduleDay("winterdesignday", [0], [Time(0, 0)])
|
|
1237
|
+
)
|
|
1238
|
+
winter_designday_schedule = rule.schedule_day
|
|
1239
|
+
rules.append(rule)
|
|
1240
|
+
if "allotherdays" in field:
|
|
1241
|
+
rule = ScheduleRule(
|
|
1242
|
+
ScheduleDay("allotherdays", [0], [Time(0, 0)])
|
|
1243
|
+
)
|
|
1244
|
+
apply_mtx = [rul.week_apply_tuple for rul in schedule_rules]
|
|
1245
|
+
if not apply_mtx: # situation if allotherdays is the only rule.
|
|
1246
|
+
rule.apply_all = True
|
|
1247
|
+
else:
|
|
1248
|
+
for j, dow in enumerate(zip(*apply_mtx)):
|
|
1249
|
+
if not any(dow):
|
|
1250
|
+
rule.apply_day_by_dow(j + 1)
|
|
1251
|
+
rules.append(rule)
|
|
1252
|
+
|
|
1253
|
+
for rule in rules:
|
|
1254
|
+
# for each created rule in this `for` field, add rules to
|
|
1255
|
+
# ScheduleRuleset list of rules and set start_date and end_date.
|
|
1256
|
+
if len(rule.days_applied) != 0:
|
|
1257
|
+
schedule_rules.append(rule)
|
|
1258
|
+
|
|
1259
|
+
# set range for rule (from previous `through` field)
|
|
1260
|
+
rule.start_date = start_date
|
|
1261
|
+
rule.end_date = end_date
|
|
1262
|
+
elif "until" in field:
|
|
1263
|
+
_, hour, min = field.split(":") # get hour and minutes
|
|
1264
|
+
|
|
1265
|
+
# value is applied `until` a certain Time, but `ScheduleDay` is
|
|
1266
|
+
# applied `from` a certain Time. Also, Time is 0:23 Hours while IDF
|
|
1267
|
+
# is 1:24 Hours.
|
|
1268
|
+
until = Time(int(hour) - 1, int(min)) # to 0:23 Hours repr
|
|
1269
|
+
untils.append(until)
|
|
1270
|
+
|
|
1271
|
+
# increment n
|
|
1272
|
+
n += 1
|
|
1273
|
+
elif "interpolate" in field:
|
|
1274
|
+
# Set interpolate on all rules for this `for` field
|
|
1275
|
+
_, interpolate = field.split(":")
|
|
1276
|
+
for rule in rules:
|
|
1277
|
+
rule.schedule_day.interpolate = interpolate != "no"
|
|
1278
|
+
else:
|
|
1279
|
+
begin = untils[n] # index list of `until` times
|
|
1280
|
+
for rule in rules:
|
|
1281
|
+
# apply field value for each created rules; try to replace the
|
|
1282
|
+
# placeholder value first, else add the value.
|
|
1283
|
+
try:
|
|
1284
|
+
rule.schedule_day.replace_value_by_time(begin, float(field))
|
|
1285
|
+
except ValueError:
|
|
1286
|
+
rule.schedule_day.add_value(float(field), begin)
|
|
1287
|
+
default_day_schedule = schedule_rules[0].schedule_day
|
|
1288
|
+
sch_ruleset = ScheduleRuleset(
|
|
1289
|
+
default_day_schedule=default_day_schedule,
|
|
1290
|
+
identifier=compact_sch[0],
|
|
1291
|
+
schedule_type_limit=schedule_type,
|
|
1292
|
+
schedule_rules=schedule_rules[1:]
|
|
1293
|
+
)
|
|
1294
|
+
ScheduleRuleset._apply_designdays_with_check(
|
|
1295
|
+
sch_ruleset,
|
|
1296
|
+
holiday_schedule,
|
|
1297
|
+
summer_designday_schedule,
|
|
1298
|
+
winter_designday_schedule)
|
|
1299
|
+
schedules.append(sch_ruleset)
|
|
1300
|
+
return schedules
|
|
1301
|
+
|
|
1302
|
+
@staticmethod
|
|
1303
|
+
def average_schedules(identifier, schedules, weights=None, timestep_resolution=1):
|
|
1304
|
+
"""Create a ScheduleRuleset that is a weighted average between ScheduleRulesets.
|
|
1305
|
+
|
|
1306
|
+
Args:
|
|
1307
|
+
identifier: Text string for a unique ID for the new unique ScheduleRuleset.
|
|
1308
|
+
Must be < 100 characters and not contain any EnergyPlus special
|
|
1309
|
+
characters. This will be used to identify the object across a
|
|
1310
|
+
model and in the exported IDF.
|
|
1311
|
+
schedules: A list of ScheduleRuleset objects that will be averaged together
|
|
1312
|
+
to make a new ScheduleRuleset.
|
|
1313
|
+
weights: An optional list of fractional numbers with the same length
|
|
1314
|
+
as the input schedules that sum to 1. These will be used to weight
|
|
1315
|
+
each of the ScheduleRuleset objects in the resulting average schedule.
|
|
1316
|
+
If None, the individual schedules will be weighted equally.
|
|
1317
|
+
timestep_resolution: An optional integer for the timestep resolution
|
|
1318
|
+
at which the schedules will be averaged. Any schedule details
|
|
1319
|
+
smaller than this timestep will be lost in the averaging process.
|
|
1320
|
+
Default: 1.
|
|
1321
|
+
"""
|
|
1322
|
+
# check the inputs
|
|
1323
|
+
assert isinstance(schedules, (list, tuple)), 'Expected a list of ' \
|
|
1324
|
+
'ScheduleRuleset objects for average_schedules. ' \
|
|
1325
|
+
'Got {}.'.format(type(schedules))
|
|
1326
|
+
if weights is None:
|
|
1327
|
+
weight = 1 / len(schedules)
|
|
1328
|
+
weights = [weight for i in schedules]
|
|
1329
|
+
else:
|
|
1330
|
+
weights = tuple_with_length(weights, len(schedules), float,
|
|
1331
|
+
'average schedules weights')
|
|
1332
|
+
assert abs(sum(weights) - 1.0) <= 1e-9, 'Average schedule weights must ' \
|
|
1333
|
+
'sum to 1. Got {}.'.format(sum(weights))
|
|
1334
|
+
|
|
1335
|
+
# if all input schedules are single week, the averaging process is a lot simpler
|
|
1336
|
+
if all([sched.is_single_week for sched in schedules]):
|
|
1337
|
+
rule_indices = [range(len(sched)) for sched in schedules]
|
|
1338
|
+
return ScheduleRuleset._get_avg_week(
|
|
1339
|
+
identifier, schedules, weights, timestep_resolution, rule_indices)
|
|
1340
|
+
else:
|
|
1341
|
+
# loop through 365 days of the year to find unique combinations of rules
|
|
1342
|
+
rules_each_day = []
|
|
1343
|
+
for doy in range(1, 366):
|
|
1344
|
+
rules_on_doy = tuple(tuple(
|
|
1345
|
+
i for i, rule in enumerate(sched._schedule_rules)
|
|
1346
|
+
if rule._start_doy <= doy <= rule._end_doy)
|
|
1347
|
+
for sched in schedules)
|
|
1348
|
+
rules_each_day.append(rules_on_doy)
|
|
1349
|
+
unique_rule_sets = set(rules_each_day)
|
|
1350
|
+
# create the average week schedules from the unique combinations of rules
|
|
1351
|
+
week_schedules = []
|
|
1352
|
+
for i, rule_indices in enumerate(unique_rule_sets):
|
|
1353
|
+
week_identifier = '{}_{}'.format(identifier, i)
|
|
1354
|
+
week_sched = ScheduleRuleset._get_avg_week(
|
|
1355
|
+
week_identifier, schedules, weights,
|
|
1356
|
+
timestep_resolution, rule_indices
|
|
1357
|
+
)
|
|
1358
|
+
week_schedules.append(week_sched)
|
|
1359
|
+
# combine the week schedules into rules
|
|
1360
|
+
final_rules, holiday_sch, summer_dd_sch, winter_dd_sch = \
|
|
1361
|
+
ScheduleRuleset._combine_week_schedules(
|
|
1362
|
+
unique_rule_sets, week_schedules, rules_each_day)
|
|
1363
|
+
# add all rules to a final ScheduleRuleset
|
|
1364
|
+
default_day_schedule = final_rules[0].schedule_day
|
|
1365
|
+
schedule_type = schedules[0].schedule_type_limit
|
|
1366
|
+
return ScheduleRuleset(
|
|
1367
|
+
identifier, default_day_schedule, final_rules[1:], schedule_type,
|
|
1368
|
+
holiday_sch, summer_dd_sch, winter_dd_sch)
|
|
1369
|
+
|
|
1370
|
+
@staticmethod
|
|
1371
|
+
def max_schedules(identifier, schedules, timestep_resolution=1):
|
|
1372
|
+
"""Create a ScheduleRuleset that uses the maximum value between ScheduleRulesets.
|
|
1373
|
+
|
|
1374
|
+
This is useful for resolving a set of schedules where the highest value
|
|
1375
|
+
should govern, like when several ventilation schedules must be merged
|
|
1376
|
+
into one.
|
|
1377
|
+
|
|
1378
|
+
Args:
|
|
1379
|
+
identifier: Text string for a unique ID for the new unique ScheduleRuleset.
|
|
1380
|
+
Must be < 100 characters and not contain any EnergyPlus special
|
|
1381
|
+
characters. This will be used to identify the object across a
|
|
1382
|
+
model and in the exported IDF.
|
|
1383
|
+
schedules: A list of ScheduleRuleset objects that will have the maximum
|
|
1384
|
+
value taken at each timestep to make a new ScheduleRuleset.
|
|
1385
|
+
timestep_resolution: An optional integer for the timestep resolution
|
|
1386
|
+
at which the schedules will be combined. Any schedule details
|
|
1387
|
+
smaller than this timestep will be lost in the process. (Default: 1).
|
|
1388
|
+
"""
|
|
1389
|
+
# check the inputs
|
|
1390
|
+
assert isinstance(schedules, (list, tuple)), 'Expected a list of ' \
|
|
1391
|
+
'ScheduleRuleset objects for max_schedules. Got {}.'.format(type(schedules))
|
|
1392
|
+
|
|
1393
|
+
# if all input schedules are single week, the process is a lot simpler
|
|
1394
|
+
if all([sched.is_single_week for sched in schedules]):
|
|
1395
|
+
rule_indices = [range(len(sched)) for sched in schedules]
|
|
1396
|
+
return ScheduleRuleset._get_ext_week(
|
|
1397
|
+
identifier, schedules, max, timestep_resolution, rule_indices)
|
|
1398
|
+
else:
|
|
1399
|
+
# loop through 365 days of the year to find unique combinations of rules
|
|
1400
|
+
rules_each_day = []
|
|
1401
|
+
for doy in range(1, 366):
|
|
1402
|
+
rules_on_doy = tuple(tuple(
|
|
1403
|
+
i for i, rule in enumerate(sched._schedule_rules)
|
|
1404
|
+
if rule._start_doy <= doy <= rule._end_doy)
|
|
1405
|
+
for sched in schedules)
|
|
1406
|
+
rules_each_day.append(rules_on_doy)
|
|
1407
|
+
unique_rule_sets = set(rules_each_day)
|
|
1408
|
+
# create the combined week schedules from the unique combinations of rules
|
|
1409
|
+
week_schedules = []
|
|
1410
|
+
for i, rule_indices in enumerate(unique_rule_sets):
|
|
1411
|
+
week_identifier = '{}_{}'.format(identifier, i)
|
|
1412
|
+
week_sched = ScheduleRuleset._get_ext_week(
|
|
1413
|
+
week_identifier, schedules, max,
|
|
1414
|
+
timestep_resolution, rule_indices
|
|
1415
|
+
)
|
|
1416
|
+
week_schedules.append(week_sched)
|
|
1417
|
+
# combine the week schedules into rules
|
|
1418
|
+
final_rules, holiday_sch, summer_dd_sch, winter_dd_sch = \
|
|
1419
|
+
ScheduleRuleset._combine_week_schedules(
|
|
1420
|
+
unique_rule_sets, week_schedules, rules_each_day)
|
|
1421
|
+
# add all rules to a final ScheduleRuleset
|
|
1422
|
+
default_day_schedule = final_rules[0].schedule_day
|
|
1423
|
+
schedule_type = schedules[0].schedule_type_limit
|
|
1424
|
+
return ScheduleRuleset(
|
|
1425
|
+
identifier, default_day_schedule, final_rules[1:], schedule_type,
|
|
1426
|
+
holiday_sch, summer_dd_sch, winter_dd_sch)
|
|
1427
|
+
|
|
1428
|
+
@staticmethod
|
|
1429
|
+
def min_schedules(identifier, schedules, timestep_resolution=1):
|
|
1430
|
+
"""Create a ScheduleRuleset that uses the minimum value between ScheduleRulesets.
|
|
1431
|
+
|
|
1432
|
+
This is useful for resolving a set of schedules where the lowest value
|
|
1433
|
+
should govern, like when several cooling setpoint schedules must be merged
|
|
1434
|
+
into one.
|
|
1435
|
+
|
|
1436
|
+
Args:
|
|
1437
|
+
identifier: Text string for a unique ID for the new unique ScheduleRuleset.
|
|
1438
|
+
Must be < 100 characters and not contain any EnergyPlus special
|
|
1439
|
+
characters. This will be used to identify the object across a
|
|
1440
|
+
model and in the exported IDF.
|
|
1441
|
+
schedules: A list of ScheduleRuleset objects that will have the minimum
|
|
1442
|
+
value taken at each timestep to make a new ScheduleRuleset.
|
|
1443
|
+
timestep_resolution: An optional integer for the timestep resolution
|
|
1444
|
+
at which the schedules will be combined. Any schedule details
|
|
1445
|
+
smaller than this timestep will be lost in the process. (Default: 1).
|
|
1446
|
+
"""
|
|
1447
|
+
# check the inputs
|
|
1448
|
+
assert isinstance(schedules, (list, tuple)), 'Expected a list of ' \
|
|
1449
|
+
'ScheduleRuleset objects for min_schedules. Got {}.'.format(type(schedules))
|
|
1450
|
+
|
|
1451
|
+
# if all input schedules are single week, the process is a lot simpler
|
|
1452
|
+
if all([sched.is_single_week for sched in schedules]):
|
|
1453
|
+
rule_indices = [range(len(sched)) for sched in schedules]
|
|
1454
|
+
return ScheduleRuleset._get_ext_week(
|
|
1455
|
+
identifier, schedules, min, timestep_resolution, rule_indices)
|
|
1456
|
+
else:
|
|
1457
|
+
# loop through 365 days of the year to find unique combinations of rules
|
|
1458
|
+
rules_each_day = []
|
|
1459
|
+
for doy in range(1, 366):
|
|
1460
|
+
rules_on_doy = tuple(tuple(
|
|
1461
|
+
i for i, rule in enumerate(sched._schedule_rules)
|
|
1462
|
+
if rule._start_doy <= doy <= rule._end_doy)
|
|
1463
|
+
for sched in schedules)
|
|
1464
|
+
rules_each_day.append(rules_on_doy)
|
|
1465
|
+
unique_rule_sets = set(rules_each_day)
|
|
1466
|
+
# create the combined week schedules from the unique combinations of rules
|
|
1467
|
+
week_schedules = []
|
|
1468
|
+
for i, rule_indices in enumerate(unique_rule_sets):
|
|
1469
|
+
week_identifier = '{}_{}'.format(identifier, i)
|
|
1470
|
+
week_sched = ScheduleRuleset._get_ext_week(
|
|
1471
|
+
week_identifier, schedules, min,
|
|
1472
|
+
timestep_resolution, rule_indices
|
|
1473
|
+
)
|
|
1474
|
+
week_schedules.append(week_sched)
|
|
1475
|
+
# combine the week schedules into rules
|
|
1476
|
+
final_rules, holiday_sch, summer_dd_sch, winter_dd_sch = \
|
|
1477
|
+
ScheduleRuleset._combine_week_schedules(
|
|
1478
|
+
unique_rule_sets, week_schedules, rules_each_day)
|
|
1479
|
+
# add all rules to a final ScheduleRuleset
|
|
1480
|
+
default_day_schedule = final_rules[0].schedule_day
|
|
1481
|
+
schedule_type = schedules[0].schedule_type_limit
|
|
1482
|
+
return ScheduleRuleset(
|
|
1483
|
+
identifier, default_day_schedule, final_rules[1:], schedule_type,
|
|
1484
|
+
holiday_sch, summer_dd_sch, winter_dd_sch)
|
|
1485
|
+
|
|
1486
|
+
def _get_sch_values(self, sch_day_vals, dow, start_date, end_date,
|
|
1487
|
+
hol_doy, hol_vals):
|
|
1488
|
+
"""Get a list of values over a date range for a typical year."""
|
|
1489
|
+
values = []
|
|
1490
|
+
for doy in range(start_date.doy, end_date.doy + 1):
|
|
1491
|
+
if dow > 7: # reset the day of the week to sunday
|
|
1492
|
+
dow = 1
|
|
1493
|
+
if doy in hol_doy:
|
|
1494
|
+
if hol_vals is not None:
|
|
1495
|
+
values.extend(hol_vals)
|
|
1496
|
+
else: # no holiday values; use default_day_schedule.
|
|
1497
|
+
values.extend(sch_day_vals[-1])
|
|
1498
|
+
else:
|
|
1499
|
+
for i, rule in enumerate(self._schedule_rules): # see if rules apply
|
|
1500
|
+
if rule.does_rule_apply(doy, dow):
|
|
1501
|
+
values.extend(sch_day_vals[i])
|
|
1502
|
+
break
|
|
1503
|
+
else: # no rule applies; use default_day_schedule.
|
|
1504
|
+
values.extend(sch_day_vals[-1])
|
|
1505
|
+
dow += 1
|
|
1506
|
+
return values
|
|
1507
|
+
|
|
1508
|
+
def _get_sch_values_leap_year(self, sch_day_vals, dow, start_date, end_date,
|
|
1509
|
+
hol_doy, hol_vals):
|
|
1510
|
+
"""Get a list of values over a date range for a leap year."""
|
|
1511
|
+
values = []
|
|
1512
|
+
for doy in range(start_date.doy, end_date.doy + 1):
|
|
1513
|
+
if dow > 7: # reset the day of the week to sunday
|
|
1514
|
+
dow = 1
|
|
1515
|
+
if doy in hol_doy:
|
|
1516
|
+
if hol_vals is not None:
|
|
1517
|
+
values.extend(hol_vals)
|
|
1518
|
+
else: # no holiday values; use default_day_schedule.
|
|
1519
|
+
values.extend(sch_day_vals[-1])
|
|
1520
|
+
else:
|
|
1521
|
+
for i, rule in enumerate(self._schedule_rules): # see if rules apply
|
|
1522
|
+
if rule.does_rule_apply_leap_year(doy, dow):
|
|
1523
|
+
values.extend(sch_day_vals[i])
|
|
1524
|
+
break
|
|
1525
|
+
else: # no rule applies; use default_day_schedule.
|
|
1526
|
+
values.extend(sch_day_vals[-1])
|
|
1527
|
+
dow += 1
|
|
1528
|
+
return values
|
|
1529
|
+
|
|
1530
|
+
def _get_week_list(self, rule_indices):
|
|
1531
|
+
"""Get a list of the ScheduleDay identifiers applied on each day of the week."""
|
|
1532
|
+
week_list = []
|
|
1533
|
+
for dow in range(7):
|
|
1534
|
+
for i in rule_indices:
|
|
1535
|
+
if self._schedule_rules[i].week_apply_tuple[dow]:
|
|
1536
|
+
week_list.append(self._schedule_rules[i].schedule_day.identifier)
|
|
1537
|
+
break
|
|
1538
|
+
else: # no rule applies; use default_day_schedule.
|
|
1539
|
+
week_list.append(self.default_day_schedule.identifier)
|
|
1540
|
+
return week_list
|
|
1541
|
+
|
|
1542
|
+
def _get_extra_week_fields(self):
|
|
1543
|
+
"""Get schedule identifiers of extra days in Schedule:Week."""
|
|
1544
|
+
# add summer and winter design days
|
|
1545
|
+
week_fields = []
|
|
1546
|
+
if self._holiday_schedule is not None:
|
|
1547
|
+
week_fields.append(self._holiday_schedule.identifier)
|
|
1548
|
+
else:
|
|
1549
|
+
week_fields.append(self._default_day_schedule.identifier)
|
|
1550
|
+
if self._summer_designday_schedule is not None:
|
|
1551
|
+
week_fields.append(self._summer_designday_schedule.identifier)
|
|
1552
|
+
else:
|
|
1553
|
+
week_fields.append(self._default_day_schedule.identifier)
|
|
1554
|
+
if self._winter_designday_schedule is not None:
|
|
1555
|
+
week_fields.append(self._winter_designday_schedule.identifier)
|
|
1556
|
+
else:
|
|
1557
|
+
week_fields.append(self._default_day_schedule.identifier)
|
|
1558
|
+
for _ in range(2): # add the extra 2 custom days that are rarely used in E+
|
|
1559
|
+
week_fields.append(self.default_day_schedule.identifier)
|
|
1560
|
+
return week_fields
|
|
1561
|
+
|
|
1562
|
+
def _idf_week_schedule_from_rule_indices(self, rule_indices, week_index):
|
|
1563
|
+
"""Create an IDF string of a week schedule from a list of rules indices."""
|
|
1564
|
+
week_sch_id = '{}_Week {}'.format(self.identifier, week_index)
|
|
1565
|
+
week_fields = [week_sch_id]
|
|
1566
|
+
# check rules that apply for the days of the week
|
|
1567
|
+
week_fields.extend(self._get_week_list(rule_indices))
|
|
1568
|
+
# add extra days (including summer and winter design days)
|
|
1569
|
+
week_fields.extend(self._get_extra_week_fields())
|
|
1570
|
+
week_schedule = generate_idf_string(
|
|
1571
|
+
'Schedule:Week:Daily', week_fields, self._schedule_week_comments)
|
|
1572
|
+
return week_schedule, week_sch_id
|
|
1573
|
+
|
|
1574
|
+
def _idf_week_schedule_from_week_list(self, week_list, week_index):
|
|
1575
|
+
"""Create an IDF string of a week schedule from a list ScheduleDay identifiers.
|
|
1576
|
+
"""
|
|
1577
|
+
week_sch_id = '{}_Week {}'.format(self.identifier, week_index)
|
|
1578
|
+
week_fields = [week_sch_id]
|
|
1579
|
+
week_fields.extend(week_list)
|
|
1580
|
+
week_fields.extend(self._get_extra_week_fields())
|
|
1581
|
+
week_schedule = generate_idf_string(
|
|
1582
|
+
'Schedule:Week:Daily', week_fields, self._schedule_week_comments)
|
|
1583
|
+
return week_schedule, week_sch_id
|
|
1584
|
+
|
|
1585
|
+
def _check_schedule_parent(self, schedule, sch_type='child'):
|
|
1586
|
+
"""Used to ensure that a ScheduleDay object has only one parent ScheduleRuleset.
|
|
1587
|
+
|
|
1588
|
+
This is important to ensure ScheduleRulesets remain self-contained units
|
|
1589
|
+
and that editing one ScheduleRuleset does not edit another one.
|
|
1590
|
+
"""
|
|
1591
|
+
if schedule._parent is None or schedule._parent is self:
|
|
1592
|
+
schedule._parent = self
|
|
1593
|
+
else:
|
|
1594
|
+
raise ValueError(
|
|
1595
|
+
'ScheduleDay objects can be assigned to a ScheduleRuleset only once.\n'
|
|
1596
|
+
'ScheduleDay "{}" cannot be the {} of ScheduleRuleset "{}" since it is '
|
|
1597
|
+
'already assigned to "{}".\nTry duplicating the ScheduleDay, changing '
|
|
1598
|
+
'its identifier, and then assigning it to this ScheduleRuleset.'.format(
|
|
1599
|
+
schedule.identifier, sch_type, self.identifier,
|
|
1600
|
+
schedule._parent.identifier))
|
|
1601
|
+
|
|
1602
|
+
def _check_schedule_rules(self, rules):
|
|
1603
|
+
"""Check schedule_rules whenever they come through the setter."""
|
|
1604
|
+
if rules is None:
|
|
1605
|
+
return []
|
|
1606
|
+
if not isinstance(rules, list):
|
|
1607
|
+
try:
|
|
1608
|
+
rules = list(rules)
|
|
1609
|
+
except (ValueError, TypeError):
|
|
1610
|
+
raise TypeError('ScheduleRuleset schedule_rules must be iterable.')
|
|
1611
|
+
for rule in rules:
|
|
1612
|
+
self._check_rule(rule)
|
|
1613
|
+
self._check_schedule_parent(rule.schedule_day, 'schedule_rule')
|
|
1614
|
+
return rules
|
|
1615
|
+
|
|
1616
|
+
@staticmethod
|
|
1617
|
+
def _check_rule(rule):
|
|
1618
|
+
"""Check that an individual rule is a ScheduleRule."""
|
|
1619
|
+
assert isinstance(rule, ScheduleRule), \
|
|
1620
|
+
'Expected ScheduleRule for ScheduleRuleset. Got {}.'.format(type(rule))
|
|
1621
|
+
|
|
1622
|
+
@staticmethod
|
|
1623
|
+
def _apply_designdays_with_check(sched, holiday_sch, summer_dd_sch, winter_dd_sch):
|
|
1624
|
+
"""Apply summer + winter design day schedules with a check for duplicates."""
|
|
1625
|
+
try:
|
|
1626
|
+
sched.holiday_schedule = holiday_sch
|
|
1627
|
+
except ValueError: # summer design day schedule is not unique
|
|
1628
|
+
holiday_sch = holiday_sch.duplicate()
|
|
1629
|
+
holiday_sch.identifier = '{}_Hol'.format(holiday_sch.identifier)
|
|
1630
|
+
sched.holiday_schedule = holiday_sch
|
|
1631
|
+
try:
|
|
1632
|
+
sched.summer_designday_schedule = summer_dd_sch
|
|
1633
|
+
except ValueError: # summer design day schedule is not unique
|
|
1634
|
+
summer_dd_sch = summer_dd_sch.duplicate()
|
|
1635
|
+
summer_dd_sch.identifier = '{}_SmrDsn'.format(summer_dd_sch.identifier)
|
|
1636
|
+
sched.summer_designday_schedule = summer_dd_sch
|
|
1637
|
+
try:
|
|
1638
|
+
sched.winter_designday_schedule = winter_dd_sch
|
|
1639
|
+
except ValueError: # winter design day schedule is not unique
|
|
1640
|
+
winter_dd_sch = winter_dd_sch.duplicate()
|
|
1641
|
+
winter_dd_sch.identifier = '{}_WntrDsn'.format(winter_dd_sch.identifier)
|
|
1642
|
+
sched.winter_designday_schedule = winter_dd_sch
|
|
1643
|
+
|
|
1644
|
+
@staticmethod
|
|
1645
|
+
def _idf_day_schedule_dictionary(day_idf_strings):
|
|
1646
|
+
"""Get a dictionary of DaySchedule objects from an IDF string list."""
|
|
1647
|
+
day_schedule_dict = {}
|
|
1648
|
+
for sch_str in day_idf_strings:
|
|
1649
|
+
sch_str = sch_str.strip()
|
|
1650
|
+
sch_obj = ScheduleDay.from_idf(sch_str)
|
|
1651
|
+
day_schedule_dict[sch_obj.identifier] = sch_obj
|
|
1652
|
+
return day_schedule_dict
|
|
1653
|
+
|
|
1654
|
+
@staticmethod
|
|
1655
|
+
def _idf_week_schedule_dictionary(week_idf_strings, day_sch_dict):
|
|
1656
|
+
"""Get a dictionary of ScheduleRule objects from Schedule:Week strings."""
|
|
1657
|
+
week_schedule_dict = {}
|
|
1658
|
+
week_designday_dict = {}
|
|
1659
|
+
for sch_str in week_idf_strings:
|
|
1660
|
+
sch_str = sch_str.strip()
|
|
1661
|
+
rules = ScheduleRule.extract_all_from_schedule_week(sch_str, day_sch_dict)
|
|
1662
|
+
if sch_str.startswith('Schedule:Week:Daily,'):
|
|
1663
|
+
ep_strs = parse_idf_string(sch_str)
|
|
1664
|
+
holiday = day_sch_dict[ep_strs[8]]
|
|
1665
|
+
summer_dd = day_sch_dict[ep_strs[9]]
|
|
1666
|
+
winter_dd = day_sch_dict[ep_strs[10]]
|
|
1667
|
+
else:
|
|
1668
|
+
ep_strs = parse_idf_string(sch_str, 'Schedule:Week:Compact,')
|
|
1669
|
+
holiday = summer_dd = winter_dd = rules[-1].schedule_day
|
|
1670
|
+
for i in range(1, len(ep_strs), 2):
|
|
1671
|
+
day_type, day_sch_id = ep_strs[i].lower(), ep_strs[i + 1]
|
|
1672
|
+
if 'holiday' in day_type:
|
|
1673
|
+
holiday = day_sch_dict[day_sch_id]
|
|
1674
|
+
elif 'summerdesignday' in day_type:
|
|
1675
|
+
summer_dd = day_sch_dict[day_sch_id]
|
|
1676
|
+
elif 'winterdesignday' in day_type:
|
|
1677
|
+
winter_dd = day_sch_dict[day_sch_id]
|
|
1678
|
+
sch_week_id = ep_strs[0]
|
|
1679
|
+
week_schedule_dict[sch_week_id] = rules
|
|
1680
|
+
week_designday_dict[sch_week_id] = [holiday, summer_dd, winter_dd]
|
|
1681
|
+
return week_schedule_dict, week_designday_dict
|
|
1682
|
+
|
|
1683
|
+
@staticmethod
|
|
1684
|
+
def _idf_schedule_type_dictionary(type_idf_strings):
|
|
1685
|
+
"""Get a dictionary of ScheduleTypeLimit objects from ScheduleTypeLimits strings.
|
|
1686
|
+
"""
|
|
1687
|
+
sch_type_dict = {}
|
|
1688
|
+
for type_str in type_idf_strings:
|
|
1689
|
+
type_str = type_str.strip()
|
|
1690
|
+
type_obj = ScheduleTypeLimit.from_idf(type_str)
|
|
1691
|
+
sch_type_dict[type_obj.identifier] = type_obj
|
|
1692
|
+
return sch_type_dict
|
|
1693
|
+
|
|
1694
|
+
@staticmethod
|
|
1695
|
+
def _combine_week_schedules(unique_rule_sets, week_schedules, rules_each_day):
|
|
1696
|
+
"""Create the average week schedules from the unique combinations of rules."""
|
|
1697
|
+
# create a dictionary mapping unique rule indices to average week schedules
|
|
1698
|
+
rule_set_map = {}
|
|
1699
|
+
for rule_i, week_sched in zip(unique_rule_sets, week_schedules):
|
|
1700
|
+
rule_set_map[rule_i] = week_sched
|
|
1701
|
+
# loop through all 365 days of the year to find when rules change
|
|
1702
|
+
yr_wk_scheds = []
|
|
1703
|
+
yr_wk_dt_range = []
|
|
1704
|
+
prev_week_rules = None
|
|
1705
|
+
for doy in range(1, 366):
|
|
1706
|
+
week_rules = rules_each_day[doy - 1]
|
|
1707
|
+
if week_rules != prev_week_rules: # change to a new rule set
|
|
1708
|
+
yr_wk_scheds.append(rule_set_map[week_rules])
|
|
1709
|
+
if doy != 1:
|
|
1710
|
+
yr_wk_dt_range[-1].append(Date.from_doy(doy - 1))
|
|
1711
|
+
yr_wk_dt_range.append([Date.from_doy(doy)])
|
|
1712
|
+
else:
|
|
1713
|
+
yr_wk_dt_range.append([Date(1, 1)])
|
|
1714
|
+
prev_week_rules = week_rules
|
|
1715
|
+
yr_wk_dt_range[-1].append(Date(12, 31))
|
|
1716
|
+
|
|
1717
|
+
# convert week ScheduleRulesets to_rules and assign start + end dates
|
|
1718
|
+
final_rules = []
|
|
1719
|
+
for wk_sch, dt_range in zip(yr_wk_scheds, yr_wk_dt_range):
|
|
1720
|
+
final_rules.extend(wk_sch.to_rules(dt_range[0], dt_range[1]))
|
|
1721
|
+
|
|
1722
|
+
# add all rules to a final average ScheduleRuleset
|
|
1723
|
+
holiday_sch = yr_wk_scheds[0].holiday_schedule.duplicate()
|
|
1724
|
+
summer_dd_sch = yr_wk_scheds[0].summer_designday_schedule.duplicate()
|
|
1725
|
+
winter_dd_sch = yr_wk_scheds[0].winter_designday_schedule.duplicate()
|
|
1726
|
+
return final_rules, holiday_sch, summer_dd_sch, winter_dd_sch
|
|
1727
|
+
|
|
1728
|
+
@staticmethod
|
|
1729
|
+
def _get_avg_week(identifier, schedules, weights, timestep_resolution, rule_indices):
|
|
1730
|
+
"""Get an average week schedule across several schedules and rule_indices."""
|
|
1731
|
+
# get matrix with each ruleset schedule in rows and each day of week in cols
|
|
1732
|
+
val_mtx = ScheduleRuleset._sch_value_mtx(
|
|
1733
|
+
schedules, timestep_resolution, rule_indices)
|
|
1734
|
+
# transpose the matrix and compute weighted average values for each dow
|
|
1735
|
+
avg_mtx = []
|
|
1736
|
+
for dow_list in zip(*val_mtx):
|
|
1737
|
+
sch_vals = [sum([val * weights[i] for i, val in enumerate(values)])
|
|
1738
|
+
for values in zip(*dow_list)]
|
|
1739
|
+
avg_mtx.append(sch_vals)
|
|
1740
|
+
# create the final ScheduleRuleset from the values
|
|
1741
|
+
return ScheduleRuleset.from_week_daily_values(
|
|
1742
|
+
identifier, avg_mtx[0], avg_mtx[1], avg_mtx[2], avg_mtx[3], avg_mtx[4],
|
|
1743
|
+
avg_mtx[5], avg_mtx[6], avg_mtx[7], timestep_resolution,
|
|
1744
|
+
schedules[0].schedule_type_limit, avg_mtx[8], avg_mtx[9])
|
|
1745
|
+
|
|
1746
|
+
@staticmethod
|
|
1747
|
+
def _get_ext_week(identifier, schedules, operator, timestep_resolution, rule_indices):
|
|
1748
|
+
"""Get a max or min week schedule across several schedules and rule_indices.
|
|
1749
|
+
|
|
1750
|
+
Note that the operator is expected to be either the native Python max
|
|
1751
|
+
operator or the native Python min operator depending on the type of extreme
|
|
1752
|
+
being requested.
|
|
1753
|
+
"""
|
|
1754
|
+
# get matrix with each ruleset schedule in rows and each day of week in cols
|
|
1755
|
+
val_mtx = ScheduleRuleset._sch_value_mtx(
|
|
1756
|
+
schedules, timestep_resolution, rule_indices)
|
|
1757
|
+
# transpose the matrix and compute weighted average values for each dow
|
|
1758
|
+
ext_mtx = []
|
|
1759
|
+
for dow_list in zip(*val_mtx):
|
|
1760
|
+
sch_vals = [operator(values) for values in zip(*dow_list)]
|
|
1761
|
+
ext_mtx.append(sch_vals)
|
|
1762
|
+
# create the final ScheduleRuleset from the values
|
|
1763
|
+
return ScheduleRuleset.from_week_daily_values(
|
|
1764
|
+
identifier, ext_mtx[0], ext_mtx[1], ext_mtx[2], ext_mtx[3], ext_mtx[4],
|
|
1765
|
+
ext_mtx[5], ext_mtx[6], ext_mtx[7], timestep_resolution,
|
|
1766
|
+
schedules[0].schedule_type_limit, ext_mtx[8], ext_mtx[9])
|
|
1767
|
+
|
|
1768
|
+
@staticmethod
|
|
1769
|
+
def _sch_value_mtx(schedules, timestep_resolution, rule_indices):
|
|
1770
|
+
"""Get a matrix with each ruleset schedule in rows and each day of week in cols.
|
|
1771
|
+
"""
|
|
1772
|
+
# get matrix with each ruleset schedule in rows and each day of week in cols
|
|
1773
|
+
val_mtx = []
|
|
1774
|
+
for s_i, sched in enumerate(schedules):
|
|
1775
|
+
week_list = []
|
|
1776
|
+
for dow in range(7):
|
|
1777
|
+
for i in rule_indices[s_i]: # see if rules apply
|
|
1778
|
+
if sched[i].week_apply_tuple[dow]:
|
|
1779
|
+
week_list.append(sched[i].schedule_day)
|
|
1780
|
+
break
|
|
1781
|
+
else: # no rule applies; use default_day_schedule.
|
|
1782
|
+
week_list.append(sched.default_day_schedule)
|
|
1783
|
+
|
|
1784
|
+
# check the rules applied for holidays + summer and winter design days
|
|
1785
|
+
holiday = sched.default_day_schedule if sched._holiday_schedule \
|
|
1786
|
+
is None else sched._holiday_schedule
|
|
1787
|
+
week_list.append(holiday)
|
|
1788
|
+
summer = sched.default_day_schedule if sched._summer_designday_schedule \
|
|
1789
|
+
is None else sched._summer_designday_schedule
|
|
1790
|
+
week_list.append(summer)
|
|
1791
|
+
winter = sched.default_day_schedule if sched._winter_designday_schedule \
|
|
1792
|
+
is None else sched._winter_designday_schedule
|
|
1793
|
+
week_list.append(winter)
|
|
1794
|
+
# add all values to the matrix
|
|
1795
|
+
val_mtx.append([day_sch.values_at_timestep(timestep_resolution)
|
|
1796
|
+
for day_sch in week_list])
|
|
1797
|
+
return val_mtx
|
|
1798
|
+
|
|
1799
|
+
@staticmethod
|
|
1800
|
+
def _instance_in_array(object_instance, object_array):
|
|
1801
|
+
"""Check if a specific object instance is already in an array.
|
|
1802
|
+
|
|
1803
|
+
This can be much faster than `if object_instance in object_array`
|
|
1804
|
+
when you expect to be testing a lot of the same instance of an object for
|
|
1805
|
+
inclusion in an array since the builtin method uses an == operator to
|
|
1806
|
+
test inclusion.
|
|
1807
|
+
"""
|
|
1808
|
+
for val in object_array:
|
|
1809
|
+
if val is object_instance:
|
|
1810
|
+
return True
|
|
1811
|
+
return False
|
|
1812
|
+
|
|
1813
|
+
def __bool__(self):
|
|
1814
|
+
return True
|
|
1815
|
+
|
|
1816
|
+
def __nonzero__(self):
|
|
1817
|
+
return True
|
|
1818
|
+
|
|
1819
|
+
def __len__(self):
|
|
1820
|
+
return len(self._schedule_rules)
|
|
1821
|
+
|
|
1822
|
+
def __getitem__(self, key):
|
|
1823
|
+
return self._schedule_rules[key]
|
|
1824
|
+
|
|
1825
|
+
def __iter__(self):
|
|
1826
|
+
return iter(self._schedule_rules)
|
|
1827
|
+
|
|
1828
|
+
def __key(self):
|
|
1829
|
+
"""A tuple based on the object properties, useful for hashing."""
|
|
1830
|
+
return (self.identifier, hash(self.default_day_schedule),
|
|
1831
|
+
hash(self.holiday_schedule), hash(self.summer_designday_schedule),
|
|
1832
|
+
hash(self.winter_designday_schedule), hash(self.schedule_type_limit)) + \
|
|
1833
|
+
tuple(hash(rule) for rule in self.schedule_rules)
|
|
1834
|
+
|
|
1835
|
+
def __hash__(self):
|
|
1836
|
+
return hash(self.__key())
|
|
1837
|
+
|
|
1838
|
+
def __eq__(self, other):
|
|
1839
|
+
return isinstance(other, ScheduleRuleset) and self.__key() == other.__key()
|
|
1840
|
+
|
|
1841
|
+
def __ne__(self, other):
|
|
1842
|
+
return not self.__eq__(other)
|
|
1843
|
+
|
|
1844
|
+
def __copy__(self):
|
|
1845
|
+
holiday = self._holiday_schedule.duplicate() if \
|
|
1846
|
+
self._holiday_schedule is not None else None
|
|
1847
|
+
summer = self._summer_designday_schedule.duplicate() if \
|
|
1848
|
+
self._summer_designday_schedule is not None else None
|
|
1849
|
+
winter = self._winter_designday_schedule.duplicate() if \
|
|
1850
|
+
self._winter_designday_schedule is not None else None
|
|
1851
|
+
new_obj = ScheduleRuleset(
|
|
1852
|
+
self.identifier, self.default_day_schedule.duplicate(),
|
|
1853
|
+
[rule.duplicate() for rule in self._schedule_rules],
|
|
1854
|
+
self._schedule_type_limit, holiday, summer, winter)
|
|
1855
|
+
new_obj._display_name = self._display_name
|
|
1856
|
+
new_obj._user_data = None if self._user_data is None else self._user_data.copy()
|
|
1857
|
+
new_obj._properties._duplicate_extension_attr(self._properties)
|
|
1858
|
+
return new_obj
|
|
1859
|
+
|
|
1860
|
+
def ToString(self):
|
|
1861
|
+
"""Overwrite .NET ToString."""
|
|
1862
|
+
return self.__repr__()
|
|
1863
|
+
|
|
1864
|
+
def __repr__(self):
|
|
1865
|
+
return 'ScheduleRuleset: {} [default day: {}] [{} rules]'.format(
|
|
1866
|
+
self.display_name, self.default_day_schedule.display_name,
|
|
1867
|
+
len(self._schedule_rules))
|