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